From 209c6904135bfde33632acec4763e105bcbc3997 Mon Sep 17 00:00:00 2001 From: ComputerTech312 Date: Thu, 2 Oct 2025 19:22:48 +0100 Subject: [PATCH] hm --- app.py | 2 +- setup.sh | 0 main.js => static/main.js | 0 styles.css => static/styles.css | 0 index.html => templates/index.html | 0 venv/bin/Activate.ps1 | 247 + venv/bin/activate | 70 + venv/bin/activate.csh | 27 + venv/bin/activate.fish | 69 + venv/bin/flask | 8 + venv/bin/pip | 8 + venv/bin/pip3 | 8 + venv/bin/pip3.12 | 8 + venv/bin/python | 1 + venv/bin/python3 | 1 + venv/bin/python3.12 | 1 + .../site/python3.12/greenlet/greenlet.h | 164 + .../Flask_SocketIO-5.3.6.dist-info/INSTALLER | 1 + .../Flask_SocketIO-5.3.6.dist-info/LICENSE | 20 + .../Flask_SocketIO-5.3.6.dist-info/METADATA | 77 + .../Flask_SocketIO-5.3.6.dist-info/RECORD | 13 + .../Flask_SocketIO-5.3.6.dist-info/REQUESTED | 0 .../Flask_SocketIO-5.3.6.dist-info/WHEEL | 5 + .../top_level.txt | 1 + .../__pycache__/six.cpython-312.pyc | Bin 0 -> 41392 bytes .../bidict-0.23.1.dist-info/INSTALLER | 1 + .../bidict-0.23.1.dist-info/LICENSE | 376 + .../bidict-0.23.1.dist-info/METADATA | 260 + .../bidict-0.23.1.dist-info/RECORD | 31 + .../bidict-0.23.1.dist-info/WHEEL | 5 + .../bidict-0.23.1.dist-info/top_level.txt | 1 + .../site-packages/bidict/__init__.py | 103 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2483 bytes .../bidict/__pycache__/_abc.cpython-312.pyc | Bin 0 -> 2797 bytes .../bidict/__pycache__/_base.cpython-312.pyc | Bin 0 -> 23195 bytes .../__pycache__/_bidict.cpython-312.pyc | Bin 0 -> 8497 bytes .../bidict/__pycache__/_dup.cpython-312.pyc | Bin 0 -> 2228 bytes .../bidict/__pycache__/_exc.cpython-312.pyc | Bin 0 -> 1552 bytes .../__pycache__/_frozen.cpython-312.pyc | Bin 0 -> 1506 bytes .../bidict/__pycache__/_iter.cpython-312.pyc | Bin 0 -> 2341 bytes .../__pycache__/_orderedbase.cpython-312.pyc | Bin 0 -> 11152 bytes .../_orderedbidict.cpython-312.pyc | Bin 0 -> 7621 bytes .../__pycache__/_typing.cpython-312.pyc | Bin 0 -> 2282 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 540 bytes .../python3.12/site-packages/bidict/_abc.py | 79 + .../python3.12/site-packages/bidict/_base.py | 556 ++ .../site-packages/bidict/_bidict.py | 194 + .../python3.12/site-packages/bidict/_dup.py | 61 + .../python3.12/site-packages/bidict/_exc.py | 36 + .../site-packages/bidict/_frozen.py | 50 + .../python3.12/site-packages/bidict/_iter.py | 51 + .../site-packages/bidict/_orderedbase.py | 238 + .../site-packages/bidict/_orderedbidict.py | 172 + .../site-packages/bidict/_typing.py | 49 + .../site-packages/bidict/metadata.py | 14 + .../python3.12/site-packages/bidict/py.typed | 1 + .../blinker-1.9.0.dist-info/INSTALLER | 1 + .../blinker-1.9.0.dist-info/LICENSE.txt | 20 + .../blinker-1.9.0.dist-info/METADATA | 60 + .../blinker-1.9.0.dist-info/RECORD | 12 + .../blinker-1.9.0.dist-info/WHEEL | 4 + .../site-packages/blinker/__init__.py | 17 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 481 bytes .../__pycache__/_utilities.cpython-312.pyc | Bin 0 -> 2708 bytes .../blinker/__pycache__/base.cpython-312.pyc | Bin 0 -> 21985 bytes .../site-packages/blinker/_utilities.py | 64 + .../python3.12/site-packages/blinker/base.py | 512 + .../python3.12/site-packages/blinker/py.typed | 0 .../click-8.3.0.dist-info/INSTALLER | 1 + .../click-8.3.0.dist-info/METADATA | 84 + .../click-8.3.0.dist-info/RECORD | 40 + .../site-packages/click-8.3.0.dist-info/WHEEL | 4 + .../licenses/LICENSE.txt | 28 + .../site-packages/click/__init__.py | 123 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4053 bytes .../click/__pycache__/_compat.cpython-312.pyc | Bin 0 -> 24175 bytes .../__pycache__/_termui_impl.cpython-312.pyc | Bin 0 -> 31592 bytes .../__pycache__/_textwrap.cpython-312.pyc | Bin 0 -> 2406 bytes .../click/__pycache__/_utils.cpython-312.pyc | Bin 0 -> 1181 bytes .../__pycache__/_winconsole.cpython-312.pyc | Bin 0 -> 11751 bytes .../click/__pycache__/core.cpython-312.pyc | Bin 0 -> 133412 bytes .../__pycache__/decorators.cpython-312.pyc | Bin 0 -> 22118 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 14757 bytes .../__pycache__/formatting.cpython-312.pyc | Bin 0 -> 13653 bytes .../click/__pycache__/globals.cpython-312.pyc | Bin 0 -> 2946 bytes .../click/__pycache__/parser.cpython-312.pyc | Bin 0 -> 20423 bytes .../shell_completion.cpython-312.pyc | Bin 0 -> 23318 bytes .../click/__pycache__/termui.cpython-312.pyc | Bin 0 -> 34504 bytes .../click/__pycache__/testing.cpython-312.pyc | Bin 0 -> 27390 bytes .../click/__pycache__/types.cpython-312.pyc | Bin 0 -> 50019 bytes .../click/__pycache__/utils.cpython-312.pyc | Bin 0 -> 24860 bytes .../python3.12/site-packages/click/_compat.py | 622 ++ .../site-packages/click/_termui_impl.py | 847 ++ .../site-packages/click/_textwrap.py | 51 + .../python3.12/site-packages/click/_utils.py | 36 + .../site-packages/click/_winconsole.py | 296 + .../python3.12/site-packages/click/core.py | 3347 +++++++ .../site-packages/click/decorators.py | 551 ++ .../site-packages/click/exceptions.py | 308 + .../site-packages/click/formatting.py | 301 + .../python3.12/site-packages/click/globals.py | 67 + .../python3.12/site-packages/click/parser.py | 532 + .../python3.12/site-packages/click/py.typed | 0 .../site-packages/click/shell_completion.py | 667 ++ .../python3.12/site-packages/click/termui.py | 877 ++ .../python3.12/site-packages/click/testing.py | 577 ++ .../python3.12/site-packages/click/types.py | 1209 +++ .../python3.12/site-packages/click/utils.py | 627 ++ .../python3.12/site-packages/dns/__init__.py | 72 + .../dns/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 725 bytes .../__pycache__/_asyncbackend.cpython-312.pyc | Bin 0 -> 4830 bytes .../_asyncio_backend.cpython-312.pyc | Bin 0 -> 14336 bytes .../dns/__pycache__/_ddr.cpython-312.pyc | Bin 0 -> 7824 bytes .../dns/__pycache__/_features.cpython-312.pyc | Bin 0 -> 3294 bytes .../_immutable_ctx.cpython-312.pyc | Bin 0 -> 3220 bytes .../dns/__pycache__/_no_ssl.cpython-312.pyc | Bin 0 -> 3141 bytes .../dns/__pycache__/_tls_util.cpython-312.pyc | Bin 0 -> 878 bytes .../__pycache__/_trio_backend.cpython-312.pyc | Bin 0 -> 13610 bytes .../__pycache__/asyncbackend.cpython-312.pyc | Bin 0 -> 3403 bytes .../__pycache__/asyncquery.cpython-312.pyc | Bin 0 -> 38582 bytes .../__pycache__/asyncresolver.cpython-312.pyc | Bin 0 -> 21131 bytes .../dns/__pycache__/btree.cpython-312.pyc | Bin 0 -> 40880 bytes .../dns/__pycache__/btreezone.cpython-312.pyc | Bin 0 -> 21013 bytes .../dns/__pycache__/dnssec.cpython-312.pyc | Bin 0 -> 51233 bytes .../__pycache__/dnssectypes.cpython-312.pyc | Bin 0 -> 1961 bytes .../dns/__pycache__/e164.cpython-312.pyc | Bin 0 -> 4791 bytes .../dns/__pycache__/edns.cpython-312.pyc | Bin 0 -> 26109 bytes .../dns/__pycache__/entropy.cpython-312.pyc | Bin 0 -> 5959 bytes .../dns/__pycache__/enum.cpython-312.pyc | Bin 0 -> 4856 bytes .../dns/__pycache__/exception.cpython-312.pyc | Bin 0 -> 7192 bytes .../dns/__pycache__/flags.cpython-312.pyc | Bin 0 -> 3063 bytes .../dns/__pycache__/grange.cpython-312.pyc | Bin 0 -> 1778 bytes .../dns/__pycache__/immutable.cpython-312.pyc | Bin 0 -> 3773 bytes .../dns/__pycache__/inet.cpython-312.pyc | Bin 0 -> 6655 bytes .../dns/__pycache__/ipv4.cpython-312.pyc | Bin 0 -> 2553 bytes .../dns/__pycache__/ipv6.cpython-312.pyc | Bin 0 -> 6679 bytes .../dns/__pycache__/message.cpython-312.pyc | Bin 0 -> 88757 bytes .../dns/__pycache__/name.cpython-312.pyc | Bin 0 -> 49166 bytes .../dns/__pycache__/namedict.cpython-312.pyc | Bin 0 -> 4347 bytes .../__pycache__/nameserver.cpython-312.pyc | Bin 0 -> 14186 bytes .../dns/__pycache__/node.cpython-312.pyc | Bin 0 -> 16573 bytes .../dns/__pycache__/opcode.cpython-312.pyc | Bin 0 -> 3201 bytes .../dns/__pycache__/query.cpython-312.pyc | Bin 0 -> 69404 bytes .../dns/__pycache__/rcode.cpython-312.pyc | Bin 0 -> 4477 bytes .../dns/__pycache__/rdata.cpython-312.pyc | Bin 0 -> 39787 bytes .../__pycache__/rdataclass.cpython-312.pyc | Bin 0 -> 3509 bytes .../dns/__pycache__/rdataset.cpython-312.pyc | Bin 0 -> 22923 bytes .../dns/__pycache__/rdatatype.cpython-312.pyc | Bin 0 -> 10289 bytes .../dns/__pycache__/renderer.cpython-312.pyc | Bin 0 -> 16303 bytes .../dns/__pycache__/resolver.cpython-312.pyc | Bin 0 -> 88416 bytes .../__pycache__/reversename.cpython-312.pyc | Bin 0 -> 4761 bytes .../dns/__pycache__/rrset.cpython-312.pyc | Bin 0 -> 12455 bytes .../dns/__pycache__/serial.cpython-312.pyc | Bin 0 -> 5173 bytes .../dns/__pycache__/set.cpython-312.pyc | Bin 0 -> 12261 bytes .../dns/__pycache__/tokenizer.cpython-312.pyc | Bin 0 -> 26528 bytes .../__pycache__/transaction.cpython-312.pyc | Bin 0 -> 29286 bytes .../dns/__pycache__/tsig.cpython-312.pyc | Bin 0 -> 17008 bytes .../__pycache__/tsigkeyring.cpython-312.pyc | Bin 0 -> 2843 bytes .../dns/__pycache__/ttl.cpython-312.pyc | Bin 0 -> 2334 bytes .../dns/__pycache__/update.cpython-312.pyc | Bin 0 -> 16266 bytes .../dns/__pycache__/version.cpython-312.pyc | Bin 0 -> 754 bytes .../dns/__pycache__/versioned.cpython-312.pyc | Bin 0 -> 14796 bytes .../dns/__pycache__/win32util.cpython-312.pyc | Bin 0 -> 19768 bytes .../dns/__pycache__/wire.cpython-312.pyc | Bin 0 -> 5766 bytes .../dns/__pycache__/xfr.cpython-312.pyc | Bin 0 -> 15034 bytes .../dns/__pycache__/zone.cpython-312.pyc | Bin 0 -> 68604 bytes .../dns/__pycache__/zonefile.cpython-312.pyc | Bin 0 -> 34511 bytes .../dns/__pycache__/zonetypes.cpython-312.pyc | Bin 0 -> 1335 bytes .../site-packages/dns/_asyncbackend.py | 100 + .../site-packages/dns/_asyncio_backend.py | 276 + venv/lib/python3.12/site-packages/dns/_ddr.py | 154 + .../python3.12/site-packages/dns/_features.py | 95 + .../site-packages/dns/_immutable_ctx.py | 76 + .../python3.12/site-packages/dns/_no_ssl.py | 61 + .../python3.12/site-packages/dns/_tls_util.py | 19 + .../site-packages/dns/_trio_backend.py | 255 + .../site-packages/dns/asyncbackend.py | 101 + .../site-packages/dns/asyncquery.py | 953 ++ .../site-packages/dns/asyncresolver.py | 478 + .../lib/python3.12/site-packages/dns/btree.py | 850 ++ .../python3.12/site-packages/dns/btreezone.py | 367 + .../python3.12/site-packages/dns/dnssec.py | 1242 +++ .../site-packages/dns/dnssecalgs/__init__.py | 124 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5474 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 4579 bytes .../__pycache__/cryptography.cpython-312.pyc | Bin 0 -> 3789 bytes .../__pycache__/dsa.cpython-312.pyc | Bin 0 -> 6218 bytes .../__pycache__/ecdsa.cpython-312.pyc | Bin 0 -> 6083 bytes .../__pycache__/eddsa.cpython-312.pyc | Bin 0 -> 4188 bytes .../__pycache__/rsa.cpython-312.pyc | Bin 0 -> 7249 bytes .../site-packages/dns/dnssecalgs/base.py | 89 + .../dns/dnssecalgs/cryptography.py | 68 + .../site-packages/dns/dnssecalgs/dsa.py | 108 + .../site-packages/dns/dnssecalgs/ecdsa.py | 100 + .../site-packages/dns/dnssecalgs/eddsa.py | 70 + .../site-packages/dns/dnssecalgs/rsa.py | 126 + .../site-packages/dns/dnssectypes.py | 71 + venv/lib/python3.12/site-packages/dns/e164.py | 116 + venv/lib/python3.12/site-packages/dns/edns.py | 591 ++ .../python3.12/site-packages/dns/entropy.py | 130 + venv/lib/python3.12/site-packages/dns/enum.py | 113 + .../python3.12/site-packages/dns/exception.py | 169 + .../lib/python3.12/site-packages/dns/flags.py | 123 + .../python3.12/site-packages/dns/grange.py | 72 + .../python3.12/site-packages/dns/immutable.py | 68 + venv/lib/python3.12/site-packages/dns/inet.py | 195 + venv/lib/python3.12/site-packages/dns/ipv4.py | 76 + venv/lib/python3.12/site-packages/dns/ipv6.py | 217 + .../python3.12/site-packages/dns/message.py | 1954 ++++ venv/lib/python3.12/site-packages/dns/name.py | 1289 +++ .../python3.12/site-packages/dns/namedict.py | 109 + .../site-packages/dns/nameserver.py | 361 + venv/lib/python3.12/site-packages/dns/node.py | 358 + .../python3.12/site-packages/dns/opcode.py | 119 + .../lib/python3.12/site-packages/dns/py.typed | 0 .../lib/python3.12/site-packages/dns/query.py | 1786 ++++ .../site-packages/dns/quic/__init__.py | 78 + .../quic/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3293 bytes .../quic/__pycache__/_asyncio.cpython-312.pyc | Bin 0 -> 18865 bytes .../quic/__pycache__/_common.cpython-312.pyc | Bin 0 -> 15041 bytes .../quic/__pycache__/_sync.cpython-312.pyc | Bin 0 -> 19786 bytes .../quic/__pycache__/_trio.cpython-312.pyc | Bin 0 -> 16127 bytes .../site-packages/dns/quic/_asyncio.py | 276 + .../site-packages/dns/quic/_common.py | 344 + .../site-packages/dns/quic/_sync.py | 306 + .../site-packages/dns/quic/_trio.py | 250 + .../lib/python3.12/site-packages/dns/rcode.py | 168 + .../lib/python3.12/site-packages/dns/rdata.py | 935 ++ .../site-packages/dns/rdataclass.py | 118 + .../python3.12/site-packages/dns/rdataset.py | 508 + .../python3.12/site-packages/dns/rdatatype.py | 338 + .../site-packages/dns/rdtypes/ANY/AFSDB.py | 45 + .../site-packages/dns/rdtypes/ANY/AMTRELAY.py | 89 + .../site-packages/dns/rdtypes/ANY/AVC.py | 26 + .../site-packages/dns/rdtypes/ANY/CAA.py | 67 + .../site-packages/dns/rdtypes/ANY/CDNSKEY.py | 33 + .../site-packages/dns/rdtypes/ANY/CDS.py | 29 + .../site-packages/dns/rdtypes/ANY/CERT.py | 113 + .../site-packages/dns/rdtypes/ANY/CNAME.py | 28 + .../site-packages/dns/rdtypes/ANY/CSYNC.py | 68 + .../site-packages/dns/rdtypes/ANY/DLV.py | 24 + .../site-packages/dns/rdtypes/ANY/DNAME.py | 27 + .../site-packages/dns/rdtypes/ANY/DNSKEY.py | 33 + .../site-packages/dns/rdtypes/ANY/DS.py | 24 + .../site-packages/dns/rdtypes/ANY/DSYNC.py | 72 + .../site-packages/dns/rdtypes/ANY/EUI48.py | 30 + .../site-packages/dns/rdtypes/ANY/EUI64.py | 30 + .../site-packages/dns/rdtypes/ANY/GPOS.py | 126 + .../site-packages/dns/rdtypes/ANY/HINFO.py | 64 + .../site-packages/dns/rdtypes/ANY/HIP.py | 85 + .../site-packages/dns/rdtypes/ANY/ISDN.py | 78 + .../site-packages/dns/rdtypes/ANY/L32.py | 42 + .../site-packages/dns/rdtypes/ANY/L64.py | 48 + .../site-packages/dns/rdtypes/ANY/LOC.py | 347 + .../site-packages/dns/rdtypes/ANY/LP.py | 42 + .../site-packages/dns/rdtypes/ANY/MX.py | 24 + .../site-packages/dns/rdtypes/ANY/NID.py | 48 + .../site-packages/dns/rdtypes/ANY/NINFO.py | 26 + .../site-packages/dns/rdtypes/ANY/NS.py | 24 + .../site-packages/dns/rdtypes/ANY/NSEC.py | 67 + .../site-packages/dns/rdtypes/ANY/NSEC3.py | 120 + .../dns/rdtypes/ANY/NSEC3PARAM.py | 69 + .../dns/rdtypes/ANY/OPENPGPKEY.py | 53 + .../site-packages/dns/rdtypes/ANY/OPT.py | 77 + .../site-packages/dns/rdtypes/ANY/PTR.py | 24 + .../site-packages/dns/rdtypes/ANY/RESINFO.py | 24 + .../site-packages/dns/rdtypes/ANY/RP.py | 58 + .../site-packages/dns/rdtypes/ANY/RRSIG.py | 155 + .../site-packages/dns/rdtypes/ANY/RT.py | 24 + .../site-packages/dns/rdtypes/ANY/SMIMEA.py | 9 + .../site-packages/dns/rdtypes/ANY/SOA.py | 78 + .../site-packages/dns/rdtypes/ANY/SPF.py | 26 + .../site-packages/dns/rdtypes/ANY/SSHFP.py | 67 + .../site-packages/dns/rdtypes/ANY/TKEY.py | 135 + .../site-packages/dns/rdtypes/ANY/TLSA.py | 9 + .../site-packages/dns/rdtypes/ANY/TSIG.py | 160 + .../site-packages/dns/rdtypes/ANY/TXT.py | 24 + .../site-packages/dns/rdtypes/ANY/URI.py | 79 + .../site-packages/dns/rdtypes/ANY/WALLET.py | 9 + .../site-packages/dns/rdtypes/ANY/X25.py | 57 + .../site-packages/dns/rdtypes/ANY/ZONEMD.py | 64 + .../site-packages/dns/rdtypes/ANY/__init__.py | 71 + .../ANY/__pycache__/AFSDB.cpython-312.pyc | Bin 0 -> 1052 bytes .../ANY/__pycache__/AMTRELAY.cpython-312.pyc | Bin 0 -> 4203 bytes .../ANY/__pycache__/AVC.cpython-312.pyc | Bin 0 -> 625 bytes .../ANY/__pycache__/CAA.cpython-312.pyc | Bin 0 -> 3361 bytes .../ANY/__pycache__/CDNSKEY.cpython-312.pyc | Bin 0 -> 712 bytes .../ANY/__pycache__/CDS.cpython-312.pyc | Bin 0 -> 820 bytes .../ANY/__pycache__/CERT.cpython-312.pyc | Bin 0 -> 4469 bytes .../ANY/__pycache__/CNAME.cpython-312.pyc | Bin 0 -> 835 bytes .../ANY/__pycache__/CSYNC.cpython-312.pyc | Bin 0 -> 3320 bytes .../ANY/__pycache__/DLV.cpython-312.pyc | Bin 0 -> 622 bytes .../ANY/__pycache__/DNAME.cpython-312.pyc | Bin 0 -> 898 bytes .../ANY/__pycache__/DNSKEY.cpython-312.pyc | Bin 0 -> 709 bytes .../ANY/__pycache__/DS.cpython-312.pyc | Bin 0 -> 619 bytes .../ANY/__pycache__/DSYNC.cpython-312.pyc | Bin 0 -> 4295 bytes .../ANY/__pycache__/EUI48.cpython-312.pyc | Bin 0 -> 707 bytes .../ANY/__pycache__/EUI64.cpython-312.pyc | Bin 0 -> 707 bytes .../ANY/__pycache__/GPOS.cpython-312.pyc | Bin 0 -> 6089 bytes .../ANY/__pycache__/HINFO.cpython-312.pyc | Bin 0 -> 2954 bytes .../ANY/__pycache__/HIP.cpython-312.pyc | Bin 0 -> 4820 bytes .../ANY/__pycache__/ISDN.cpython-312.pyc | Bin 0 -> 3421 bytes .../ANY/__pycache__/L32.cpython-312.pyc | Bin 0 -> 2508 bytes .../ANY/__pycache__/L64.cpython-312.pyc | Bin 0 -> 2966 bytes .../ANY/__pycache__/LOC.cpython-312.pyc | Bin 0 -> 14115 bytes .../ANY/__pycache__/LP.cpython-312.pyc | Bin 0 -> 2457 bytes .../ANY/__pycache__/MX.cpython-312.pyc | Bin 0 -> 619 bytes .../ANY/__pycache__/NID.cpython-312.pyc | Bin 0 -> 2959 bytes .../ANY/__pycache__/NINFO.cpython-312.pyc | Bin 0 -> 631 bytes .../ANY/__pycache__/NS.cpython-312.pyc | Bin 0 -> 619 bytes .../ANY/__pycache__/NSEC.cpython-312.pyc | Bin 0 -> 3063 bytes .../ANY/__pycache__/NSEC3.cpython-312.pyc | Bin 0 -> 6367 bytes .../__pycache__/NSEC3PARAM.cpython-312.pyc | Bin 0 -> 3382 bytes .../__pycache__/OPENPGPKEY.cpython-312.pyc | Bin 0 -> 2297 bytes .../ANY/__pycache__/OPT.cpython-312.pyc | Bin 0 -> 3511 bytes .../ANY/__pycache__/PTR.cpython-312.pyc | Bin 0 -> 622 bytes .../ANY/__pycache__/RESINFO.cpython-312.pyc | Bin 0 -> 637 bytes .../ANY/__pycache__/RP.cpython-312.pyc | Bin 0 -> 2517 bytes .../ANY/__pycache__/RRSIG.cpython-312.pyc | Bin 0 -> 6584 bytes .../ANY/__pycache__/RT.cpython-312.pyc | Bin 0 -> 637 bytes .../ANY/__pycache__/SMIMEA.cpython-312.pyc | Bin 0 -> 637 bytes .../ANY/__pycache__/SOA.cpython-312.pyc | Bin 0 -> 3794 bytes .../ANY/__pycache__/SPF.cpython-312.pyc | Bin 0 -> 625 bytes .../ANY/__pycache__/SSHFP.cpython-312.pyc | Bin 0 -> 3127 bytes .../ANY/__pycache__/TKEY.cpython-312.pyc | Bin 0 -> 5071 bytes .../ANY/__pycache__/TLSA.cpython-312.pyc | Bin 0 -> 631 bytes .../ANY/__pycache__/TSIG.cpython-312.pyc | Bin 0 -> 5891 bytes .../ANY/__pycache__/TXT.cpython-312.pyc | Bin 0 -> 625 bytes .../ANY/__pycache__/URI.cpython-312.pyc | Bin 0 -> 4150 bytes .../ANY/__pycache__/WALLET.cpython-312.pyc | Bin 0 -> 634 bytes .../ANY/__pycache__/X25.cpython-312.pyc | Bin 0 -> 2339 bytes .../ANY/__pycache__/ZONEMD.cpython-312.pyc | Bin 0 -> 4180 bytes .../ANY/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 573 bytes .../site-packages/dns/rdtypes/CH/A.py | 60 + .../site-packages/dns/rdtypes/CH/__init__.py | 22 + .../rdtypes/CH/__pycache__/A.cpython-312.pyc | Bin 0 -> 2532 bytes .../CH/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 251 bytes .../site-packages/dns/rdtypes/IN/A.py | 51 + .../site-packages/dns/rdtypes/IN/AAAA.py | 51 + .../site-packages/dns/rdtypes/IN/APL.py | 150 + .../site-packages/dns/rdtypes/IN/DHCID.py | 54 + .../site-packages/dns/rdtypes/IN/HTTPS.py | 9 + .../site-packages/dns/rdtypes/IN/IPSECKEY.py | 87 + .../site-packages/dns/rdtypes/IN/KX.py | 24 + .../site-packages/dns/rdtypes/IN/NAPTR.py | 109 + .../site-packages/dns/rdtypes/IN/NSAP.py | 60 + .../site-packages/dns/rdtypes/IN/NSAP_PTR.py | 24 + .../site-packages/dns/rdtypes/IN/PX.py | 73 + .../site-packages/dns/rdtypes/IN/SRV.py | 75 + .../site-packages/dns/rdtypes/IN/SVCB.py | 9 + .../site-packages/dns/rdtypes/IN/WKS.py | 100 + .../site-packages/dns/rdtypes/IN/__init__.py | 35 + .../rdtypes/IN/__pycache__/A.cpython-312.pyc | Bin 0 -> 2099 bytes .../IN/__pycache__/AAAA.cpython-312.pyc | Bin 0 -> 2123 bytes .../IN/__pycache__/APL.cpython-312.pyc | Bin 0 -> 6909 bytes .../IN/__pycache__/DHCID.cpython-312.pyc | Bin 0 -> 2243 bytes .../IN/__pycache__/HTTPS.cpython-312.pyc | Bin 0 -> 633 bytes .../IN/__pycache__/IPSECKEY.cpython-312.pyc | Bin 0 -> 4231 bytes .../rdtypes/IN/__pycache__/KX.cpython-312.pyc | Bin 0 -> 636 bytes .../IN/__pycache__/NAPTR.cpython-312.pyc | Bin 0 -> 5045 bytes .../IN/__pycache__/NSAP.cpython-312.pyc | Bin 0 -> 2689 bytes .../IN/__pycache__/NSAP_PTR.cpython-312.pyc | Bin 0 -> 644 bytes .../rdtypes/IN/__pycache__/PX.cpython-312.pyc | Bin 0 -> 3439 bytes .../IN/__pycache__/SRV.cpython-312.pyc | Bin 0 -> 3692 bytes .../IN/__pycache__/SVCB.cpython-312.pyc | Bin 0 -> 630 bytes .../IN/__pycache__/WKS.cpython-312.pyc | Bin 0 -> 4593 bytes .../IN/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 332 bytes .../site-packages/dns/rdtypes/__init__.py | 33 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 331 bytes .../__pycache__/dnskeybase.cpython-312.pyc | Bin 0 -> 3869 bytes .../__pycache__/dsbase.cpython-312.pyc | Bin 0 -> 4180 bytes .../__pycache__/euibase.cpython-312.pyc | Bin 0 -> 3590 bytes .../__pycache__/mxbase.cpython-312.pyc | Bin 0 -> 4383 bytes .../__pycache__/nsbase.cpython-312.pyc | Bin 0 -> 2816 bytes .../__pycache__/svcbbase.cpython-312.pyc | Bin 0 -> 29869 bytes .../__pycache__/tlsabase.cpython-312.pyc | Bin 0 -> 3376 bytes .../__pycache__/txtbase.cpython-312.pyc | Bin 0 -> 5179 bytes .../rdtypes/__pycache__/util.cpython-312.pyc | Bin 0 -> 13864 bytes .../site-packages/dns/rdtypes/dnskeybase.py | 83 + .../site-packages/dns/rdtypes/dsbase.py | 83 + .../site-packages/dns/rdtypes/euibase.py | 73 + .../site-packages/dns/rdtypes/mxbase.py | 87 + .../site-packages/dns/rdtypes/nsbase.py | 63 + .../site-packages/dns/rdtypes/svcbbase.py | 587 ++ .../site-packages/dns/rdtypes/tlsabase.py | 69 + .../site-packages/dns/rdtypes/txtbase.py | 109 + .../site-packages/dns/rdtypes/util.py | 269 + .../python3.12/site-packages/dns/renderer.py | 355 + .../python3.12/site-packages/dns/resolver.py | 2068 ++++ .../site-packages/dns/reversename.py | 106 + .../lib/python3.12/site-packages/dns/rrset.py | 287 + .../python3.12/site-packages/dns/serial.py | 118 + venv/lib/python3.12/site-packages/dns/set.py | 308 + .../python3.12/site-packages/dns/tokenizer.py | 706 ++ .../site-packages/dns/transaction.py | 651 ++ venv/lib/python3.12/site-packages/dns/tsig.py | 359 + .../site-packages/dns/tsigkeyring.py | 68 + venv/lib/python3.12/site-packages/dns/ttl.py | 90 + .../python3.12/site-packages/dns/update.py | 389 + .../python3.12/site-packages/dns/version.py | 42 + .../python3.12/site-packages/dns/versioned.py | 320 + .../python3.12/site-packages/dns/win32util.py | 438 + venv/lib/python3.12/site-packages/dns/wire.py | 98 + venv/lib/python3.12/site-packages/dns/xfr.py | 356 + venv/lib/python3.12/site-packages/dns/zone.py | 1462 +++ .../python3.12/site-packages/dns/zonefile.py | 756 ++ .../python3.12/site-packages/dns/zonetypes.py | 37 + .../dnspython-2.8.0.dist-info/INSTALLER | 1 + .../dnspython-2.8.0.dist-info/METADATA | 149 + .../dnspython-2.8.0.dist-info/RECORD | 304 + .../dnspython-2.8.0.dist-info/WHEEL | 4 + .../licenses/LICENSE | 35 + .../site-packages/engineio/__init__.py | 13 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 675 bytes .../__pycache__/async_client.cpython-312.pyc | Bin 0 -> 40044 bytes .../__pycache__/async_server.cpython-312.pyc | Bin 0 -> 30807 bytes .../__pycache__/async_socket.cpython-312.pyc | Bin 0 -> 16167 bytes .../__pycache__/base_client.cpython-312.pyc | Bin 0 -> 7400 bytes .../__pycache__/base_server.cpython-312.pyc | Bin 0 -> 16908 bytes .../__pycache__/base_socket.cpython-312.pyc | Bin 0 -> 890 bytes .../__pycache__/client.cpython-312.pyc | Bin 0 -> 33996 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 1098 bytes .../engineio/__pycache__/json.cpython-312.pyc | Bin 0 -> 748 bytes .../__pycache__/middleware.cpython-312.pyc | Bin 0 -> 4663 bytes .../__pycache__/packet.cpython-312.pyc | Bin 0 -> 3873 bytes .../__pycache__/payload.cpython-312.pyc | Bin 0 -> 2114 bytes .../__pycache__/server.cpython-312.pyc | Bin 0 -> 24473 bytes .../__pycache__/socket.cpython-312.pyc | Bin 0 -> 14557 bytes .../__pycache__/static_files.cpython-312.pyc | Bin 0 -> 2166 bytes .../site-packages/engineio/async_client.py | 689 ++ .../engineio/async_drivers/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 184 bytes .../_websocket_wsgi.cpython-312.pyc | Bin 0 -> 2152 bytes .../__pycache__/aiohttp.cpython-312.pyc | Bin 0 -> 5760 bytes .../__pycache__/asgi.cpython-312.pyc | Bin 0 -> 14490 bytes .../__pycache__/eventlet.cpython-312.pyc | Bin 0 -> 2907 bytes .../__pycache__/gevent.cpython-312.pyc | Bin 0 -> 4205 bytes .../__pycache__/gevent_uwsgi.cpython-312.pyc | Bin 0 -> 7459 bytes .../__pycache__/sanic.cpython-312.pyc | Bin 0 -> 6655 bytes .../__pycache__/threading.cpython-312.pyc | Bin 0 -> 1034 bytes .../__pycache__/tornado.cpython-312.pyc | Bin 0 -> 9920 bytes .../engineio/async_drivers/_websocket_wsgi.py | 34 + .../engineio/async_drivers/aiohttp.py | 127 + .../engineio/async_drivers/asgi.py | 296 + .../engineio/async_drivers/eventlet.py | 52 + .../engineio/async_drivers/gevent.py | 83 + .../engineio/async_drivers/gevent_uwsgi.py | 168 + .../engineio/async_drivers/sanic.py | 148 + .../engineio/async_drivers/threading.py | 19 + .../engineio/async_drivers/tornado.py | 182 + .../site-packages/engineio/async_server.py | 611 ++ .../site-packages/engineio/async_socket.py | 261 + .../site-packages/engineio/base_client.py | 169 + .../site-packages/engineio/base_server.py | 358 + .../site-packages/engineio/base_socket.py | 14 + .../site-packages/engineio/client.py | 632 ++ .../site-packages/engineio/exceptions.py | 22 + .../python3.12/site-packages/engineio/json.py | 16 + .../site-packages/engineio/middleware.py | 86 + .../site-packages/engineio/packet.py | 82 + .../site-packages/engineio/payload.py | 46 + .../site-packages/engineio/server.py | 503 + .../site-packages/engineio/socket.py | 256 + .../site-packages/engineio/static_files.py | 60 + .../eventlet-0.33.3.dist-info/AUTHORS | 174 + .../eventlet-0.33.3.dist-info/INSTALLER | 1 + .../eventlet-0.33.3.dist-info/LICENSE | 23 + .../eventlet-0.33.3.dist-info/METADATA | 112 + .../eventlet-0.33.3.dist-info/RECORD | 190 + .../eventlet-0.33.3.dist-info/REQUESTED | 0 .../eventlet-0.33.3.dist-info/WHEEL | 6 + .../eventlet-0.33.3.dist-info/top_level.txt | 1 + .../site-packages/eventlet/__init__.py | 78 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2822 bytes .../__pycache__/backdoor.cpython-312.pyc | Bin 0 -> 7298 bytes .../__pycache__/convenience.cpython-312.pyc | Bin 0 -> 9156 bytes .../__pycache__/corolocal.cpython-312.pyc | Bin 0 -> 2873 bytes .../__pycache__/coros.cpython-312.pyc | Bin 0 -> 2616 bytes .../__pycache__/dagpool.cpython-312.pyc | Bin 0 -> 25542 bytes .../__pycache__/db_pool.cpython-312.pyc | Bin 0 -> 19063 bytes .../__pycache__/debug.cpython-312.pyc | Bin 0 -> 8530 bytes .../__pycache__/event.cpython-312.pyc | Bin 0 -> 9422 bytes .../__pycache__/greenpool.cpython-312.pyc | Bin 0 -> 13131 bytes .../__pycache__/greenthread.cpython-312.pyc | Bin 0 -> 15042 bytes .../eventlet/__pycache__/lock.cpython-312.pyc | Bin 0 -> 1613 bytes .../__pycache__/patcher.cpython-312.pyc | Bin 0 -> 20818 bytes .../__pycache__/pools.cpython-312.pyc | Bin 0 -> 8480 bytes .../__pycache__/queue.cpython-312.pyc | Bin 0 -> 23262 bytes .../__pycache__/semaphore.cpython-312.pyc | Bin 0 -> 15949 bytes .../__pycache__/timeout.cpython-312.pyc | Bin 0 -> 7452 bytes .../__pycache__/tpool.cpython-312.pyc | Bin 0 -> 15170 bytes .../__pycache__/websocket.cpython-312.pyc | Bin 0 -> 40147 bytes .../eventlet/__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 48909 bytes .../site-packages/eventlet/backdoor.py | 144 + .../site-packages/eventlet/convenience.py | 189 + .../site-packages/eventlet/corolocal.py | 53 + .../site-packages/eventlet/coros.py | 61 + .../site-packages/eventlet/dagpool.py | 602 ++ .../site-packages/eventlet/db_pool.py | 461 + .../site-packages/eventlet/debug.py | 174 + .../site-packages/eventlet/event.py | 220 + .../eventlet/green/BaseHTTPServer.py | 16 + .../eventlet/green/CGIHTTPServer.py | 19 + .../site-packages/eventlet/green/MySQLdb.py | 37 + .../eventlet/green/OpenSSL/SSL.py | 124 + .../eventlet/green/OpenSSL/__init__.py | 9 + .../OpenSSL/__pycache__/SSL.cpython-312.pyc | Bin 0 -> 6153 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 396 bytes .../__pycache__/crypto.cpython-312.pyc | Bin 0 -> 218 bytes .../OpenSSL/__pycache__/tsafe.cpython-312.pyc | Bin 0 -> 216 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 258 bytes .../eventlet/green/OpenSSL/crypto.py | 1 + .../eventlet/green/OpenSSL/tsafe.py | 1 + .../eventlet/green/OpenSSL/version.py | 1 + .../site-packages/eventlet/green/Queue.py | 32 + .../eventlet/green/SimpleHTTPServer.py | 14 + .../eventlet/green/SocketServer.py | 15 + .../site-packages/eventlet/green/__init__.py | 1 + .../BaseHTTPServer.cpython-312.pyc | Bin 0 -> 624 bytes .../__pycache__/CGIHTTPServer.cpython-312.pyc | Bin 0 -> 643 bytes .../green/__pycache__/MySQLdb.cpython-312.pyc | Bin 0 -> 1622 bytes .../green/__pycache__/Queue.cpython-312.pyc | Bin 0 -> 1664 bytes .../SimpleHTTPServer.cpython-312.pyc | Bin 0 -> 531 bytes .../__pycache__/SocketServer.cpython-312.pyc | Bin 0 -> 561 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 176 bytes .../__pycache__/_socket_nodns.cpython-312.pyc | Bin 0 -> 1461 bytes .../__pycache__/asynchat.cpython-312.pyc | Bin 0 -> 439 bytes .../__pycache__/asyncore.cpython-312.pyc | Bin 0 -> 483 bytes .../green/__pycache__/builtin.cpython-312.pyc | Bin 0 -> 1901 bytes .../green/__pycache__/ftplib.cpython-312.pyc | Bin 0 -> 451 bytes .../green/__pycache__/httplib.cpython-312.pyc | Bin 0 -> 959 bytes .../green/__pycache__/os.cpython-312.pyc | Bin 0 -> 4462 bytes .../green/__pycache__/profile.cpython-312.pyc | Bin 0 -> 10672 bytes .../green/__pycache__/select.cpython-312.pyc | Bin 0 -> 4450 bytes .../__pycache__/selectors.cpython-312.pyc | Bin 0 -> 726 bytes .../green/__pycache__/socket.cpython-312.pyc | Bin 0 -> 2451 bytes .../green/__pycache__/ssl.cpython-312.pyc | Bin 0 -> 21049 bytes .../__pycache__/subprocess.cpython-312.pyc | Bin 0 -> 5657 bytes .../green/__pycache__/thread.cpython-312.pyc | Bin 0 -> 3843 bytes .../__pycache__/threading.cpython-312.pyc | Bin 0 -> 5004 bytes .../green/__pycache__/time.cpython-312.pyc | Bin 0 -> 472 bytes .../green/__pycache__/urllib2.cpython-312.pyc | Bin 0 -> 755 bytes .../green/__pycache__/zmq.cpython-312.pyc | Bin 0 -> 19794 bytes .../eventlet/green/_socket_nodns.py | 33 + .../site-packages/eventlet/green/asynchat.py | 11 + .../site-packages/eventlet/green/asyncore.py | 13 + .../site-packages/eventlet/green/builtin.py | 51 + .../site-packages/eventlet/green/ftplib.py | 13 + .../eventlet/green/http/__init__.py | 191 + .../http/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 6247 bytes .../http/__pycache__/client.cpython-312.pyc | Bin 0 -> 54557 bytes .../__pycache__/cookiejar.cpython-312.pyc | Bin 0 -> 80452 bytes .../http/__pycache__/cookies.cpython-312.pyc | Bin 0 -> 22730 bytes .../http/__pycache__/server.cpython-312.pyc | Bin 0 -> 50065 bytes .../eventlet/green/http/client.py | 1567 +++ .../eventlet/green/http/cookiejar.py | 2152 +++++ .../eventlet/green/http/cookies.py | 691 ++ .../eventlet/green/http/server.py | 1266 +++ .../site-packages/eventlet/green/httplib.py | 22 + .../site-packages/eventlet/green/os.py | 120 + .../site-packages/eventlet/green/profile.py | 257 + .../site-packages/eventlet/green/select.py | 86 + .../site-packages/eventlet/green/selectors.py | 34 + .../site-packages/eventlet/green/socket.py | 63 + .../site-packages/eventlet/green/ssl.py | 493 + .../eventlet/green/subprocess.py | 143 + .../site-packages/eventlet/green/thread.py | 115 + .../site-packages/eventlet/green/threading.py | 134 + .../site-packages/eventlet/green/time.py | 6 + .../eventlet/green/urllib/__init__.py | 40 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1699 bytes .../urllib/__pycache__/error.cpython-312.pyc | Bin 0 -> 411 bytes .../urllib/__pycache__/parse.cpython-312.pyc | Bin 0 -> 325 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 1624 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 332 bytes .../eventlet/green/urllib/error.py | 4 + .../eventlet/green/urllib/parse.py | 3 + .../eventlet/green/urllib/request.py | 50 + .../eventlet/green/urllib/response.py | 3 + .../site-packages/eventlet/green/urllib2.py | 20 + .../site-packages/eventlet/green/zmq.py | 465 + .../eventlet/greenio/__init__.py | 8 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 345 bytes .../greenio/__pycache__/base.cpython-312.pyc | Bin 0 -> 22430 bytes .../greenio/__pycache__/py2.cpython-312.pyc | Bin 0 -> 11429 bytes .../greenio/__pycache__/py3.cpython-312.pyc | Bin 0 -> 9814 bytes .../site-packages/eventlet/greenio/base.py | 510 + .../site-packages/eventlet/greenio/py2.py | 230 + .../site-packages/eventlet/greenio/py3.py | 217 + .../site-packages/eventlet/greenpool.py | 257 + .../site-packages/eventlet/greenthread.py | 302 + .../site-packages/eventlet/hubs/__init__.py | 190 + .../hubs/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 8340 bytes .../hubs/__pycache__/epolls.cpython-312.pyc | Bin 0 -> 2171 bytes .../hubs/__pycache__/hub.cpython-312.pyc | Bin 0 -> 24243 bytes .../hubs/__pycache__/kqueue.cpython-312.pyc | Bin 0 -> 6643 bytes .../hubs/__pycache__/poll.cpython-312.pyc | Bin 0 -> 6199 bytes .../hubs/__pycache__/pyevent.cpython-312.pyc | Bin 0 -> 9876 bytes .../hubs/__pycache__/selects.cpython-312.pyc | Bin 0 -> 3778 bytes .../hubs/__pycache__/timer.cpython-312.pyc | Bin 0 -> 5258 bytes .../site-packages/eventlet/hubs/epolls.py | 31 + .../site-packages/eventlet/hubs/hub.py | 501 + .../site-packages/eventlet/hubs/kqueue.py | 112 + .../site-packages/eventlet/hubs/poll.py | 119 + .../site-packages/eventlet/hubs/pyevent.py | 193 + .../site-packages/eventlet/hubs/selects.py | 64 + .../site-packages/eventlet/hubs/timer.py | 106 + .../python3.12/site-packages/eventlet/lock.py | 37 + .../site-packages/eventlet/patcher.py | 530 + .../site-packages/eventlet/pools.py | 186 + .../site-packages/eventlet/queue.py | 492 + .../site-packages/eventlet/semaphore.py | 315 + .../eventlet/support/__init__.py | 78 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3561 bytes .../__pycache__/greendns.cpython-312.pyc | Bin 0 -> 41370 bytes .../__pycache__/greenlets.cpython-312.pyc | Bin 0 -> 673 bytes .../psycopg2_patcher.cpython-312.pyc | Bin 0 -> 1824 bytes .../support/__pycache__/pylib.cpython-312.pyc | Bin 0 -> 673 bytes .../stacklesspypys.cpython-312.pyc | Bin 0 -> 683 bytes .../__pycache__/stacklesss.cpython-312.pyc | Bin 0 -> 3512 bytes .../eventlet/support/greendns.py | 929 ++ .../eventlet/support/greenlets.py | 8 + .../eventlet/support/psycopg2_patcher.py | 55 + .../site-packages/eventlet/support/pylib.py | 12 + .../eventlet/support/stacklesspypys.py | 12 + .../eventlet/support/stacklesss.py | 84 + .../site-packages/eventlet/timeout.py | 184 + .../site-packages/eventlet/tpool.py | 343 + .../site-packages/eventlet/websocket.py | 864 ++ .../python3.12/site-packages/eventlet/wsgi.py | 1031 ++ .../site-packages/eventlet/zipkin/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 177 bytes .../zipkin/__pycache__/api.cpython-312.pyc | Bin 0 -> 8317 bytes .../zipkin/__pycache__/client.cpython-312.pyc | Bin 0 -> 3568 bytes .../__pycache__/greenthread.cpython-312.pyc | Bin 0 -> 1623 bytes .../zipkin/__pycache__/http.cpython-312.pyc | Bin 0 -> 3174 bytes .../zipkin/__pycache__/log.cpython-312.pyc | Bin 0 -> 977 bytes .../__pycache__/patcher.cpython-312.pyc | Bin 0 -> 2005 bytes .../zipkin/__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 3785 bytes .../eventlet/zipkin/_thrift/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 185 bytes .../zipkin/_thrift/zipkinCore/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 239 bytes .../__pycache__/constants.cpython-312.pyc | Bin 0 -> 422 bytes .../__pycache__/ttypes.cpython-312.pyc | Bin 0 -> 22647 bytes .../zipkin/_thrift/zipkinCore/constants.py | 14 + .../zipkin/_thrift/zipkinCore/ttypes.py | 452 + .../site-packages/eventlet/zipkin/api.py | 186 + .../site-packages/eventlet/zipkin/client.py | 56 + .../eventlet/zipkin/greenthread.py | 33 + .../site-packages/eventlet/zipkin/http.py | 61 + .../site-packages/eventlet/zipkin/log.py | 19 + .../site-packages/eventlet/zipkin/patcher.py | 41 + .../site-packages/eventlet/zipkin/wsgi.py | 78 + .../flask-2.3.3.dist-info/INSTALLER | 1 + .../flask-2.3.3.dist-info/LICENSE.rst | 28 + .../flask-2.3.3.dist-info/METADATA | 116 + .../flask-2.3.3.dist-info/RECORD | 53 + .../flask-2.3.3.dist-info/REQUESTED | 0 .../site-packages/flask-2.3.3.dist-info/WHEEL | 4 + .../flask-2.3.3.dist-info/entry_points.txt | 3 + .../site-packages/flask/__init__.py | 102 + .../site-packages/flask/__main__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3223 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 221 bytes .../flask/__pycache__/app.cpython-312.pyc | Bin 0 -> 83155 bytes .../__pycache__/blueprints.cpython-312.pyc | Bin 0 -> 30741 bytes .../flask/__pycache__/cli.cpython-312.pyc | Bin 0 -> 39867 bytes .../flask/__pycache__/config.cpython-312.pyc | Bin 0 -> 15412 bytes .../flask/__pycache__/ctx.cpython-312.pyc | Bin 0 -> 19537 bytes .../__pycache__/debughelpers.cpython-312.pyc | Bin 0 -> 8537 bytes .../flask/__pycache__/globals.cpython-312.pyc | Bin 0 -> 3613 bytes .../flask/__pycache__/helpers.cpython-312.pyc | Bin 0 -> 28188 bytes .../flask/__pycache__/logging.cpython-312.pyc | Bin 0 -> 3242 bytes .../__pycache__/scaffold.cpython-312.pyc | Bin 0 -> 33308 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 15891 bytes .../flask/__pycache__/signals.cpython-312.pyc | Bin 0 -> 1713 bytes .../__pycache__/templating.cpython-312.pyc | Bin 0 -> 9895 bytes .../flask/__pycache__/testing.cpython-312.pyc | Bin 0 -> 13521 bytes .../flask/__pycache__/typing.cpython-312.pyc | Bin 0 -> 3760 bytes .../flask/__pycache__/views.cpython-312.pyc | Bin 0 -> 6824 bytes .../__pycache__/wrappers.cpython-312.pyc | Bin 0 -> 6062 bytes .../lib/python3.12/site-packages/flask/app.py | 2213 +++++ .../site-packages/flask/blueprints.py | 626 ++ .../lib/python3.12/site-packages/flask/cli.py | 1068 ++ .../python3.12/site-packages/flask/config.py | 347 + .../lib/python3.12/site-packages/flask/ctx.py | 440 + .../site-packages/flask/debughelpers.py | 160 + .../python3.12/site-packages/flask/globals.py | 96 + .../python3.12/site-packages/flask/helpers.py | 701 ++ .../site-packages/flask/json/__init__.py | 170 + .../json/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 6673 bytes .../json/__pycache__/provider.cpython-312.pyc | Bin 0 -> 9224 bytes .../json/__pycache__/tag.cpython-312.pyc | Bin 0 -> 13322 bytes .../site-packages/flask/json/provider.py | 216 + .../site-packages/flask/json/tag.py | 314 + .../python3.12/site-packages/flask/logging.py | 76 + .../python3.12/site-packages/flask/py.typed | 0 .../site-packages/flask/scaffold.py | 873 ++ .../site-packages/flask/sessions.py | 367 + .../python3.12/site-packages/flask/signals.py | 33 + .../site-packages/flask/templating.py | 220 + .../python3.12/site-packages/flask/testing.py | 295 + .../python3.12/site-packages/flask/typing.py | 82 + .../python3.12/site-packages/flask/views.py | 190 + .../site-packages/flask/wrappers.py | 173 + .../site-packages/flask_socketio/__init__.py | 1117 +++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 57908 bytes .../__pycache__/namespace.cpython-312.pyc | Bin 0 -> 2716 bytes .../__pycache__/test_client.cpython-312.pyc | Bin 0 -> 13537 bytes .../site-packages/flask_socketio/namespace.py | 46 + .../flask_socketio/test_client.py | 236 + .../greenlet-3.2.4.dist-info/INSTALLER | 1 + .../greenlet-3.2.4.dist-info/METADATA | 117 + .../greenlet-3.2.4.dist-info/RECORD | 121 + .../greenlet-3.2.4.dist-info/WHEEL | 6 + .../greenlet-3.2.4.dist-info/licenses/LICENSE | 30 + .../licenses/LICENSE.PSF | 47 + .../greenlet-3.2.4.dist-info/top_level.txt | 1 + .../site-packages/greenlet/CObjects.cpp | 157 + .../site-packages/greenlet/PyGreenlet.cpp | 751 ++ .../site-packages/greenlet/PyGreenlet.hpp | 35 + .../greenlet/PyGreenletUnswitchable.cpp | 147 + .../site-packages/greenlet/PyModule.cpp | 292 + .../greenlet/TBrokenGreenlet.cpp | 45 + .../greenlet/TExceptionState.cpp | 62 + .../site-packages/greenlet/TGreenlet.cpp | 719 ++ .../site-packages/greenlet/TGreenlet.hpp | 830 ++ .../greenlet/TGreenletGlobals.cpp | 94 + .../site-packages/greenlet/TMainGreenlet.cpp | 153 + .../site-packages/greenlet/TPythonState.cpp | 406 + .../site-packages/greenlet/TStackState.cpp | 265 + .../site-packages/greenlet/TThreadState.hpp | 497 + .../greenlet/TThreadStateCreator.hpp | 102 + .../greenlet/TThreadStateDestroy.cpp | 217 + .../site-packages/greenlet/TUserGreenlet.cpp | 662 ++ .../site-packages/greenlet/__init__.py | 71 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1056 bytes .../_greenlet.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 1446176 bytes .../site-packages/greenlet/greenlet.cpp | 320 + .../site-packages/greenlet/greenlet.h | 164 + .../greenlet/greenlet_allocator.hpp | 89 + .../greenlet/greenlet_compiler_compat.hpp | 98 + .../greenlet/greenlet_cpython_compat.hpp | 150 + .../greenlet/greenlet_exceptions.hpp | 171 + .../greenlet/greenlet_internal.hpp | 107 + .../greenlet/greenlet_msvc_compat.hpp | 91 + .../site-packages/greenlet/greenlet_refs.hpp | 1118 +++ .../greenlet/greenlet_slp_switch.hpp | 99 + .../greenlet/greenlet_thread_support.hpp | 31 + .../greenlet/platform/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 179 bytes .../platform/setup_switch_x64_masm.cmd | 2 + .../greenlet/platform/switch_aarch64_gcc.h | 124 + .../greenlet/platform/switch_alpha_unix.h | 30 + .../greenlet/platform/switch_amd64_unix.h | 87 + .../greenlet/platform/switch_arm32_gcc.h | 79 + .../greenlet/platform/switch_arm32_ios.h | 67 + .../greenlet/platform/switch_arm64_masm.asm | 53 + .../greenlet/platform/switch_arm64_masm.obj | Bin 0 -> 746 bytes .../greenlet/platform/switch_arm64_msvc.h | 17 + .../greenlet/platform/switch_csky_gcc.h | 48 + .../platform/switch_loongarch64_linux.h | 31 + .../greenlet/platform/switch_m68k_gcc.h | 38 + .../greenlet/platform/switch_mips_unix.h | 64 + .../greenlet/platform/switch_ppc64_aix.h | 103 + .../greenlet/platform/switch_ppc64_linux.h | 105 + .../greenlet/platform/switch_ppc_aix.h | 87 + .../greenlet/platform/switch_ppc_linux.h | 84 + .../greenlet/platform/switch_ppc_macosx.h | 82 + .../greenlet/platform/switch_ppc_unix.h | 82 + .../greenlet/platform/switch_riscv_unix.h | 41 + .../greenlet/platform/switch_s390_unix.h | 87 + .../greenlet/platform/switch_sh_gcc.h | 36 + .../greenlet/platform/switch_sparc_sun_gcc.h | 92 + .../greenlet/platform/switch_x32_unix.h | 63 + .../greenlet/platform/switch_x64_masm.asm | 111 + .../greenlet/platform/switch_x64_masm.obj | Bin 0 -> 1078 bytes .../greenlet/platform/switch_x64_msvc.h | 60 + .../greenlet/platform/switch_x86_msvc.h | 326 + .../greenlet/platform/switch_x86_unix.h | 105 + .../greenlet/slp_platformselect.h | 77 + .../site-packages/greenlet/tests/__init__.py | 248 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 9232 bytes ...fail_clearing_run_switches.cpython-312.pyc | Bin 0 -> 2064 bytes .../fail_cpp_exception.cpython-312.pyc | Bin 0 -> 1599 bytes ...nitialstub_already_started.cpython-312.pyc | Bin 0 -> 3467 bytes .../fail_slp_switch.cpython-312.pyc | Bin 0 -> 1292 bytes ...ail_switch_three_greenlets.cpython-312.pyc | Bin 0 -> 1708 bytes ...il_switch_three_greenlets2.cpython-312.pyc | Bin 0 -> 2562 bytes .../fail_switch_two_greenlets.cpython-312.pyc | Bin 0 -> 1679 bytes .../__pycache__/leakcheck.cpython-312.pyc | Bin 0 -> 11686 bytes .../test_contextvars.cpython-312.pyc | Bin 0 -> 15463 bytes .../__pycache__/test_cpp.cpython-312.pyc | Bin 0 -> 4093 bytes .../test_extension_interface.cpython-312.pyc | Bin 0 -> 7451 bytes .../tests/__pycache__/test_gc.cpython-312.pyc | Bin 0 -> 4914 bytes .../test_generator.cpython-312.pyc | Bin 0 -> 3067 bytes .../test_generator_nested.cpython-312.pyc | Bin 0 -> 7805 bytes .../__pycache__/test_greenlet.cpython-312.pyc | Bin 0 -> 75519 bytes .../test_greenlet_trash.cpython-312.pyc | Bin 0 -> 6764 bytes .../__pycache__/test_leaks.cpython-312.pyc | Bin 0 -> 19958 bytes .../test_stack_saved.cpython-312.pyc | Bin 0 -> 1331 bytes .../__pycache__/test_throw.cpython-312.pyc | Bin 0 -> 7367 bytes .../__pycache__/test_tracing.cpython-312.pyc | Bin 0 -> 13863 bytes .../__pycache__/test_version.cpython-312.pyc | Bin 0 -> 2550 bytes .../__pycache__/test_weakref.cpython-312.pyc | Bin 0 -> 2716 bytes .../greenlet/tests/_test_extension.c | 231 + ..._extension.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 17256 bytes .../greenlet/tests/_test_extension_cpp.cpp | 226 + ...ension_cpp.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 58384 bytes .../tests/fail_clearing_run_switches.py | 47 + .../greenlet/tests/fail_cpp_exception.py | 33 + .../tests/fail_initialstub_already_started.py | 78 + .../greenlet/tests/fail_slp_switch.py | 29 + .../tests/fail_switch_three_greenlets.py | 44 + .../tests/fail_switch_three_greenlets2.py | 55 + .../tests/fail_switch_two_greenlets.py | 41 + .../site-packages/greenlet/tests/leakcheck.py | 336 + .../greenlet/tests/test_contextvars.py | 312 + .../site-packages/greenlet/tests/test_cpp.py | 73 + .../tests/test_extension_interface.py | 115 + .../site-packages/greenlet/tests/test_gc.py | 86 + .../greenlet/tests/test_generator.py | 59 + .../greenlet/tests/test_generator_nested.py | 168 + .../greenlet/tests/test_greenlet.py | 1353 +++ .../greenlet/tests/test_greenlet_trash.py | 187 + .../greenlet/tests/test_leaks.py | 457 + .../greenlet/tests/test_stack_saved.py | 19 + .../greenlet/tests/test_throw.py | 128 + .../greenlet/tests/test_tracing.py | 299 + .../greenlet/tests/test_version.py | 41 + .../greenlet/tests/test_weakref.py | 35 + .../h11-0.16.0.dist-info/INSTALLER | 1 + .../h11-0.16.0.dist-info/METADATA | 202 + .../site-packages/h11-0.16.0.dist-info/RECORD | 29 + .../site-packages/h11-0.16.0.dist-info/WHEEL | 5 + .../h11-0.16.0.dist-info/licenses/LICENSE.txt | 22 + .../h11-0.16.0.dist-info/top_level.txt | 1 + .../python3.12/site-packages/h11/__init__.py | 62 + .../h11/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1056 bytes .../h11/__pycache__/_abnf.cpython-312.pyc | Bin 0 -> 1762 bytes .../__pycache__/_connection.cpython-312.pyc | Bin 0 -> 23112 bytes .../h11/__pycache__/_events.cpython-312.pyc | Bin 0 -> 13207 bytes .../h11/__pycache__/_headers.cpython-312.pyc | Bin 0 -> 7983 bytes .../h11/__pycache__/_readers.cpython-312.pyc | Bin 0 -> 9639 bytes .../_receivebuffer.cpython-312.pyc | Bin 0 -> 4685 bytes .../h11/__pycache__/_state.cpython-312.pyc | Bin 0 -> 8449 bytes .../h11/__pycache__/_util.cpython-312.pyc | Bin 0 -> 4700 bytes .../h11/__pycache__/_version.cpython-312.pyc | Bin 0 -> 194 bytes .../h11/__pycache__/_writers.cpython-312.pyc | Bin 0 -> 6276 bytes .../lib/python3.12/site-packages/h11/_abnf.py | 132 + .../site-packages/h11/_connection.py | 659 ++ .../python3.12/site-packages/h11/_events.py | 369 + .../python3.12/site-packages/h11/_headers.py | 282 + .../python3.12/site-packages/h11/_readers.py | 250 + .../site-packages/h11/_receivebuffer.py | 153 + .../python3.12/site-packages/h11/_state.py | 365 + .../lib/python3.12/site-packages/h11/_util.py | 135 + .../python3.12/site-packages/h11/_version.py | 16 + .../python3.12/site-packages/h11/_writers.py | 145 + .../lib/python3.12/site-packages/h11/py.typed | 1 + .../itsdangerous-2.2.0.dist-info/INSTALLER | 1 + .../itsdangerous-2.2.0.dist-info/LICENSE.txt | 28 + .../itsdangerous-2.2.0.dist-info/METADATA | 60 + .../itsdangerous-2.2.0.dist-info/RECORD | 22 + .../itsdangerous-2.2.0.dist-info/WHEEL | 4 + .../site-packages/itsdangerous/__init__.py | 38 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1612 bytes .../__pycache__/_json.cpython-312.pyc | Bin 0 -> 1166 bytes .../__pycache__/encoding.cpython-312.pyc | Bin 0 -> 2666 bytes .../__pycache__/exc.cpython-312.pyc | Bin 0 -> 3926 bytes .../__pycache__/serializer.cpython-312.pyc | Bin 0 -> 15407 bytes .../__pycache__/signer.cpython-312.pyc | Bin 0 -> 11272 bytes .../__pycache__/timed.cpython-312.pyc | Bin 0 -> 8720 bytes .../__pycache__/url_safe.cpython-312.pyc | Bin 0 -> 3516 bytes .../site-packages/itsdangerous/_json.py | 18 + .../site-packages/itsdangerous/encoding.py | 54 + .../site-packages/itsdangerous/exc.py | 106 + .../site-packages/itsdangerous/py.typed | 0 .../site-packages/itsdangerous/serializer.py | 406 + .../site-packages/itsdangerous/signer.py | 266 + .../site-packages/itsdangerous/timed.py | 228 + .../site-packages/itsdangerous/url_safe.py | 83 + .../jinja2-3.1.6.dist-info/INSTALLER | 1 + .../jinja2-3.1.6.dist-info/METADATA | 84 + .../jinja2-3.1.6.dist-info/RECORD | 57 + .../jinja2-3.1.6.dist-info/WHEEL | 4 + .../jinja2-3.1.6.dist-info/entry_points.txt | 3 + .../licenses/LICENSE.txt | 28 + .../site-packages/jinja2/__init__.py | 38 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1637 bytes .../__pycache__/_identifier.cpython-312.pyc | Bin 0 -> 2118 bytes .../__pycache__/async_utils.cpython-312.pyc | Bin 0 -> 4958 bytes .../__pycache__/bccache.cpython-312.pyc | Bin 0 -> 19329 bytes .../__pycache__/compiler.cpython-312.pyc | Bin 0 -> 104044 bytes .../__pycache__/constants.cpython-312.pyc | Bin 0 -> 1540 bytes .../jinja2/__pycache__/debug.cpython-312.pyc | Bin 0 -> 6565 bytes .../__pycache__/defaults.cpython-312.pyc | Bin 0 -> 1590 bytes .../__pycache__/environment.cpython-312.pyc | Bin 0 -> 76666 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7704 bytes .../jinja2/__pycache__/ext.cpython-312.pyc | Bin 0 -> 41897 bytes .../__pycache__/filters.cpython-312.pyc | Bin 0 -> 72314 bytes .../__pycache__/idtracking.cpython-312.pyc | Bin 0 -> 19179 bytes .../jinja2/__pycache__/lexer.cpython-312.pyc | Bin 0 -> 32060 bytes .../__pycache__/loaders.cpython-312.pyc | Bin 0 -> 32339 bytes .../jinja2/__pycache__/meta.cpython-312.pyc | Bin 0 -> 5474 bytes .../__pycache__/nativetypes.cpython-312.pyc | Bin 0 -> 6995 bytes .../jinja2/__pycache__/nodes.cpython-312.pyc | Bin 0 -> 58257 bytes .../__pycache__/optimizer.cpython-312.pyc | Bin 0 -> 2673 bytes .../jinja2/__pycache__/parser.cpython-312.pyc | Bin 0 -> 61187 bytes .../__pycache__/runtime.cpython-312.pyc | Bin 0 -> 48875 bytes .../__pycache__/sandbox.cpython-312.pyc | Bin 0 -> 18091 bytes .../jinja2/__pycache__/tests.cpython-312.pyc | Bin 0 -> 9034 bytes .../jinja2/__pycache__/utils.cpython-312.pyc | Bin 0 -> 34847 bytes .../__pycache__/visitor.cpython-312.pyc | Bin 0 -> 5349 bytes .../site-packages/jinja2/_identifier.py | 6 + .../site-packages/jinja2/async_utils.py | 99 + .../site-packages/jinja2/bccache.py | 408 + .../site-packages/jinja2/compiler.py | 1998 ++++ .../site-packages/jinja2/constants.py | 20 + .../python3.12/site-packages/jinja2/debug.py | 191 + .../site-packages/jinja2/defaults.py | 48 + .../site-packages/jinja2/environment.py | 1672 ++++ .../site-packages/jinja2/exceptions.py | 166 + .../python3.12/site-packages/jinja2/ext.py | 870 ++ .../site-packages/jinja2/filters.py | 1873 ++++ .../site-packages/jinja2/idtracking.py | 318 + .../python3.12/site-packages/jinja2/lexer.py | 868 ++ .../site-packages/jinja2/loaders.py | 693 ++ .../python3.12/site-packages/jinja2/meta.py | 112 + .../site-packages/jinja2/nativetypes.py | 130 + .../python3.12/site-packages/jinja2/nodes.py | 1206 +++ .../site-packages/jinja2/optimizer.py | 48 + .../python3.12/site-packages/jinja2/parser.py | 1049 ++ .../python3.12/site-packages/jinja2/py.typed | 0 .../site-packages/jinja2/runtime.py | 1062 ++ .../site-packages/jinja2/sandbox.py | 436 + .../python3.12/site-packages/jinja2/tests.py | 256 + .../python3.12/site-packages/jinja2/utils.py | 766 ++ .../site-packages/jinja2/visitor.py | 92 + .../markupsafe-3.0.3.dist-info/INSTALLER | 1 + .../markupsafe-3.0.3.dist-info/METADATA | 74 + .../markupsafe-3.0.3.dist-info/RECORD | 14 + .../markupsafe-3.0.3.dist-info/WHEEL | 7 + .../licenses/LICENSE.txt | 28 + .../markupsafe-3.0.3.dist-info/top_level.txt | 1 + .../site-packages/markupsafe/__init__.py | 396 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 20947 bytes .../__pycache__/_native.cpython-312.pyc | Bin 0 -> 599 bytes .../site-packages/markupsafe/_native.py | 8 + .../site-packages/markupsafe/_speedups.c | 200 + .../_speedups.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 44072 bytes .../site-packages/markupsafe/_speedups.pyi | 1 + .../site-packages/markupsafe/py.typed | 0 .../pip-24.0.dist-info/AUTHORS.txt | 760 ++ .../pip-24.0.dist-info/INSTALLER | 1 + .../pip-24.0.dist-info/LICENSE.txt | 20 + .../site-packages/pip-24.0.dist-info/METADATA | 88 + .../site-packages/pip-24.0.dist-info/RECORD | 1005 ++ .../pip-24.0.dist-info/REQUESTED | 0 .../site-packages/pip-24.0.dist-info/WHEEL | 5 + .../pip-24.0.dist-info/entry_points.txt | 4 + .../pip-24.0.dist-info/top_level.txt | 1 + .../python3.12/site-packages/pip/__init__.py | 13 + .../python3.12/site-packages/pip/__main__.py | 24 + .../site-packages/pip/__pip-runner__.py | 50 + .../pip/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 674 bytes .../pip/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 830 bytes .../__pip-runner__.cpython-312.pyc | Bin 0 -> 2193 bytes .../site-packages/pip/_internal/__init__.py | 18 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 776 bytes .../__pycache__/build_env.cpython-312.pyc | Bin 0 -> 14283 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 12654 bytes .../__pycache__/configuration.cpython-312.pyc | Bin 0 -> 17655 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 33273 bytes .../__pycache__/main.cpython-312.pyc | Bin 0 -> 659 bytes .../__pycache__/pyproject.cpython-312.pyc | Bin 0 -> 4960 bytes .../self_outdated_check.cpython-312.pyc | Bin 0 -> 10541 bytes .../__pycache__/wheel_builder.cpython-312.pyc | Bin 0 -> 13638 bytes .../site-packages/pip/_internal/build_env.py | 311 + .../site-packages/pip/_internal/cache.py | 290 + .../pip/_internal/cli/__init__.py | 4 + .../cli/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 267 bytes .../autocompletion.cpython-312.pyc | Bin 0 -> 8454 bytes .../__pycache__/base_command.cpython-312.pyc | Bin 0 -> 10444 bytes .../__pycache__/cmdoptions.cpython-312.pyc | Bin 0 -> 30363 bytes .../command_context.cpython-312.pyc | Bin 0 -> 1770 bytes .../cli/__pycache__/main.cpython-312.pyc | Bin 0 -> 2287 bytes .../__pycache__/main_parser.cpython-312.pyc | Bin 0 -> 4894 bytes .../cli/__pycache__/parser.cpython-312.pyc | Bin 0 -> 15011 bytes .../__pycache__/progress_bars.cpython-312.pyc | Bin 0 -> 2609 bytes .../__pycache__/req_command.cpython-312.pyc | Bin 0 -> 18841 bytes .../cli/__pycache__/spinners.cpython-312.pyc | Bin 0 -> 7829 bytes .../__pycache__/status_codes.cpython-312.pyc | Bin 0 -> 364 bytes .../pip/_internal/cli/autocompletion.py | 172 + .../pip/_internal/cli/base_command.py | 236 + .../pip/_internal/cli/cmdoptions.py | 1074 ++ .../pip/_internal/cli/command_context.py | 27 + .../site-packages/pip/_internal/cli/main.py | 79 + .../pip/_internal/cli/main_parser.py | 134 + .../site-packages/pip/_internal/cli/parser.py | 294 + .../pip/_internal/cli/progress_bars.py | 68 + .../pip/_internal/cli/req_command.py | 505 + .../pip/_internal/cli/spinners.py | 159 + .../pip/_internal/cli/status_codes.py | 6 + .../pip/_internal/commands/__init__.py | 132 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3991 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 9700 bytes .../__pycache__/check.cpython-312.pyc | Bin 0 -> 2079 bytes .../__pycache__/completion.cpython-312.pyc | Bin 0 -> 5181 bytes .../__pycache__/configuration.cpython-312.pyc | Bin 0 -> 13201 bytes .../__pycache__/debug.cpython-312.pyc | Bin 0 -> 10150 bytes .../__pycache__/download.cpython-312.pyc | Bin 0 -> 7578 bytes .../__pycache__/freeze.cpython-312.pyc | Bin 0 -> 4405 bytes .../commands/__pycache__/hash.cpython-312.pyc | Bin 0 -> 2972 bytes .../commands/__pycache__/help.cpython-312.pyc | Bin 0 -> 1662 bytes .../__pycache__/index.cpython-312.pyc | Bin 0 -> 6709 bytes .../__pycache__/inspect.cpython-312.pyc | Bin 0 -> 3964 bytes .../__pycache__/install.cpython-312.pyc | Bin 0 -> 28902 bytes .../commands/__pycache__/list.cpython-312.pyc | Bin 0 -> 15645 bytes .../__pycache__/search.cpython-312.pyc | Bin 0 -> 7610 bytes .../commands/__pycache__/show.cpython-312.pyc | Bin 0 -> 9717 bytes .../__pycache__/uninstall.cpython-312.pyc | Bin 0 -> 4715 bytes .../__pycache__/wheel.cpython-312.pyc | Bin 0 -> 8945 bytes .../pip/_internal/commands/cache.py | 225 + .../pip/_internal/commands/check.py | 54 + .../pip/_internal/commands/completion.py | 130 + .../pip/_internal/commands/configuration.py | 280 + .../pip/_internal/commands/debug.py | 201 + .../pip/_internal/commands/download.py | 147 + .../pip/_internal/commands/freeze.py | 109 + .../pip/_internal/commands/hash.py | 59 + .../pip/_internal/commands/help.py | 41 + .../pip/_internal/commands/index.py | 139 + .../pip/_internal/commands/inspect.py | 92 + .../pip/_internal/commands/install.py | 774 ++ .../pip/_internal/commands/list.py | 370 + .../pip/_internal/commands/search.py | 174 + .../pip/_internal/commands/show.py | 189 + .../pip/_internal/commands/uninstall.py | 113 + .../pip/_internal/commands/wheel.py | 183 + .../pip/_internal/configuration.py | 383 + .../pip/_internal/distributions/__init__.py | 21 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 930 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 2851 bytes .../__pycache__/installed.cpython-312.pyc | Bin 0 -> 1689 bytes .../__pycache__/sdist.cpython-312.pyc | Bin 0 -> 8477 bytes .../__pycache__/wheel.cpython-312.pyc | Bin 0 -> 2237 bytes .../pip/_internal/distributions/base.py | 51 + .../pip/_internal/distributions/installed.py | 29 + .../pip/_internal/distributions/sdist.py | 156 + .../pip/_internal/distributions/wheel.py | 40 + .../site-packages/pip/_internal/exceptions.py | 728 ++ .../pip/_internal/index/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 221 bytes .../__pycache__/collector.cpython-312.pyc | Bin 0 -> 21875 bytes .../package_finder.cpython-312.pyc | Bin 0 -> 40724 bytes .../index/__pycache__/sources.cpython-312.pyc | Bin 0 -> 12593 bytes .../pip/_internal/index/collector.py | 507 + .../pip/_internal/index/package_finder.py | 1027 ++ .../pip/_internal/index/sources.py | 285 + .../pip/_internal/locations/__init__.py | 467 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 16765 bytes .../__pycache__/_distutils.cpython-312.pyc | Bin 0 -> 6845 bytes .../__pycache__/_sysconfig.cpython-312.pyc | Bin 0 -> 8000 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 3770 bytes .../pip/_internal/locations/_distutils.py | 172 + .../pip/_internal/locations/_sysconfig.py | 213 + .../pip/_internal/locations/base.py | 81 + .../site-packages/pip/_internal/main.py | 12 + .../pip/_internal/metadata/__init__.py | 128 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5871 bytes .../__pycache__/_json.cpython-312.pyc | Bin 0 -> 2864 bytes .../metadata/__pycache__/base.cpython-312.pyc | Bin 0 -> 35701 bytes .../__pycache__/pkg_resources.cpython-312.pyc | Bin 0 -> 15779 bytes .../pip/_internal/metadata/_json.py | 84 + .../pip/_internal/metadata/base.py | 702 ++ .../_internal/metadata/importlib/__init__.py | 6 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 347 bytes .../__pycache__/_compat.cpython-312.pyc | Bin 0 -> 3322 bytes .../__pycache__/_dists.cpython-312.pyc | Bin 0 -> 13414 bytes .../__pycache__/_envs.cpython-312.pyc | Bin 0 -> 11169 bytes .../_internal/metadata/importlib/_compat.py | 55 + .../_internal/metadata/importlib/_dists.py | 227 + .../pip/_internal/metadata/importlib/_envs.py | 189 + .../pip/_internal/metadata/pkg_resources.py | 278 + .../pip/_internal/models/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 255 bytes .../__pycache__/candidate.cpython-312.pyc | Bin 0 -> 1894 bytes .../__pycache__/direct_url.cpython-312.pyc | Bin 0 -> 11188 bytes .../format_control.cpython-312.pyc | Bin 0 -> 4216 bytes .../models/__pycache__/index.cpython-312.pyc | Bin 0 -> 1683 bytes .../installation_report.cpython-312.pyc | Bin 0 -> 2261 bytes .../models/__pycache__/link.cpython-312.pyc | Bin 0 -> 25991 bytes .../models/__pycache__/scheme.cpython-312.pyc | Bin 0 -> 1158 bytes .../__pycache__/search_scope.cpython-312.pyc | Bin 0 -> 5077 bytes .../selection_prefs.cpython-312.pyc | Bin 0 -> 1840 bytes .../__pycache__/target_python.cpython-312.pyc | Bin 0 -> 4943 bytes .../models/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 5769 bytes .../pip/_internal/models/candidate.py | 30 + .../pip/_internal/models/direct_url.py | 235 + .../pip/_internal/models/format_control.py | 78 + .../pip/_internal/models/index.py | 28 + .../_internal/models/installation_report.py | 56 + .../pip/_internal/models/link.py | 579 ++ .../pip/_internal/models/scheme.py | 31 + .../pip/_internal/models/search_scope.py | 132 + .../pip/_internal/models/selection_prefs.py | 51 + .../pip/_internal/models/target_python.py | 122 + .../pip/_internal/models/wheel.py | 92 + .../pip/_internal/network/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 243 bytes .../network/__pycache__/auth.cpython-312.pyc | Bin 0 -> 21985 bytes .../network/__pycache__/cache.cpython-312.pyc | Bin 0 -> 6507 bytes .../__pycache__/download.cpython-312.pyc | Bin 0 -> 8542 bytes .../__pycache__/lazy_wheel.cpython-312.pyc | Bin 0 -> 11652 bytes .../__pycache__/session.cpython-312.pyc | Bin 0 -> 18763 bytes .../network/__pycache__/utils.cpython-312.pyc | Bin 0 -> 2242 bytes .../__pycache__/xmlrpc.cpython-312.pyc | Bin 0 -> 2938 bytes .../pip/_internal/network/auth.py | 561 ++ .../pip/_internal/network/cache.py | 106 + .../pip/_internal/network/download.py | 186 + .../pip/_internal/network/lazy_wheel.py | 210 + .../pip/_internal/network/session.py | 520 + .../pip/_internal/network/utils.py | 96 + .../pip/_internal/network/xmlrpc.py | 62 + .../pip/_internal/operations/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 186 bytes .../__pycache__/check.cpython-312.pyc | Bin 0 -> 7568 bytes .../__pycache__/freeze.cpython-312.pyc | Bin 0 -> 10106 bytes .../__pycache__/prepare.cpython-312.pyc | Bin 0 -> 25736 bytes .../_internal/operations/build/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 192 bytes .../__pycache__/build_tracker.cpython-312.pyc | Bin 0 -> 7812 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 1869 bytes .../metadata_editable.cpython-312.pyc | Bin 0 -> 1903 bytes .../metadata_legacy.cpython-312.pyc | Bin 0 -> 3054 bytes .../build/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 1673 bytes .../wheel_editable.cpython-312.pyc | Bin 0 -> 2014 bytes .../__pycache__/wheel_legacy.cpython-312.pyc | Bin 0 -> 3918 bytes .../operations/build/build_tracker.py | 139 + .../_internal/operations/build/metadata.py | 39 + .../operations/build/metadata_editable.py | 41 + .../operations/build/metadata_legacy.py | 74 + .../pip/_internal/operations/build/wheel.py | 37 + .../operations/build/wheel_editable.py | 46 + .../operations/build/wheel_legacy.py | 102 + .../pip/_internal/operations/check.py | 187 + .../pip/_internal/operations/freeze.py | 255 + .../_internal/operations/install/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 255 bytes .../editable_legacy.cpython-312.pyc | Bin 0 -> 1806 bytes .../install/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 33848 bytes .../operations/install/editable_legacy.py | 46 + .../pip/_internal/operations/install/wheel.py | 734 ++ .../pip/_internal/operations/prepare.py | 730 ++ .../site-packages/pip/_internal/pyproject.py | 179 + .../pip/_internal/req/__init__.py | 92 + .../req/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3732 bytes .../__pycache__/constructors.cpython-312.pyc | Bin 0 -> 21571 bytes .../req/__pycache__/req_file.cpython-312.pyc | Bin 0 -> 21450 bytes .../__pycache__/req_install.cpython-312.pyc | Bin 0 -> 38403 bytes .../req/__pycache__/req_set.cpython-312.pyc | Bin 0 -> 7207 bytes .../__pycache__/req_uninstall.cpython-312.pyc | Bin 0 -> 32966 bytes .../pip/_internal/req/constructors.py | 576 ++ .../pip/_internal/req/req_file.py | 554 ++ .../pip/_internal/req/req_install.py | 923 ++ .../pip/_internal/req/req_set.py | 119 + .../pip/_internal/req/req_uninstall.py | 649 ++ .../pip/_internal/resolution/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 186 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 1174 bytes .../pip/_internal/resolution/base.py | 20 + .../_internal/resolution/legacy/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 193 bytes .../__pycache__/resolver.cpython-312.pyc | Bin 0 -> 22428 bytes .../_internal/resolution/legacy/resolver.py | 598 ++ .../resolution/resolvelib/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 197 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 8326 bytes .../__pycache__/candidates.cpython-312.pyc | Bin 0 -> 30387 bytes .../__pycache__/factory.cpython-312.pyc | Bin 0 -> 32103 bytes .../found_candidates.cpython-312.pyc | Bin 0 -> 6197 bytes .../__pycache__/provider.cpython-312.pyc | Bin 0 -> 10367 bytes .../__pycache__/reporter.cpython-312.pyc | Bin 0 -> 4924 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 11418 bytes .../__pycache__/resolver.cpython-312.pyc | Bin 0 -> 12340 bytes .../_internal/resolution/resolvelib/base.py | 141 + .../resolution/resolvelib/candidates.py | 597 ++ .../resolution/resolvelib/factory.py | 812 ++ .../resolution/resolvelib/found_candidates.py | 155 + .../resolution/resolvelib/provider.py | 255 + .../resolution/resolvelib/reporter.py | 80 + .../resolution/resolvelib/requirements.py | 166 + .../resolution/resolvelib/resolver.py | 317 + .../pip/_internal/self_outdated_check.py | 248 + .../pip/_internal/utils/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 181 bytes .../__pycache__/_jaraco_text.cpython-312.pyc | Bin 0 -> 4522 bytes .../utils/__pycache__/_log.cpython-312.pyc | Bin 0 -> 1852 bytes .../utils/__pycache__/appdirs.cpython-312.pyc | Bin 0 -> 2396 bytes .../utils/__pycache__/compat.cpython-312.pyc | Bin 0 -> 2199 bytes .../compatibility_tags.cpython-312.pyc | Bin 0 -> 5547 bytes .../__pycache__/datetime.cpython-312.pyc | Bin 0 -> 670 bytes .../__pycache__/deprecation.cpython-312.pyc | Bin 0 -> 4172 bytes .../direct_url_helpers.cpython-312.pyc | Bin 0 -> 3549 bytes .../__pycache__/egg_link.cpython-312.pyc | Bin 0 -> 3212 bytes .../__pycache__/encoding.cpython-312.pyc | Bin 0 -> 2144 bytes .../__pycache__/entrypoints.cpython-312.pyc | Bin 0 -> 3979 bytes .../__pycache__/filesystem.cpython-312.pyc | Bin 0 -> 7444 bytes .../__pycache__/filetypes.cpython-312.pyc | Bin 0 -> 1150 bytes .../utils/__pycache__/glibc.cpython-312.pyc | Bin 0 -> 2328 bytes .../utils/__pycache__/hashes.cpython-312.pyc | Bin 0 -> 7540 bytes .../utils/__pycache__/logging.cpython-312.pyc | Bin 0 -> 13543 bytes .../utils/__pycache__/misc.cpython-312.pyc | Bin 0 -> 34107 bytes .../utils/__pycache__/models.cpython-312.pyc | Bin 0 -> 2698 bytes .../__pycache__/packaging.cpython-312.pyc | Bin 0 -> 2569 bytes .../setuptools_build.cpython-312.pyc | Bin 0 -> 4536 bytes .../__pycache__/subprocess.cpython-312.pyc | Bin 0 -> 8704 bytes .../__pycache__/temp_dir.cpython-312.pyc | Bin 0 -> 12048 bytes .../__pycache__/unpacking.cpython-312.pyc | Bin 0 -> 11094 bytes .../utils/__pycache__/urls.cpython-312.pyc | Bin 0 -> 2391 bytes .../__pycache__/virtualenv.cpython-312.pyc | Bin 0 -> 4466 bytes .../utils/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 5912 bytes .../pip/_internal/utils/_jaraco_text.py | 109 + .../site-packages/pip/_internal/utils/_log.py | 38 + .../pip/_internal/utils/appdirs.py | 52 + .../pip/_internal/utils/compat.py | 63 + .../pip/_internal/utils/compatibility_tags.py | 165 + .../pip/_internal/utils/datetime.py | 11 + .../pip/_internal/utils/deprecation.py | 120 + .../pip/_internal/utils/direct_url_helpers.py | 87 + .../pip/_internal/utils/egg_link.py | 80 + .../pip/_internal/utils/encoding.py | 36 + .../pip/_internal/utils/entrypoints.py | 84 + .../pip/_internal/utils/filesystem.py | 153 + .../pip/_internal/utils/filetypes.py | 27 + .../pip/_internal/utils/glibc.py | 88 + .../pip/_internal/utils/hashes.py | 151 + .../pip/_internal/utils/logging.py | 348 + .../site-packages/pip/_internal/utils/misc.py | 783 ++ .../pip/_internal/utils/models.py | 39 + .../pip/_internal/utils/packaging.py | 57 + .../pip/_internal/utils/setuptools_build.py | 146 + .../pip/_internal/utils/subprocess.py | 260 + .../pip/_internal/utils/temp_dir.py | 296 + .../pip/_internal/utils/unpacking.py | 257 + .../site-packages/pip/_internal/utils/urls.py | 62 + .../pip/_internal/utils/virtualenv.py | 104 + .../pip/_internal/utils/wheel.py | 134 + .../pip/_internal/vcs/__init__.py | 15 + .../vcs/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 520 bytes .../vcs/__pycache__/bazaar.cpython-312.pyc | Bin 0 -> 5012 bytes .../vcs/__pycache__/git.cpython-312.pyc | Bin 0 -> 18981 bytes .../vcs/__pycache__/mercurial.cpython-312.pyc | Bin 0 -> 7601 bytes .../__pycache__/subversion.cpython-312.pyc | Bin 0 -> 12473 bytes .../versioncontrol.cpython-312.pyc | Bin 0 -> 28999 bytes .../site-packages/pip/_internal/vcs/bazaar.py | 112 + .../site-packages/pip/_internal/vcs/git.py | 526 + .../pip/_internal/vcs/mercurial.py | 163 + .../pip/_internal/vcs/subversion.py | 324 + .../pip/_internal/vcs/versioncontrol.py | 705 ++ .../pip/_internal/wheel_builder.py | 354 + .../site-packages/pip/_vendor/__init__.py | 121 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4682 bytes .../_vendor/__pycache__/six.cpython-312.pyc | Bin 0 -> 41259 bytes .../typing_extensions.cpython-312.pyc | Bin 0 -> 122039 bytes .../pip/_vendor/cachecontrol/__init__.py | 28 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 892 bytes .../__pycache__/_cmd.cpython-312.pyc | Bin 0 -> 2636 bytes .../__pycache__/adapter.cpython-312.pyc | Bin 0 -> 6454 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 3799 bytes .../__pycache__/controller.cpython-312.pyc | Bin 0 -> 16157 bytes .../__pycache__/filewrapper.cpython-312.pyc | Bin 0 -> 4337 bytes .../__pycache__/heuristics.cpython-312.pyc | Bin 0 -> 6684 bytes .../__pycache__/serialize.cpython-312.pyc | Bin 0 -> 6395 bytes .../__pycache__/wrapper.cpython-312.pyc | Bin 0 -> 1664 bytes .../pip/_vendor/cachecontrol/_cmd.py | 70 + .../pip/_vendor/cachecontrol/adapter.py | 161 + .../pip/_vendor/cachecontrol/cache.py | 74 + .../_vendor/cachecontrol/caches/__init__.py | 8 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 425 bytes .../__pycache__/file_cache.cpython-312.pyc | Bin 0 -> 7700 bytes .../__pycache__/redis_cache.cpython-312.pyc | Bin 0 -> 2728 bytes .../_vendor/cachecontrol/caches/file_cache.py | 181 + .../cachecontrol/caches/redis_cache.py | 48 + .../pip/_vendor/cachecontrol/controller.py | 494 + .../pip/_vendor/cachecontrol/filewrapper.py | 119 + .../pip/_vendor/cachecontrol/heuristics.py | 154 + .../pip/_vendor/cachecontrol/serialize.py | 206 + .../pip/_vendor/cachecontrol/wrapper.py | 43 + .../pip/_vendor/certifi/__init__.py | 4 + .../pip/_vendor/certifi/__main__.py | 12 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 308 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 635 bytes .../certifi/__pycache__/core.cpython-312.pyc | Bin 0 -> 3317 bytes .../pip/_vendor/certifi/cacert.pem | 4635 +++++++++ .../site-packages/pip/_vendor/certifi/core.py | 119 + .../pip/_vendor/chardet/__init__.py | 115 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4558 bytes .../__pycache__/big5freq.cpython-312.pyc | Bin 0 -> 27189 bytes .../__pycache__/big5prober.cpython-312.pyc | Bin 0 -> 1377 bytes .../chardistribution.cpython-312.pyc | Bin 0 -> 9628 bytes .../charsetgroupprober.cpython-312.pyc | Bin 0 -> 4112 bytes .../__pycache__/charsetprober.cpython-312.pyc | Bin 0 -> 5008 bytes .../codingstatemachine.cpython-312.pyc | Bin 0 -> 3868 bytes .../codingstatemachinedict.cpython-312.pyc | Bin 0 -> 779 bytes .../__pycache__/cp949prober.cpython-312.pyc | Bin 0 -> 1386 bytes .../chardet/__pycache__/enums.cpython-312.pyc | Bin 0 -> 2986 bytes .../__pycache__/escprober.cpython-312.pyc | Bin 0 -> 4556 bytes .../chardet/__pycache__/escsm.cpython-312.pyc | Bin 0 -> 15300 bytes .../__pycache__/eucjpprober.cpython-312.pyc | Bin 0 -> 4373 bytes .../__pycache__/euckrfreq.cpython-312.pyc | Bin 0 -> 12072 bytes .../__pycache__/euckrprober.cpython-312.pyc | Bin 0 -> 1380 bytes .../__pycache__/euctwfreq.cpython-312.pyc | Bin 0 -> 27194 bytes .../__pycache__/euctwprober.cpython-312.pyc | Bin 0 -> 1380 bytes .../__pycache__/gb2312freq.cpython-312.pyc | Bin 0 -> 19116 bytes .../__pycache__/gb2312prober.cpython-312.pyc | Bin 0 -> 1393 bytes .../__pycache__/hebrewprober.cpython-312.pyc | Bin 0 -> 5812 bytes .../__pycache__/jisfreq.cpython-312.pyc | Bin 0 -> 22145 bytes .../__pycache__/johabfreq.cpython-312.pyc | Bin 0 -> 82993 bytes .../__pycache__/johabprober.cpython-312.pyc | Bin 0 -> 1384 bytes .../__pycache__/jpcntx.cpython-312.pyc | Bin 0 -> 39539 bytes .../langbulgarianmodel.cpython-312.pyc | Bin 0 -> 83112 bytes .../langgreekmodel.cpython-312.pyc | Bin 0 -> 76978 bytes .../langhebrewmodel.cpython-312.pyc | Bin 0 -> 77489 bytes .../langhungarianmodel.cpython-312.pyc | Bin 0 -> 83066 bytes .../langrussianmodel.cpython-312.pyc | Bin 0 -> 105241 bytes .../__pycache__/langthaimodel.cpython-312.pyc | Bin 0 -> 77667 bytes .../langturkishmodel.cpython-312.pyc | Bin 0 -> 77506 bytes .../__pycache__/latin1prober.cpython-312.pyc | Bin 0 -> 6992 bytes .../macromanprober.cpython-312.pyc | Bin 0 -> 7172 bytes .../mbcharsetprober.cpython-312.pyc | Bin 0 -> 3893 bytes .../mbcsgroupprober.cpython-312.pyc | Bin 0 -> 1578 bytes .../__pycache__/mbcssm.cpython-312.pyc | Bin 0 -> 38635 bytes .../__pycache__/resultdict.cpython-312.pyc | Bin 0 -> 622 bytes .../sbcharsetprober.cpython-312.pyc | Bin 0 -> 6377 bytes .../sbcsgroupprober.cpython-312.pyc | Bin 0 -> 2347 bytes .../__pycache__/sjisprober.cpython-312.pyc | Bin 0 -> 4485 bytes .../universaldetector.cpython-312.pyc | Bin 0 -> 12259 bytes .../__pycache__/utf1632prober.cpython-312.pyc | Bin 0 -> 9969 bytes .../__pycache__/utf8prober.cpython-312.pyc | Bin 0 -> 3165 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 478 bytes .../pip/_vendor/chardet/big5freq.py | 386 + .../pip/_vendor/chardet/big5prober.py | 47 + .../pip/_vendor/chardet/chardistribution.py | 261 + .../pip/_vendor/chardet/charsetgroupprober.py | 106 + .../pip/_vendor/chardet/charsetprober.py | 147 + .../pip/_vendor/chardet/cli/__init__.py | 0 .../cli/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 185 bytes .../__pycache__/chardetect.cpython-312.pyc | Bin 0 -> 4002 bytes .../pip/_vendor/chardet/cli/chardetect.py | 112 + .../pip/_vendor/chardet/codingstatemachine.py | 90 + .../_vendor/chardet/codingstatemachinedict.py | 19 + .../pip/_vendor/chardet/cp949prober.py | 49 + .../pip/_vendor/chardet/enums.py | 85 + .../pip/_vendor/chardet/escprober.py | 102 + .../pip/_vendor/chardet/escsm.py | 261 + .../pip/_vendor/chardet/eucjpprober.py | 102 + .../pip/_vendor/chardet/euckrfreq.py | 196 + .../pip/_vendor/chardet/euckrprober.py | 47 + .../pip/_vendor/chardet/euctwfreq.py | 388 + .../pip/_vendor/chardet/euctwprober.py | 47 + .../pip/_vendor/chardet/gb2312freq.py | 284 + .../pip/_vendor/chardet/gb2312prober.py | 47 + .../pip/_vendor/chardet/hebrewprober.py | 316 + .../pip/_vendor/chardet/jisfreq.py | 325 + .../pip/_vendor/chardet/johabfreq.py | 2382 +++++ .../pip/_vendor/chardet/johabprober.py | 47 + .../pip/_vendor/chardet/jpcntx.py | 238 + .../pip/_vendor/chardet/langbulgarianmodel.py | 4649 +++++++++ .../pip/_vendor/chardet/langgreekmodel.py | 4397 +++++++++ .../pip/_vendor/chardet/langhebrewmodel.py | 4380 +++++++++ .../pip/_vendor/chardet/langhungarianmodel.py | 4649 +++++++++ .../pip/_vendor/chardet/langrussianmodel.py | 5725 +++++++++++ .../pip/_vendor/chardet/langthaimodel.py | 4380 +++++++++ .../pip/_vendor/chardet/langturkishmodel.py | 4380 +++++++++ .../pip/_vendor/chardet/latin1prober.py | 147 + .../pip/_vendor/chardet/macromanprober.py | 162 + .../pip/_vendor/chardet/mbcharsetprober.py | 95 + .../pip/_vendor/chardet/mbcsgroupprober.py | 57 + .../pip/_vendor/chardet/mbcssm.py | 661 ++ .../pip/_vendor/chardet/metadata/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 190 bytes .../__pycache__/languages.cpython-312.pyc | Bin 0 -> 9745 bytes .../pip/_vendor/chardet/metadata/languages.py | 352 + .../pip/_vendor/chardet/resultdict.py | 16 + .../pip/_vendor/chardet/sbcharsetprober.py | 162 + .../pip/_vendor/chardet/sbcsgroupprober.py | 88 + .../pip/_vendor/chardet/sjisprober.py | 105 + .../pip/_vendor/chardet/universaldetector.py | 362 + .../pip/_vendor/chardet/utf1632prober.py | 225 + .../pip/_vendor/chardet/utf8prober.py | 82 + .../pip/_vendor/chardet/version.py | 9 + .../pip/_vendor/colorama/__init__.py | 7 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 482 bytes .../colorama/__pycache__/ansi.cpython-312.pyc | Bin 0 -> 3940 bytes .../__pycache__/ansitowin32.cpython-312.pyc | Bin 0 -> 16411 bytes .../__pycache__/initialise.cpython-312.pyc | Bin 0 -> 3540 bytes .../__pycache__/win32.cpython-312.pyc | Bin 0 -> 8116 bytes .../__pycache__/winterm.cpython-312.pyc | Bin 0 -> 9078 bytes .../pip/_vendor/colorama/ansi.py | 102 + .../pip/_vendor/colorama/ansitowin32.py | 277 + .../pip/_vendor/colorama/initialise.py | 121 + .../pip/_vendor/colorama/tests/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 188 bytes .../__pycache__/ansi_test.cpython-312.pyc | Bin 0 -> 5457 bytes .../ansitowin32_test.cpython-312.pyc | Bin 0 -> 18093 bytes .../initialise_test.cpython-312.pyc | Bin 0 -> 11738 bytes .../__pycache__/isatty_test.cpython-312.pyc | Bin 0 -> 4894 bytes .../tests/__pycache__/utils.cpython-312.pyc | Bin 0 -> 2478 bytes .../__pycache__/winterm_test.cpython-312.pyc | Bin 0 -> 6602 bytes .../pip/_vendor/colorama/tests/ansi_test.py | 76 + .../colorama/tests/ansitowin32_test.py | 294 + .../_vendor/colorama/tests/initialise_test.py | 189 + .../pip/_vendor/colorama/tests/isatty_test.py | 57 + .../pip/_vendor/colorama/tests/utils.py | 49 + .../_vendor/colorama/tests/winterm_test.py | 131 + .../pip/_vendor/colorama/win32.py | 180 + .../pip/_vendor/colorama/winterm.py | 195 + .../pip/_vendor/distlib/__init__.py | 33 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1259 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 45595 bytes .../__pycache__/database.cpython-312.pyc | Bin 0 -> 66017 bytes .../distlib/__pycache__/index.cpython-312.pyc | Bin 0 -> 24356 bytes .../__pycache__/locators.cpython-312.pyc | Bin 0 -> 60148 bytes .../__pycache__/manifest.cpython-312.pyc | Bin 0 -> 15115 bytes .../__pycache__/markers.cpython-312.pyc | Bin 0 -> 7672 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 41789 bytes .../__pycache__/resources.cpython-312.pyc | Bin 0 -> 17315 bytes .../__pycache__/scripts.cpython-312.pyc | Bin 0 -> 19570 bytes .../distlib/__pycache__/util.cpython-312.pyc | Bin 0 -> 88246 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 30356 bytes .../distlib/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 51851 bytes .../pip/_vendor/distlib/compat.py | 1138 +++ .../pip/_vendor/distlib/database.py | 1359 +++ .../pip/_vendor/distlib/index.py | 508 + .../pip/_vendor/distlib/locators.py | 1303 +++ .../pip/_vendor/distlib/manifest.py | 384 + .../pip/_vendor/distlib/markers.py | 167 + .../pip/_vendor/distlib/metadata.py | 1068 ++ .../pip/_vendor/distlib/resources.py | 358 + .../pip/_vendor/distlib/scripts.py | 452 + .../site-packages/pip/_vendor/distlib/util.py | 2025 ++++ .../pip/_vendor/distlib/version.py | 751 ++ .../pip/_vendor/distlib/wheel.py | 1099 +++ .../pip/_vendor/distro/__init__.py | 54 + .../pip/_vendor/distro/__main__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 950 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 282 bytes .../distro/__pycache__/distro.cpython-312.pyc | Bin 0 -> 53744 bytes .../pip/_vendor/distro/distro.py | 1399 +++ .../pip/_vendor/idna/__init__.py | 44 + .../idna/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 871 bytes .../idna/__pycache__/codec.cpython-312.pyc | Bin 0 -> 4623 bytes .../idna/__pycache__/compat.cpython-312.pyc | Bin 0 -> 877 bytes .../idna/__pycache__/core.cpython-312.pyc | Bin 0 -> 16013 bytes .../idna/__pycache__/idnadata.cpython-312.pyc | Bin 0 -> 99487 bytes .../__pycache__/intranges.cpython-312.pyc | Bin 0 -> 2628 bytes .../__pycache__/package_data.cpython-312.pyc | Bin 0 -> 206 bytes .../__pycache__/uts46data.cpython-312.pyc | Bin 0 -> 158860 bytes .../site-packages/pip/_vendor/idna/codec.py | 112 + .../site-packages/pip/_vendor/idna/compat.py | 13 + .../site-packages/pip/_vendor/idna/core.py | 400 + .../pip/_vendor/idna/idnadata.py | 4246 ++++++++ .../pip/_vendor/idna/intranges.py | 54 + .../pip/_vendor/idna/package_data.py | 2 + .../pip/_vendor/idna/uts46data.py | 8600 +++++++++++++++++ .../pip/_vendor/msgpack/__init__.py | 57 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1821 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 2015 bytes .../msgpack/__pycache__/ext.cpython-312.pyc | Bin 0 -> 8658 bytes .../__pycache__/fallback.cpython-312.pyc | Bin 0 -> 43566 bytes .../pip/_vendor/msgpack/exceptions.py | 48 + .../site-packages/pip/_vendor/msgpack/ext.py | 193 + .../pip/_vendor/msgpack/fallback.py | 1010 ++ .../pip/_vendor/packaging/__about__.py | 26 + .../pip/_vendor/packaging/__init__.py | 25 + .../__pycache__/__about__.cpython-312.pyc | Bin 0 -> 620 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 456 bytes .../__pycache__/_manylinux.cpython-312.pyc | Bin 0 -> 12066 bytes .../__pycache__/_musllinux.cpython-312.pyc | Bin 0 -> 6900 bytes .../__pycache__/_structures.cpython-312.pyc | Bin 0 -> 3231 bytes .../__pycache__/markers.cpython-312.pyc | Bin 0 -> 14048 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 6936 bytes .../__pycache__/specifiers.cpython-312.pyc | Bin 0 -> 31237 bytes .../__pycache__/tags.cpython-312.pyc | Bin 0 -> 18946 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 5858 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 19929 bytes .../pip/_vendor/packaging/_manylinux.py | 301 + .../pip/_vendor/packaging/_musllinux.py | 136 + .../pip/_vendor/packaging/_structures.py | 61 + .../pip/_vendor/packaging/markers.py | 304 + .../pip/_vendor/packaging/requirements.py | 146 + .../pip/_vendor/packaging/specifiers.py | 802 ++ .../pip/_vendor/packaging/tags.py | 487 + .../pip/_vendor/packaging/utils.py | 136 + .../pip/_vendor/packaging/version.py | 504 + .../pip/_vendor/pkg_resources/__init__.py | 3361 +++++++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 146464 bytes .../pip/_vendor/platformdirs/__init__.py | 566 ++ .../pip/_vendor/platformdirs/__main__.py | 53 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 18019 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 1936 bytes .../__pycache__/android.cpython-312.pyc | Bin 0 -> 9434 bytes .../__pycache__/api.cpython-312.pyc | Bin 0 -> 9662 bytes .../__pycache__/macos.cpython-312.pyc | Bin 0 -> 5627 bytes .../__pycache__/unix.cpython-312.pyc | Bin 0 -> 12431 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 301 bytes .../__pycache__/windows.cpython-312.pyc | Bin 0 -> 12989 bytes .../pip/_vendor/platformdirs/android.py | 210 + .../pip/_vendor/platformdirs/api.py | 223 + .../pip/_vendor/platformdirs/macos.py | 91 + .../pip/_vendor/platformdirs/unix.py | 223 + .../pip/_vendor/platformdirs/version.py | 4 + .../pip/_vendor/platformdirs/windows.py | 255 + .../pip/_vendor/pygments/__init__.py | 82 + .../pip/_vendor/pygments/__main__.py | 17 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3479 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 725 bytes .../__pycache__/cmdline.cpython-312.pyc | Bin 0 -> 26596 bytes .../__pycache__/console.cpython-312.pyc | Bin 0 -> 2617 bytes .../__pycache__/filter.cpython-312.pyc | Bin 0 -> 3223 bytes .../__pycache__/formatter.cpython-312.pyc | Bin 0 -> 4560 bytes .../__pycache__/lexer.cpython-312.pyc | Bin 0 -> 38320 bytes .../__pycache__/modeline.cpython-312.pyc | Bin 0 -> 1559 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 0 -> 3387 bytes .../__pycache__/regexopt.cpython-312.pyc | Bin 0 -> 4072 bytes .../__pycache__/scanner.cpython-312.pyc | Bin 0 -> 4747 bytes .../__pycache__/sphinxext.cpython-312.pyc | Bin 0 -> 11037 bytes .../__pycache__/style.cpython-312.pyc | Bin 0 -> 6665 bytes .../__pycache__/token.cpython-312.pyc | Bin 0 -> 8133 bytes .../__pycache__/unistring.cpython-312.pyc | Bin 0 -> 32979 bytes .../pygments/__pycache__/util.cpython-312.pyc | Bin 0 -> 13972 bytes .../pip/_vendor/pygments/cmdline.py | 668 ++ .../pip/_vendor/pygments/console.py | 70 + .../pip/_vendor/pygments/filter.py | 71 + .../pip/_vendor/pygments/filters/__init__.py | 940 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 37927 bytes .../pip/_vendor/pygments/formatter.py | 124 + .../_vendor/pygments/formatters/__init__.py | 158 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 6917 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 4206 bytes .../__pycache__/bbcode.cpython-312.pyc | Bin 0 -> 4185 bytes .../__pycache__/groff.cpython-312.pyc | Bin 0 -> 7255 bytes .../__pycache__/html.cpython-312.pyc | Bin 0 -> 40563 bytes .../__pycache__/img.cpython-312.pyc | Bin 0 -> 27034 bytes .../__pycache__/irc.cpython-312.pyc | Bin 0 -> 6056 bytes .../__pycache__/latex.cpython-312.pyc | Bin 0 -> 19945 bytes .../__pycache__/other.cpython-312.pyc | Bin 0 -> 6875 bytes .../__pycache__/pangomarkup.cpython-312.pyc | Bin 0 -> 2921 bytes .../__pycache__/rtf.cpython-312.pyc | Bin 0 -> 6117 bytes .../__pycache__/svg.cpython-312.pyc | Bin 0 -> 9057 bytes .../__pycache__/terminal.cpython-312.pyc | Bin 0 -> 5820 bytes .../__pycache__/terminal256.cpython-312.pyc | Bin 0 -> 15148 bytes .../_vendor/pygments/formatters/_mapping.py | 23 + .../pip/_vendor/pygments/formatters/bbcode.py | 108 + .../pip/_vendor/pygments/formatters/groff.py | 170 + .../pip/_vendor/pygments/formatters/html.py | 989 ++ .../pip/_vendor/pygments/formatters/img.py | 645 ++ .../pip/_vendor/pygments/formatters/irc.py | 154 + .../pip/_vendor/pygments/formatters/latex.py | 521 + .../pip/_vendor/pygments/formatters/other.py | 161 + .../pygments/formatters/pangomarkup.py | 83 + .../pip/_vendor/pygments/formatters/rtf.py | 146 + .../pip/_vendor/pygments/formatters/svg.py | 188 + .../_vendor/pygments/formatters/terminal.py | 127 + .../pygments/formatters/terminal256.py | 338 + .../pip/_vendor/pygments/lexer.py | 943 ++ .../pip/_vendor/pygments/lexers/__init__.py | 362 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 14643 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 64395 bytes .../lexers/__pycache__/python.cpython-312.pyc | Bin 0 -> 42630 bytes .../pip/_vendor/pygments/lexers/_mapping.py | 559 ++ .../pip/_vendor/pygments/lexers/python.py | 1198 +++ .../pip/_vendor/pygments/modeline.py | 43 + .../pip/_vendor/pygments/plugin.py | 88 + .../pip/_vendor/pygments/regexopt.py | 91 + .../pip/_vendor/pygments/scanner.py | 104 + .../pip/_vendor/pygments/sphinxext.py | 217 + .../pip/_vendor/pygments/style.py | 197 + .../pip/_vendor/pygments/styles/__init__.py | 103 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4439 bytes .../pip/_vendor/pygments/token.py | 213 + .../pip/_vendor/pygments/unistring.py | 153 + .../pip/_vendor/pygments/util.py | 330 + .../pip/_vendor/pyparsing/__init__.py | 322 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 7902 bytes .../__pycache__/actions.cpython-312.pyc | Bin 0 -> 8386 bytes .../__pycache__/common.cpython-312.pyc | Bin 0 -> 13405 bytes .../__pycache__/core.cpython-312.pyc | Bin 0 -> 267699 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 12985 bytes .../__pycache__/helpers.cpython-312.pyc | Bin 0 -> 48492 bytes .../__pycache__/results.cpython-312.pyc | Bin 0 -> 34101 bytes .../__pycache__/testing.cpython-312.pyc | Bin 0 -> 17179 bytes .../__pycache__/unicode.cpython-312.pyc | Bin 0 -> 13175 bytes .../__pycache__/util.cpython-312.pyc | Bin 0 -> 14895 bytes .../pip/_vendor/pyparsing/actions.py | 217 + .../pip/_vendor/pyparsing/common.py | 432 + .../pip/_vendor/pyparsing/core.py | 6115 ++++++++++++ .../pip/_vendor/pyparsing/diagram/__init__.py | 656 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 26804 bytes .../pip/_vendor/pyparsing/exceptions.py | 299 + .../pip/_vendor/pyparsing/helpers.py | 1100 +++ .../pip/_vendor/pyparsing/results.py | 796 ++ .../pip/_vendor/pyparsing/testing.py | 331 + .../pip/_vendor/pyparsing/unicode.py | 361 + .../pip/_vendor/pyparsing/util.py | 284 + .../pip/_vendor/pyproject_hooks/__init__.py | 23 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 604 bytes .../__pycache__/_compat.cpython-312.pyc | Bin 0 -> 365 bytes .../__pycache__/_impl.cpython-312.pyc | Bin 0 -> 14716 bytes .../pip/_vendor/pyproject_hooks/_compat.py | 8 + .../pip/_vendor/pyproject_hooks/_impl.py | 330 + .../pyproject_hooks/_in_process/__init__.py | 18 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1071 bytes .../__pycache__/_in_process.cpython-312.pyc | Bin 0 -> 14388 bytes .../_in_process/_in_process.py | 353 + .../pip/_vendor/requests/__init__.py | 182 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5444 bytes .../__pycache__/__version__.cpython-312.pyc | Bin 0 -> 575 bytes .../_internal_utils.cpython-312.pyc | Bin 0 -> 2015 bytes .../__pycache__/adapters.cpython-312.pyc | Bin 0 -> 21271 bytes .../requests/__pycache__/api.cpython-312.pyc | Bin 0 -> 7195 bytes .../requests/__pycache__/auth.cpython-312.pyc | Bin 0 -> 13914 bytes .../__pycache__/certs.cpython-312.pyc | Bin 0 -> 913 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 1498 bytes .../__pycache__/cookies.cpython-312.pyc | Bin 0 -> 25237 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7038 bytes .../requests/__pycache__/help.cpython-312.pyc | Bin 0 -> 4303 bytes .../__pycache__/hooks.cpython-312.pyc | Bin 0 -> 1043 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 35439 bytes .../__pycache__/packages.cpython-312.pyc | Bin 0 -> 763 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 27748 bytes .../__pycache__/status_codes.cpython-312.pyc | Bin 0 -> 5950 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 5608 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 36066 bytes .../pip/_vendor/requests/__version__.py | 14 + .../pip/_vendor/requests/_internal_utils.py | 50 + .../pip/_vendor/requests/adapters.py | 538 ++ .../site-packages/pip/_vendor/requests/api.py | 157 + .../pip/_vendor/requests/auth.py | 315 + .../pip/_vendor/requests/certs.py | 24 + .../pip/_vendor/requests/compat.py | 67 + .../pip/_vendor/requests/cookies.py | 561 ++ .../pip/_vendor/requests/exceptions.py | 141 + .../pip/_vendor/requests/help.py | 131 + .../pip/_vendor/requests/hooks.py | 33 + .../pip/_vendor/requests/models.py | 1034 ++ .../pip/_vendor/requests/packages.py | 16 + .../pip/_vendor/requests/sessions.py | 833 ++ .../pip/_vendor/requests/status_codes.py | 128 + .../pip/_vendor/requests/structures.py | 99 + .../pip/_vendor/requests/utils.py | 1088 +++ .../pip/_vendor/resolvelib/__init__.py | 26 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 625 bytes .../__pycache__/providers.cpython-312.pyc | Bin 0 -> 6842 bytes .../__pycache__/reporters.cpython-312.pyc | Bin 0 -> 2645 bytes .../__pycache__/resolvers.cpython-312.pyc | Bin 0 -> 25888 bytes .../__pycache__/structs.cpython-312.pyc | Bin 0 -> 10497 bytes .../pip/_vendor/resolvelib/compat/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 191 bytes .../collections_abc.cpython-312.pyc | Bin 0 -> 411 bytes .../resolvelib/compat/collections_abc.py | 6 + .../pip/_vendor/resolvelib/providers.py | 133 + .../pip/_vendor/resolvelib/reporters.py | 43 + .../pip/_vendor/resolvelib/resolvers.py | 547 ++ .../pip/_vendor/resolvelib/structs.py | 170 + .../pip/_vendor/rich/__init__.py | 177 + .../pip/_vendor/rich/__main__.py | 274 + .../rich/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 7006 bytes .../rich/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 10295 bytes .../__pycache__/_cell_widths.cpython-312.pyc | Bin 0 -> 7812 bytes .../__pycache__/_emoji_codes.cpython-312.pyc | Bin 0 -> 205967 bytes .../_emoji_replace.cpython-312.pyc | Bin 0 -> 1720 bytes .../_export_format.cpython-312.pyc | Bin 0 -> 2312 bytes .../__pycache__/_extension.cpython-312.pyc | Bin 0 -> 528 bytes .../rich/__pycache__/_fileno.cpython-312.pyc | Bin 0 -> 846 bytes .../rich/__pycache__/_inspect.cpython-312.pyc | Bin 0 -> 12068 bytes .../__pycache__/_log_render.cpython-312.pyc | Bin 0 -> 4138 bytes .../rich/__pycache__/_loop.cpython-312.pyc | Bin 0 -> 1876 bytes .../__pycache__/_null_file.cpython-312.pyc | Bin 0 -> 3611 bytes .../__pycache__/_palettes.cpython-312.pyc | Bin 0 -> 5151 bytes .../rich/__pycache__/_pick.cpython-312.pyc | Bin 0 -> 717 bytes .../rich/__pycache__/_ratio.cpython-312.pyc | Bin 0 -> 6570 bytes .../__pycache__/_spinners.cpython-312.pyc | Bin 0 -> 13170 bytes .../rich/__pycache__/_stack.cpython-312.pyc | Bin 0 -> 956 bytes .../rich/__pycache__/_timer.cpython-312.pyc | Bin 0 -> 856 bytes .../_win32_console.cpython-312.pyc | Bin 0 -> 28967 bytes .../rich/__pycache__/_windows.cpython-312.pyc | Bin 0 -> 2481 bytes .../_windows_renderer.cpython-312.pyc | Bin 0 -> 3564 bytes .../rich/__pycache__/_wrap.cpython-312.pyc | Bin 0 -> 2351 bytes .../rich/__pycache__/abc.cpython-312.pyc | Bin 0 -> 1599 bytes .../rich/__pycache__/align.cpython-312.pyc | Bin 0 -> 12313 bytes .../rich/__pycache__/ansi.cpython-312.pyc | Bin 0 -> 9097 bytes .../rich/__pycache__/bar.cpython-312.pyc | Bin 0 -> 4263 bytes .../rich/__pycache__/box.cpython-312.pyc | Bin 0 -> 11849 bytes .../rich/__pycache__/cells.cpython-312.pyc | Bin 0 -> 5609 bytes .../rich/__pycache__/color.cpython-312.pyc | Bin 0 -> 26561 bytes .../__pycache__/color_triplet.cpython-312.pyc | Bin 0 -> 1692 bytes .../rich/__pycache__/columns.cpython-312.pyc | Bin 0 -> 8578 bytes .../rich/__pycache__/console.cpython-312.pyc | Bin 0 -> 113784 bytes .../__pycache__/constrain.cpython-312.pyc | Bin 0 -> 2249 bytes .../__pycache__/containers.cpython-312.pyc | Bin 0 -> 9217 bytes .../rich/__pycache__/control.cpython-312.pyc | Bin 0 -> 10920 bytes .../default_styles.cpython-312.pyc | Bin 0 -> 10364 bytes .../rich/__pycache__/diagnose.cpython-312.pyc | Bin 0 -> 1478 bytes .../rich/__pycache__/emoji.cpython-312.pyc | Bin 0 -> 4200 bytes .../rich/__pycache__/errors.cpython-312.pyc | Bin 0 -> 1836 bytes .../__pycache__/file_proxy.cpython-312.pyc | Bin 0 -> 3568 bytes .../rich/__pycache__/filesize.cpython-312.pyc | Bin 0 -> 3073 bytes .../__pycache__/highlighter.cpython-312.pyc | Bin 0 -> 9889 bytes .../rich/__pycache__/json.cpython-312.pyc | Bin 0 -> 6026 bytes .../rich/__pycache__/jupyter.cpython-312.pyc | Bin 0 -> 5200 bytes .../rich/__pycache__/layout.cpython-312.pyc | Bin 0 -> 20211 bytes .../rich/__pycache__/live.cpython-312.pyc | Bin 0 -> 19134 bytes .../__pycache__/live_render.cpython-312.pyc | Bin 0 -> 4885 bytes .../rich/__pycache__/logging.cpython-312.pyc | Bin 0 -> 13545 bytes .../rich/__pycache__/markup.cpython-312.pyc | Bin 0 -> 9289 bytes .../rich/__pycache__/measure.cpython-312.pyc | Bin 0 -> 6367 bytes .../rich/__pycache__/padding.cpython-312.pyc | Bin 0 -> 7125 bytes .../rich/__pycache__/pager.cpython-312.pyc | Bin 0 -> 1811 bytes .../rich/__pycache__/palette.cpython-312.pyc | Bin 0 -> 5305 bytes .../rich/__pycache__/panel.cpython-312.pyc | Bin 0 -> 12088 bytes .../rich/__pycache__/pretty.cpython-312.pyc | Bin 0 -> 40047 bytes .../rich/__pycache__/progress.cpython-312.pyc | Bin 0 -> 75069 bytes .../__pycache__/progress_bar.cpython-312.pyc | Bin 0 -> 10380 bytes .../rich/__pycache__/prompt.cpython-312.pyc | Bin 0 -> 14772 bytes .../rich/__pycache__/protocol.cpython-312.pyc | Bin 0 -> 1783 bytes .../rich/__pycache__/region.cpython-312.pyc | Bin 0 -> 558 bytes .../rich/__pycache__/repr.cpython-312.pyc | Bin 0 -> 6617 bytes .../rich/__pycache__/rule.cpython-312.pyc | Bin 0 -> 6559 bytes .../rich/__pycache__/scope.cpython-312.pyc | Bin 0 -> 3821 bytes .../rich/__pycache__/screen.cpython-312.pyc | Bin 0 -> 2475 bytes .../rich/__pycache__/segment.cpython-312.pyc | Bin 0 -> 28152 bytes .../rich/__pycache__/spinner.cpython-312.pyc | Bin 0 -> 6055 bytes .../rich/__pycache__/status.cpython-312.pyc | Bin 0 -> 6059 bytes .../rich/__pycache__/style.cpython-312.pyc | Bin 0 -> 33505 bytes .../rich/__pycache__/styled.cpython-312.pyc | Bin 0 -> 2130 bytes .../rich/__pycache__/syntax.cpython-312.pyc | Bin 0 -> 39603 bytes .../rich/__pycache__/table.cpython-312.pyc | Bin 0 -> 43575 bytes .../terminal_theme.cpython-312.pyc | Bin 0 -> 3339 bytes .../rich/__pycache__/text.cpython-312.pyc | Bin 0 -> 58954 bytes .../rich/__pycache__/theme.cpython-312.pyc | Bin 0 -> 6331 bytes .../rich/__pycache__/themes.cpython-312.pyc | Bin 0 -> 305 bytes .../__pycache__/traceback.cpython-312.pyc | Bin 0 -> 31539 bytes .../rich/__pycache__/tree.cpython-312.pyc | Bin 0 -> 11430 bytes .../pip/_vendor/rich/_cell_widths.py | 451 + .../pip/_vendor/rich/_emoji_codes.py | 3610 +++++++ .../pip/_vendor/rich/_emoji_replace.py | 32 + .../pip/_vendor/rich/_export_format.py | 76 + .../pip/_vendor/rich/_extension.py | 10 + .../site-packages/pip/_vendor/rich/_fileno.py | 24 + .../pip/_vendor/rich/_inspect.py | 270 + .../pip/_vendor/rich/_log_render.py | 94 + .../site-packages/pip/_vendor/rich/_loop.py | 43 + .../pip/_vendor/rich/_null_file.py | 69 + .../pip/_vendor/rich/_palettes.py | 309 + .../site-packages/pip/_vendor/rich/_pick.py | 17 + .../site-packages/pip/_vendor/rich/_ratio.py | 160 + .../pip/_vendor/rich/_spinners.py | 482 + .../site-packages/pip/_vendor/rich/_stack.py | 16 + .../site-packages/pip/_vendor/rich/_timer.py | 19 + .../pip/_vendor/rich/_win32_console.py | 662 ++ .../pip/_vendor/rich/_windows.py | 72 + .../pip/_vendor/rich/_windows_renderer.py | 56 + .../site-packages/pip/_vendor/rich/_wrap.py | 56 + .../site-packages/pip/_vendor/rich/abc.py | 33 + .../site-packages/pip/_vendor/rich/align.py | 311 + .../site-packages/pip/_vendor/rich/ansi.py | 240 + .../site-packages/pip/_vendor/rich/bar.py | 94 + .../site-packages/pip/_vendor/rich/box.py | 517 + .../site-packages/pip/_vendor/rich/cells.py | 154 + .../site-packages/pip/_vendor/rich/color.py | 622 ++ .../pip/_vendor/rich/color_triplet.py | 38 + .../site-packages/pip/_vendor/rich/columns.py | 187 + .../site-packages/pip/_vendor/rich/console.py | 2633 +++++ .../pip/_vendor/rich/constrain.py | 37 + .../pip/_vendor/rich/containers.py | 167 + .../site-packages/pip/_vendor/rich/control.py | 225 + .../pip/_vendor/rich/default_styles.py | 190 + .../pip/_vendor/rich/diagnose.py | 37 + .../site-packages/pip/_vendor/rich/emoji.py | 96 + .../site-packages/pip/_vendor/rich/errors.py | 34 + .../pip/_vendor/rich/file_proxy.py | 57 + .../pip/_vendor/rich/filesize.py | 89 + .../pip/_vendor/rich/highlighter.py | 232 + .../site-packages/pip/_vendor/rich/json.py | 140 + .../site-packages/pip/_vendor/rich/jupyter.py | 101 + .../site-packages/pip/_vendor/rich/layout.py | 443 + .../site-packages/pip/_vendor/rich/live.py | 375 + .../pip/_vendor/rich/live_render.py | 113 + .../site-packages/pip/_vendor/rich/logging.py | 289 + .../site-packages/pip/_vendor/rich/markup.py | 246 + .../site-packages/pip/_vendor/rich/measure.py | 151 + .../site-packages/pip/_vendor/rich/padding.py | 141 + .../site-packages/pip/_vendor/rich/pager.py | 34 + .../site-packages/pip/_vendor/rich/palette.py | 100 + .../site-packages/pip/_vendor/rich/panel.py | 308 + .../site-packages/pip/_vendor/rich/pretty.py | 994 ++ .../pip/_vendor/rich/progress.py | 1702 ++++ .../pip/_vendor/rich/progress_bar.py | 224 + .../site-packages/pip/_vendor/rich/prompt.py | 376 + .../pip/_vendor/rich/protocol.py | 42 + .../site-packages/pip/_vendor/rich/region.py | 10 + .../site-packages/pip/_vendor/rich/repr.py | 149 + .../site-packages/pip/_vendor/rich/rule.py | 130 + .../site-packages/pip/_vendor/rich/scope.py | 86 + .../site-packages/pip/_vendor/rich/screen.py | 54 + .../site-packages/pip/_vendor/rich/segment.py | 739 ++ .../site-packages/pip/_vendor/rich/spinner.py | 137 + .../site-packages/pip/_vendor/rich/status.py | 132 + .../site-packages/pip/_vendor/rich/style.py | 796 ++ .../site-packages/pip/_vendor/rich/styled.py | 42 + .../site-packages/pip/_vendor/rich/syntax.py | 948 ++ .../site-packages/pip/_vendor/rich/table.py | 1002 ++ .../pip/_vendor/rich/terminal_theme.py | 153 + .../site-packages/pip/_vendor/rich/text.py | 1307 +++ .../site-packages/pip/_vendor/rich/theme.py | 115 + .../site-packages/pip/_vendor/rich/themes.py | 5 + .../pip/_vendor/rich/traceback.py | 756 ++ .../site-packages/pip/_vendor/rich/tree.py | 251 + .../site-packages/pip/_vendor/six.py | 998 ++ .../pip/_vendor/tenacity/__init__.py | 608 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 27077 bytes .../__pycache__/_asyncio.cpython-312.pyc | Bin 0 -> 4797 bytes .../__pycache__/_utils.cpython-312.pyc | Bin 0 -> 2306 bytes .../__pycache__/after.cpython-312.pyc | Bin 0 -> 1615 bytes .../__pycache__/before.cpython-312.pyc | Bin 0 -> 1455 bytes .../__pycache__/before_sleep.cpython-312.pyc | Bin 0 -> 2293 bytes .../tenacity/__pycache__/nap.cpython-312.pyc | Bin 0 -> 1403 bytes .../__pycache__/retry.cpython-312.pyc | Bin 0 -> 14272 bytes .../tenacity/__pycache__/stop.cpython-312.pyc | Bin 0 -> 5559 bytes .../__pycache__/tornadoweb.cpython-312.pyc | Bin 0 -> 2577 bytes .../tenacity/__pycache__/wait.cpython-312.pyc | Bin 0 -> 12404 bytes .../pip/_vendor/tenacity/_asyncio.py | 94 + .../pip/_vendor/tenacity/_utils.py | 76 + .../pip/_vendor/tenacity/after.py | 51 + .../pip/_vendor/tenacity/before.py | 46 + .../pip/_vendor/tenacity/before_sleep.py | 71 + .../site-packages/pip/_vendor/tenacity/nap.py | 43 + .../pip/_vendor/tenacity/retry.py | 272 + .../pip/_vendor/tenacity/stop.py | 103 + .../pip/_vendor/tenacity/tornadoweb.py | 59 + .../pip/_vendor/tenacity/wait.py | 228 + .../pip/_vendor/tomli/__init__.py | 11 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 375 bytes .../tomli/__pycache__/_parser.cpython-312.pyc | Bin 0 -> 26918 bytes .../tomli/__pycache__/_re.cpython-312.pyc | Bin 0 -> 3899 bytes .../tomli/__pycache__/_types.cpython-312.pyc | Bin 0 -> 357 bytes .../pip/_vendor/tomli/_parser.py | 691 ++ .../site-packages/pip/_vendor/tomli/_re.py | 107 + .../site-packages/pip/_vendor/tomli/_types.py | 10 + .../pip/_vendor/truststore/__init__.py | 13 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 609 bytes .../__pycache__/_api.cpython-312.pyc | Bin 0 -> 15788 bytes .../__pycache__/_macos.cpython-312.pyc | Bin 0 -> 16653 bytes .../__pycache__/_openssl.cpython-312.pyc | Bin 0 -> 2206 bytes .../_ssl_constants.cpython-312.pyc | Bin 0 -> 1090 bytes .../__pycache__/_windows.cpython-312.pyc | Bin 0 -> 15497 bytes .../pip/_vendor/truststore/_api.py | 302 + .../pip/_vendor/truststore/_macos.py | 501 + .../pip/_vendor/truststore/_openssl.py | 66 + .../pip/_vendor/truststore/_ssl_constants.py | 31 + .../pip/_vendor/truststore/_windows.py | 554 ++ .../pip/_vendor/typing_extensions.py | 3072 ++++++ .../pip/_vendor/urllib3/__init__.py | 102 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3396 bytes .../__pycache__/_collections.cpython-312.pyc | Bin 0 -> 16479 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 209 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 20398 bytes .../connectionpool.cpython-312.pyc | Bin 0 -> 36433 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 13484 bytes .../__pycache__/fields.cpython-312.pyc | Bin 0 -> 10404 bytes .../__pycache__/filepost.cpython-312.pyc | Bin 0 -> 4009 bytes .../__pycache__/poolmanager.cpython-312.pyc | Bin 0 -> 20791 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 7285 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 33959 bytes .../pip/_vendor/urllib3/_collections.py | 355 + .../pip/_vendor/urllib3/_version.py | 2 + .../pip/_vendor/urllib3/connection.py | 572 ++ .../pip/_vendor/urllib3/connectionpool.py | 1137 +++ .../pip/_vendor/urllib3/contrib/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 189 bytes .../_appengine_environ.cpython-312.pyc | Bin 0 -> 1839 bytes .../__pycache__/appengine.cpython-312.pyc | Bin 0 -> 11555 bytes .../__pycache__/ntlmpool.cpython-312.pyc | Bin 0 -> 5710 bytes .../__pycache__/pyopenssl.cpython-312.pyc | Bin 0 -> 24441 bytes .../securetransport.cpython-312.pyc | Bin 0 -> 35547 bytes .../contrib/__pycache__/socks.cpython-312.pyc | Bin 0 -> 7502 bytes .../urllib3/contrib/_appengine_environ.py | 36 + .../contrib/_securetransport/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 206 bytes .../__pycache__/bindings.cpython-312.pyc | Bin 0 -> 17418 bytes .../__pycache__/low_level.cpython-312.pyc | Bin 0 -> 14792 bytes .../contrib/_securetransport/bindings.py | 519 + .../contrib/_securetransport/low_level.py | 397 + .../pip/_vendor/urllib3/contrib/appengine.py | 314 + .../pip/_vendor/urllib3/contrib/ntlmpool.py | 130 + .../pip/_vendor/urllib3/contrib/pyopenssl.py | 518 + .../urllib3/contrib/securetransport.py | 921 ++ .../pip/_vendor/urllib3/contrib/socks.py | 216 + .../pip/_vendor/urllib3/exceptions.py | 323 + .../pip/_vendor/urllib3/fields.py | 274 + .../pip/_vendor/urllib3/filepost.py | 98 + .../pip/_vendor/urllib3/packages/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 190 bytes .../packages/__pycache__/six.cpython-312.pyc | Bin 0 -> 41310 bytes .../urllib3/packages/backports/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 200 bytes .../__pycache__/makefile.cpython-312.pyc | Bin 0 -> 1816 bytes .../weakref_finalize.cpython-312.pyc | Bin 0 -> 7322 bytes .../urllib3/packages/backports/makefile.py | 51 + .../packages/backports/weakref_finalize.py | 155 + .../pip/_vendor/urllib3/packages/six.py | 1076 +++ .../pip/_vendor/urllib3/poolmanager.py | 556 ++ .../pip/_vendor/urllib3/request.py | 191 + .../pip/_vendor/urllib3/response.py | 879 ++ .../pip/_vendor/urllib3/util/__init__.py | 49 + .../util/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1137 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 4747 bytes .../util/__pycache__/proxy.cpython-312.pyc | Bin 0 -> 1543 bytes .../util/__pycache__/queue.cpython-312.pyc | Bin 0 -> 1343 bytes .../util/__pycache__/request.cpython-312.pyc | Bin 0 -> 4174 bytes .../util/__pycache__/response.cpython-312.pyc | Bin 0 -> 2980 bytes .../util/__pycache__/retry.cpython-312.pyc | Bin 0 -> 21709 bytes .../util/__pycache__/ssl_.cpython-312.pyc | Bin 0 -> 15094 bytes .../ssl_match_hostname.cpython-312.pyc | Bin 0 -> 5062 bytes .../__pycache__/ssltransport.cpython-312.pyc | Bin 0 -> 10763 bytes .../util/__pycache__/timeout.cpython-312.pyc | Bin 0 -> 11130 bytes .../util/__pycache__/url.cpython-312.pyc | Bin 0 -> 15786 bytes .../util/__pycache__/wait.cpython-312.pyc | Bin 0 -> 4394 bytes .../pip/_vendor/urllib3/util/connection.py | 149 + .../pip/_vendor/urllib3/util/proxy.py | 57 + .../pip/_vendor/urllib3/util/queue.py | 22 + .../pip/_vendor/urllib3/util/request.py | 137 + .../pip/_vendor/urllib3/util/response.py | 107 + .../pip/_vendor/urllib3/util/retry.py | 622 ++ .../pip/_vendor/urllib3/util/ssl_.py | 495 + .../urllib3/util/ssl_match_hostname.py | 159 + .../pip/_vendor/urllib3/util/ssltransport.py | 221 + .../pip/_vendor/urllib3/util/timeout.py | 271 + .../pip/_vendor/urllib3/util/url.py | 435 + .../pip/_vendor/urllib3/util/wait.py | 152 + .../site-packages/pip/_vendor/vendor.txt | 24 + .../pip/_vendor/webencodings/__init__.py | 342 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 11992 bytes .../__pycache__/labels.cpython-312.pyc | Bin 0 -> 7123 bytes .../__pycache__/mklabels.cpython-312.pyc | Bin 0 -> 2690 bytes .../__pycache__/tests.cpython-312.pyc | Bin 0 -> 9242 bytes .../x_user_defined.cpython-312.pyc | Bin 0 -> 3286 bytes .../pip/_vendor/webencodings/labels.py | 231 + .../pip/_vendor/webencodings/mklabels.py | 59 + .../pip/_vendor/webencodings/tests.py | 153 + .../_vendor/webencodings/x_user_defined.py | 325 + .../lib/python3.12/site-packages/pip/py.typed | 4 + .../INSTALLER | 1 + .../python_engineio-4.12.3.dist-info/METADATA | 49 + .../python_engineio-4.12.3.dist-info/RECORD | 58 + .../python_engineio-4.12.3.dist-info/WHEEL | 5 + .../licenses/LICENSE | 20 + .../top_level.txt | 1 + .../python_socketio-5.9.0.dist-info/INSTALLER | 1 + .../python_socketio-5.9.0.dist-info/LICENSE | 20 + .../python_socketio-5.9.0.dist-info/METADATA | 72 + .../python_socketio-5.9.0.dist-info/RECORD | 53 + .../python_socketio-5.9.0.dist-info/REQUESTED | 0 .../python_socketio-5.9.0.dist-info/WHEEL | 5 + .../top_level.txt | 1 + .../INSTALLER | 1 + .../simple_websocket-1.1.0.dist-info/LICENSE | 21 + .../simple_websocket-1.1.0.dist-info/METADATA | 37 + .../simple_websocket-1.1.0.dist-info/RECORD | 16 + .../simple_websocket-1.1.0.dist-info/WHEEL | 5 + .../top_level.txt | 1 + .../simple_websocket/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 368 bytes .../__pycache__/aiows.cpython-312.pyc | Bin 0 -> 24600 bytes .../__pycache__/asgi.cpython-312.pyc | Bin 0 -> 3252 bytes .../__pycache__/errors.cpython-312.pyc | Bin 0 -> 1569 bytes .../__pycache__/ws.cpython-312.pyc | Bin 0 -> 24779 bytes .../site-packages/simple_websocket/aiows.py | 467 + .../site-packages/simple_websocket/asgi.py | 50 + .../site-packages/simple_websocket/errors.py | 20 + .../site-packages/simple_websocket/ws.py | 488 + .../six-1.17.0.dist-info/INSTALLER | 1 + .../six-1.17.0.dist-info/LICENSE | 18 + .../six-1.17.0.dist-info/METADATA | 43 + .../site-packages/six-1.17.0.dist-info/RECORD | 8 + .../site-packages/six-1.17.0.dist-info/WHEEL | 6 + .../six-1.17.0.dist-info/top_level.txt | 1 + venv/lib/python3.12/site-packages/six.py | 1003 ++ .../site-packages/socketio/__init__.py | 36 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1481 bytes .../socketio/__pycache__/asgi.cpython-312.pyc | Bin 0 -> 2136 bytes .../asyncio_aiopika_manager.cpython-312.pyc | Bin 0 -> 8056 bytes .../asyncio_client.cpython-312.pyc | Bin 0 -> 32024 bytes .../asyncio_manager.cpython-312.pyc | Bin 0 -> 4613 bytes .../asyncio_namespace.cpython-312.pyc | Bin 0 -> 11212 bytes .../asyncio_pubsub_manager.cpython-312.pyc | Bin 0 -> 10314 bytes .../asyncio_redis_manager.cpython-312.pyc | Bin 0 -> 5939 bytes .../asyncio_server.cpython-312.pyc | Bin 0 -> 37743 bytes .../__pycache__/base_manager.cpython-312.pyc | Bin 0 -> 11667 bytes .../__pycache__/client.cpython-312.pyc | Bin 0 -> 38074 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 1832 bytes .../__pycache__/kafka_manager.cpython-312.pyc | Bin 0 -> 3733 bytes .../__pycache__/kombu_manager.cpython-312.pyc | Bin 0 -> 7724 bytes .../__pycache__/middleware.cpython-312.pyc | Bin 0 -> 2279 bytes .../msgpack_packet.cpython-312.pyc | Bin 0 -> 1224 bytes .../__pycache__/namespace.cpython-312.pyc | Bin 0 -> 11648 bytes .../__pycache__/packet.cpython-312.pyc | Bin 0 -> 8820 bytes .../pubsub_manager.cpython-312.pyc | Bin 0 -> 8857 bytes .../__pycache__/redis_manager.cpython-312.pyc | Bin 0 -> 6152 bytes .../__pycache__/server.cpython-312.pyc | Bin 0 -> 44751 bytes .../__pycache__/tornado.cpython-312.pyc | Bin 0 -> 625 bytes .../__pycache__/zmq_manager.cpython-312.pyc | Bin 0 -> 4892 bytes .../python3.12/site-packages/socketio/asgi.py | 42 + .../socketio/asyncio_aiopika_manager.py | 126 + .../site-packages/socketio/asyncio_client.py | 549 ++ .../site-packages/socketio/asyncio_manager.py | 98 + .../socketio/asyncio_namespace.py | 231 + .../socketio/asyncio_pubsub_manager.py | 194 + .../socketio/asyncio_redis_manager.py | 107 + .../site-packages/socketio/asyncio_server.py | 610 ++ .../site-packages/socketio/base_manager.py | 235 + .../site-packages/socketio/client.py | 732 ++ .../site-packages/socketio/exceptions.py | 34 + .../site-packages/socketio/kafka_manager.py | 67 + .../site-packages/socketio/kombu_manager.py | 136 + .../site-packages/socketio/middleware.py | 42 + .../site-packages/socketio/msgpack_packet.py | 18 + .../site-packages/socketio/namespace.py | 214 + .../site-packages/socketio/packet.py | 190 + .../site-packages/socketio/pubsub_manager.py | 181 + .../site-packages/socketio/redis_manager.py | 117 + .../site-packages/socketio/server.py | 805 ++ .../site-packages/socketio/tornado.py | 11 + .../site-packages/socketio/zmq_manager.py | 107 + .../werkzeug-3.1.3.dist-info/INSTALLER | 1 + .../werkzeug-3.1.3.dist-info/LICENSE.txt | 28 + .../werkzeug-3.1.3.dist-info/METADATA | 99 + .../werkzeug-3.1.3.dist-info/RECORD | 116 + .../werkzeug-3.1.3.dist-info/WHEEL | 4 + .../site-packages/werkzeug/__init__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 324 bytes .../__pycache__/_internal.cpython-312.pyc | Bin 0 -> 9744 bytes .../__pycache__/_reloader.cpython-312.pyc | Bin 0 -> 20595 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 33310 bytes .../__pycache__/formparser.cpython-312.pyc | Bin 0 -> 17010 bytes .../werkzeug/__pycache__/http.cpython-312.pyc | Bin 0 -> 50235 bytes .../__pycache__/local.cpython-312.pyc | Bin 0 -> 28465 bytes .../__pycache__/security.cpython-312.pyc | Bin 0 -> 7118 bytes .../__pycache__/serving.cpython-312.pyc | Bin 0 -> 46112 bytes .../werkzeug/__pycache__/test.cpython-312.pyc | Bin 0 -> 59854 bytes .../__pycache__/testapp.cpython-312.pyc | Bin 0 -> 8875 bytes .../werkzeug/__pycache__/urls.cpython-312.pyc | Bin 0 -> 8254 bytes .../__pycache__/user_agent.cpython-312.pyc | Bin 0 -> 2137 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 28128 bytes .../werkzeug/__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 25200 bytes .../site-packages/werkzeug/_internal.py | 211 + .../site-packages/werkzeug/_reloader.py | 471 + .../werkzeug/datastructures/__init__.py | 64 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2401 bytes .../__pycache__/accept.cpython-312.pyc | Bin 0 -> 15928 bytes .../__pycache__/auth.cpython-312.pyc | Bin 0 -> 14443 bytes .../__pycache__/cache_control.cpython-312.pyc | Bin 0 -> 12210 bytes .../__pycache__/csp.cpython-312.pyc | Bin 0 -> 6171 bytes .../__pycache__/etag.cpython-312.pyc | Bin 0 -> 5386 bytes .../__pycache__/file_storage.cpython-312.pyc | Bin 0 -> 8808 bytes .../__pycache__/headers.cpython-312.pyc | Bin 0 -> 30504 bytes .../__pycache__/mixins.cpython-312.pyc | Bin 0 -> 16384 bytes .../__pycache__/range.cpython-312.pyc | Bin 0 -> 10039 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 59075 bytes .../werkzeug/datastructures/accept.py | 350 + .../werkzeug/datastructures/auth.py | 317 + .../werkzeug/datastructures/cache_control.py | 273 + .../werkzeug/datastructures/csp.py | 100 + .../werkzeug/datastructures/etag.py | 106 + .../werkzeug/datastructures/file_storage.py | 209 + .../werkzeug/datastructures/headers.py | 662 ++ .../werkzeug/datastructures/mixins.py | 317 + .../werkzeug/datastructures/range.py | 214 + .../werkzeug/datastructures/structures.py | 1239 +++ .../site-packages/werkzeug/debug/__init__.py | 565 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 23460 bytes .../debug/__pycache__/console.cpython-312.pyc | Bin 0 -> 11621 bytes .../debug/__pycache__/repr.cpython-312.pyc | Bin 0 -> 13794 bytes .../debug/__pycache__/tbtools.cpython-312.pyc | Bin 0 -> 16993 bytes .../site-packages/werkzeug/debug/console.py | 219 + .../site-packages/werkzeug/debug/repr.py | 282 + .../werkzeug/debug/shared/ICON_LICENSE.md | 6 + .../werkzeug/debug/shared/console.png | Bin 0 -> 507 bytes .../werkzeug/debug/shared/debugger.js | 344 + .../werkzeug/debug/shared/less.png | Bin 0 -> 191 bytes .../werkzeug/debug/shared/more.png | Bin 0 -> 200 bytes .../werkzeug/debug/shared/style.css | 150 + .../site-packages/werkzeug/debug/tbtools.py | 450 + .../site-packages/werkzeug/exceptions.py | 894 ++ .../site-packages/werkzeug/formparser.py | 430 + .../python3.12/site-packages/werkzeug/http.py | 1405 +++ .../site-packages/werkzeug/local.py | 653 ++ .../werkzeug/middleware/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 181 bytes .../__pycache__/dispatcher.cpython-312.pyc | Bin 0 -> 3299 bytes .../__pycache__/http_proxy.cpython-312.pyc | Bin 0 -> 9391 bytes .../__pycache__/lint.cpython-312.pyc | Bin 0 -> 17761 bytes .../__pycache__/profiler.cpython-312.pyc | Bin 0 -> 7185 bytes .../__pycache__/proxy_fix.cpython-312.pyc | Bin 0 -> 7182 bytes .../__pycache__/shared_data.cpython-312.pyc | Bin 0 -> 12737 bytes .../werkzeug/middleware/dispatcher.py | 81 + .../werkzeug/middleware/http_proxy.py | 236 + .../site-packages/werkzeug/middleware/lint.py | 439 + .../werkzeug/middleware/profiler.py | 155 + .../werkzeug/middleware/proxy_fix.py | 183 + .../werkzeug/middleware/shared_data.py | 283 + .../site-packages/werkzeug/py.typed | 0 .../werkzeug/routing/__init__.py | 134 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4654 bytes .../__pycache__/converters.cpython-312.pyc | Bin 0 -> 10894 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7897 bytes .../routing/__pycache__/map.cpython-312.pyc | Bin 0 -> 39822 bytes .../__pycache__/matcher.cpython-312.pyc | Bin 0 -> 8265 bytes .../routing/__pycache__/rules.cpython-312.pyc | Bin 0 -> 39156 bytes .../werkzeug/routing/converters.py | 261 + .../werkzeug/routing/exceptions.py | 152 + .../site-packages/werkzeug/routing/map.py | 951 ++ .../site-packages/werkzeug/routing/matcher.py | 202 + .../site-packages/werkzeug/routing/rules.py | 928 ++ .../site-packages/werkzeug/sansio/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 177 bytes .../sansio/__pycache__/http.cpython-312.pyc | Bin 0 -> 5628 bytes .../__pycache__/multipart.cpython-312.pyc | Bin 0 -> 14036 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 21870 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 31725 bytes .../sansio/__pycache__/utils.cpython-312.pyc | Bin 0 -> 6167 bytes .../site-packages/werkzeug/sansio/http.py | 170 + .../werkzeug/sansio/multipart.py | 323 + .../site-packages/werkzeug/sansio/request.py | 534 + .../site-packages/werkzeug/sansio/response.py | 763 ++ .../site-packages/werkzeug/sansio/utils.py | 167 + .../site-packages/werkzeug/security.py | 166 + .../site-packages/werkzeug/serving.py | 1125 +++ .../python3.12/site-packages/werkzeug/test.py | 1464 +++ .../site-packages/werkzeug/testapp.py | 194 + .../python3.12/site-packages/werkzeug/urls.py | 203 + .../site-packages/werkzeug/user_agent.py | 47 + .../site-packages/werkzeug/utils.py | 691 ++ .../werkzeug/wrappers/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 301 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 26131 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 34556 bytes .../werkzeug/wrappers/request.py | 650 ++ .../werkzeug/wrappers/response.py | 831 ++ .../python3.12/site-packages/werkzeug/wsgi.py | 595 ++ .../wsproto-1.2.0.dist-info/INSTALLER | 1 + .../wsproto-1.2.0.dist-info/LICENSE | 21 + .../wsproto-1.2.0.dist-info/METADATA | 177 + .../wsproto-1.2.0.dist-info/RECORD | 23 + .../wsproto-1.2.0.dist-info/WHEEL | 5 + .../wsproto-1.2.0.dist-info/top_level.txt | 1 + .../site-packages/wsproto/__init__.py | 94 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4359 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 8666 bytes .../__pycache__/events.cpython-312.pyc | Bin 0 -> 10515 bytes .../__pycache__/extensions.cpython-312.pyc | Bin 0 -> 13713 bytes .../frame_protocol.cpython-312.pyc | Bin 0 -> 26268 bytes .../__pycache__/handshake.cpython-312.pyc | Bin 0 -> 20916 bytes .../__pycache__/typing.cpython-312.pyc | Bin 0 -> 276 bytes .../__pycache__/utilities.cpython-312.pyc | Bin 0 -> 3707 bytes .../site-packages/wsproto/connection.py | 189 + .../site-packages/wsproto/events.py | 295 + .../site-packages/wsproto/extensions.py | 315 + .../site-packages/wsproto/frame_protocol.py | 673 ++ .../site-packages/wsproto/handshake.py | 491 + .../python3.12/site-packages/wsproto/py.typed | 1 + .../site-packages/wsproto/typing.py | 3 + .../site-packages/wsproto/utilities.py | 88 + venv/lib64 | 1 + venv/pyvenv.cfg | 5 + 2179 files changed, 340188 insertions(+), 1 deletion(-) mode change 100644 => 100755 setup.sh rename main.js => static/main.js (100%) rename styles.css => static/styles.css (100%) rename index.html => templates/index.html (100%) create mode 100644 venv/bin/Activate.ps1 create mode 100644 venv/bin/activate create mode 100644 venv/bin/activate.csh create mode 100644 venv/bin/activate.fish create mode 100755 venv/bin/flask create mode 100755 venv/bin/pip create mode 100755 venv/bin/pip3 create mode 100755 venv/bin/pip3.12 create mode 120000 venv/bin/python create mode 120000 venv/bin/python3 create mode 120000 venv/bin/python3.12 create mode 100644 venv/include/site/python3.12/greenlet/greenlet.h create mode 100644 venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/LICENSE create mode 100644 venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/__pycache__/six.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/LICENSE create mode 100644 venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/bidict/__init__.py create mode 100644 venv/lib/python3.12/site-packages/bidict/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/bidict/__pycache__/_abc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/bidict/__pycache__/_base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/bidict/__pycache__/_bidict.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/bidict/__pycache__/_dup.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/bidict/__pycache__/_exc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/bidict/__pycache__/_frozen.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/bidict/__pycache__/_iter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/bidict/__pycache__/_orderedbase.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/bidict/__pycache__/_orderedbidict.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/bidict/__pycache__/_typing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/bidict/__pycache__/metadata.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/bidict/_abc.py create mode 100644 venv/lib/python3.12/site-packages/bidict/_base.py create mode 100644 venv/lib/python3.12/site-packages/bidict/_bidict.py create mode 100644 venv/lib/python3.12/site-packages/bidict/_dup.py create mode 100644 venv/lib/python3.12/site-packages/bidict/_exc.py create mode 100644 venv/lib/python3.12/site-packages/bidict/_frozen.py create mode 100644 venv/lib/python3.12/site-packages/bidict/_iter.py create mode 100644 venv/lib/python3.12/site-packages/bidict/_orderedbase.py create mode 100644 venv/lib/python3.12/site-packages/bidict/_orderedbidict.py create mode 100644 venv/lib/python3.12/site-packages/bidict/_typing.py create mode 100644 venv/lib/python3.12/site-packages/bidict/metadata.py create mode 100644 venv/lib/python3.12/site-packages/bidict/py.typed create mode 100644 venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/blinker/__init__.py create mode 100644 venv/lib/python3.12/site-packages/blinker/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/blinker/__pycache__/_utilities.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/blinker/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/blinker/_utilities.py create mode 100644 venv/lib/python3.12/site-packages/blinker/base.py create mode 100644 venv/lib/python3.12/site-packages/blinker/py.typed create mode 100644 venv/lib/python3.12/site-packages/click-8.3.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/click-8.3.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/click-8.3.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/click-8.3.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/click-8.3.0.dist-info/licenses/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/click/__init__.py create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/_textwrap.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/_winconsole.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/shell_completion.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/testing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/_compat.py create mode 100644 venv/lib/python3.12/site-packages/click/_termui_impl.py create mode 100644 venv/lib/python3.12/site-packages/click/_textwrap.py create mode 100644 venv/lib/python3.12/site-packages/click/_utils.py create mode 100644 venv/lib/python3.12/site-packages/click/_winconsole.py create mode 100644 venv/lib/python3.12/site-packages/click/core.py create mode 100644 venv/lib/python3.12/site-packages/click/decorators.py create mode 100644 venv/lib/python3.12/site-packages/click/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/click/formatting.py create mode 100644 venv/lib/python3.12/site-packages/click/globals.py create mode 100644 venv/lib/python3.12/site-packages/click/parser.py create mode 100644 venv/lib/python3.12/site-packages/click/py.typed create mode 100644 venv/lib/python3.12/site-packages/click/shell_completion.py create mode 100644 venv/lib/python3.12/site-packages/click/termui.py create mode 100644 venv/lib/python3.12/site-packages/click/testing.py create mode 100644 venv/lib/python3.12/site-packages/click/types.py create mode 100644 venv/lib/python3.12/site-packages/click/utils.py create mode 100644 venv/lib/python3.12/site-packages/dns/__init__.py create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/_asyncbackend.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/_asyncio_backend.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/_ddr.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/_features.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/_immutable_ctx.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/_no_ssl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/_tls_util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/_trio_backend.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/asyncbackend.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/asyncquery.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/asyncresolver.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/btree.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/btreezone.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/dnssec.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/dnssectypes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/e164.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/edns.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/entropy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/enum.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/exception.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/flags.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/grange.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/immutable.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/inet.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/ipv4.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/ipv6.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/message.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/name.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/namedict.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/nameserver.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/node.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/opcode.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/query.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/rcode.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/rdata.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/rdataclass.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/rdataset.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/rdatatype.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/renderer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/resolver.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/reversename.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/rrset.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/serial.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/set.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/tokenizer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/transaction.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/tsig.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/tsigkeyring.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/ttl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/update.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/versioned.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/win32util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/wire.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/xfr.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/zone.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/zonefile.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/__pycache__/zonetypes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/_asyncbackend.py create mode 100644 venv/lib/python3.12/site-packages/dns/_asyncio_backend.py create mode 100644 venv/lib/python3.12/site-packages/dns/_ddr.py create mode 100644 venv/lib/python3.12/site-packages/dns/_features.py create mode 100644 venv/lib/python3.12/site-packages/dns/_immutable_ctx.py create mode 100644 venv/lib/python3.12/site-packages/dns/_no_ssl.py create mode 100644 venv/lib/python3.12/site-packages/dns/_tls_util.py create mode 100644 venv/lib/python3.12/site-packages/dns/_trio_backend.py create mode 100644 venv/lib/python3.12/site-packages/dns/asyncbackend.py create mode 100644 venv/lib/python3.12/site-packages/dns/asyncquery.py create mode 100644 venv/lib/python3.12/site-packages/dns/asyncresolver.py create mode 100644 venv/lib/python3.12/site-packages/dns/btree.py create mode 100644 venv/lib/python3.12/site-packages/dns/btreezone.py create mode 100644 venv/lib/python3.12/site-packages/dns/dnssec.py create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/__init__.py create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/cryptography.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/dsa.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/ecdsa.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/eddsa.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/rsa.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/base.py create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/cryptography.py create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/dsa.py create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/ecdsa.py create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/eddsa.py create mode 100644 venv/lib/python3.12/site-packages/dns/dnssecalgs/rsa.py create mode 100644 venv/lib/python3.12/site-packages/dns/dnssectypes.py create mode 100644 venv/lib/python3.12/site-packages/dns/e164.py create mode 100644 venv/lib/python3.12/site-packages/dns/edns.py create mode 100644 venv/lib/python3.12/site-packages/dns/entropy.py create mode 100644 venv/lib/python3.12/site-packages/dns/enum.py create mode 100644 venv/lib/python3.12/site-packages/dns/exception.py create mode 100644 venv/lib/python3.12/site-packages/dns/flags.py create mode 100644 venv/lib/python3.12/site-packages/dns/grange.py create mode 100644 venv/lib/python3.12/site-packages/dns/immutable.py create mode 100644 venv/lib/python3.12/site-packages/dns/inet.py create mode 100644 venv/lib/python3.12/site-packages/dns/ipv4.py create mode 100644 venv/lib/python3.12/site-packages/dns/ipv6.py create mode 100644 venv/lib/python3.12/site-packages/dns/message.py create mode 100644 venv/lib/python3.12/site-packages/dns/name.py create mode 100644 venv/lib/python3.12/site-packages/dns/namedict.py create mode 100644 venv/lib/python3.12/site-packages/dns/nameserver.py create mode 100644 venv/lib/python3.12/site-packages/dns/node.py create mode 100644 venv/lib/python3.12/site-packages/dns/opcode.py create mode 100644 venv/lib/python3.12/site-packages/dns/py.typed create mode 100644 venv/lib/python3.12/site-packages/dns/query.py create mode 100644 venv/lib/python3.12/site-packages/dns/quic/__init__.py create mode 100644 venv/lib/python3.12/site-packages/dns/quic/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/quic/__pycache__/_asyncio.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/quic/__pycache__/_common.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/quic/__pycache__/_sync.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/quic/__pycache__/_trio.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/quic/_asyncio.py create mode 100644 venv/lib/python3.12/site-packages/dns/quic/_common.py create mode 100644 venv/lib/python3.12/site-packages/dns/quic/_sync.py create mode 100644 venv/lib/python3.12/site-packages/dns/quic/_trio.py create mode 100644 venv/lib/python3.12/site-packages/dns/rcode.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdata.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdataclass.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdataset.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdatatype.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/AFSDB.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/AMTRELAY.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/AVC.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CAA.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CDNSKEY.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CDS.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CERT.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CNAME.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CSYNC.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DLV.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DNAME.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DNSKEY.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DS.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DSYNC.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/EUI48.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/EUI64.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/GPOS.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/HINFO.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/HIP.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/ISDN.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/L32.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/L64.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/LOC.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/LP.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/MX.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NID.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NINFO.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NS.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NSEC.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NSEC3.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NSEC3PARAM.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/OPENPGPKEY.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/OPT.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/PTR.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RESINFO.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RP.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RRSIG.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RT.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SMIMEA.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SOA.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SPF.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SSHFP.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TKEY.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TLSA.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TSIG.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TXT.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/URI.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/WALLET.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/X25.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/ZONEMD.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__init__.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/AFSDB.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/AMTRELAY.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/AVC.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/CAA.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/CDNSKEY.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/CDS.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/CERT.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/CNAME.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/CSYNC.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/DLV.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/DNAME.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/DNSKEY.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/DS.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/DSYNC.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/EUI48.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/EUI64.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/GPOS.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/HINFO.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/HIP.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/ISDN.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/L32.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/L64.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/LOC.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/LP.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/MX.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NID.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NINFO.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NS.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NSEC.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NSEC3.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NSEC3PARAM.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/OPENPGPKEY.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/OPT.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/PTR.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/RESINFO.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/RP.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/RRSIG.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/RT.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/SMIMEA.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/SOA.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/SPF.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/SSHFP.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/TKEY.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/TLSA.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/TSIG.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/TXT.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/URI.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/WALLET.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/X25.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/ZONEMD.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/CH/A.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/CH/__init__.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/CH/__pycache__/A.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/CH/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/A.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/AAAA.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/APL.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/DHCID.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/HTTPS.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/IPSECKEY.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/KX.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/NAPTR.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/NSAP.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/NSAP_PTR.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/PX.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/SRV.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/SVCB.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/WKS.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__init__.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/A.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/AAAA.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/APL.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/DHCID.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/HTTPS.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/IPSECKEY.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/KX.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/NAPTR.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/NSAP.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/NSAP_PTR.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/PX.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/SRV.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/SVCB.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/WKS.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/__init__.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/dnskeybase.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/dsbase.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/euibase.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/mxbase.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/nsbase.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/svcbbase.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/tlsabase.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/txtbase.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/dnskeybase.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/dsbase.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/euibase.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/mxbase.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/nsbase.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/svcbbase.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/tlsabase.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/txtbase.py create mode 100644 venv/lib/python3.12/site-packages/dns/rdtypes/util.py create mode 100644 venv/lib/python3.12/site-packages/dns/renderer.py create mode 100644 venv/lib/python3.12/site-packages/dns/resolver.py create mode 100644 venv/lib/python3.12/site-packages/dns/reversename.py create mode 100644 venv/lib/python3.12/site-packages/dns/rrset.py create mode 100644 venv/lib/python3.12/site-packages/dns/serial.py create mode 100644 venv/lib/python3.12/site-packages/dns/set.py create mode 100644 venv/lib/python3.12/site-packages/dns/tokenizer.py create mode 100644 venv/lib/python3.12/site-packages/dns/transaction.py create mode 100644 venv/lib/python3.12/site-packages/dns/tsig.py create mode 100644 venv/lib/python3.12/site-packages/dns/tsigkeyring.py create mode 100644 venv/lib/python3.12/site-packages/dns/ttl.py create mode 100644 venv/lib/python3.12/site-packages/dns/update.py create mode 100644 venv/lib/python3.12/site-packages/dns/version.py create mode 100644 venv/lib/python3.12/site-packages/dns/versioned.py create mode 100644 venv/lib/python3.12/site-packages/dns/win32util.py create mode 100644 venv/lib/python3.12/site-packages/dns/wire.py create mode 100644 venv/lib/python3.12/site-packages/dns/xfr.py create mode 100644 venv/lib/python3.12/site-packages/dns/zone.py create mode 100644 venv/lib/python3.12/site-packages/dns/zonefile.py create mode 100644 venv/lib/python3.12/site-packages/dns/zonetypes.py create mode 100644 venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/engineio/__init__.py create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/async_client.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/async_server.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/async_socket.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/base_client.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/base_server.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/base_socket.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/client.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/middleware.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/packet.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/payload.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/server.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/socket.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/__pycache__/static_files.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/async_client.py create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/__init__.py create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/_websocket_wsgi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/aiohttp.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/asgi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/eventlet.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/gevent.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/gevent_uwsgi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/sanic.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/threading.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/tornado.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/_websocket_wsgi.py create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/aiohttp.py create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/asgi.py create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/eventlet.py create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/gevent.py create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/gevent_uwsgi.py create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/sanic.py create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/threading.py create mode 100644 venv/lib/python3.12/site-packages/engineio/async_drivers/tornado.py create mode 100644 venv/lib/python3.12/site-packages/engineio/async_server.py create mode 100644 venv/lib/python3.12/site-packages/engineio/async_socket.py create mode 100644 venv/lib/python3.12/site-packages/engineio/base_client.py create mode 100644 venv/lib/python3.12/site-packages/engineio/base_server.py create mode 100644 venv/lib/python3.12/site-packages/engineio/base_socket.py create mode 100644 venv/lib/python3.12/site-packages/engineio/client.py create mode 100644 venv/lib/python3.12/site-packages/engineio/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/engineio/json.py create mode 100644 venv/lib/python3.12/site-packages/engineio/middleware.py create mode 100644 venv/lib/python3.12/site-packages/engineio/packet.py create mode 100644 venv/lib/python3.12/site-packages/engineio/payload.py create mode 100644 venv/lib/python3.12/site-packages/engineio/server.py create mode 100644 venv/lib/python3.12/site-packages/engineio/socket.py create mode 100644 venv/lib/python3.12/site-packages/engineio/static_files.py create mode 100644 venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/AUTHORS create mode 100644 venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/LICENSE create mode 100644 venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/eventlet/__init__.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/backdoor.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/convenience.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/corolocal.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/coros.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/dagpool.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/db_pool.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/debug.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/event.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/greenpool.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/greenthread.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/lock.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/patcher.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/pools.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/queue.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/semaphore.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/timeout.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/tpool.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/websocket.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/__pycache__/wsgi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/backdoor.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/convenience.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/corolocal.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/coros.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/dagpool.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/db_pool.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/debug.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/event.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/BaseHTTPServer.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/CGIHTTPServer.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/MySQLdb.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/SSL.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__init__.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__pycache__/SSL.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__pycache__/crypto.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__pycache__/tsafe.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/crypto.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/tsafe.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/version.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/Queue.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/SimpleHTTPServer.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/SocketServer.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__init__.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/BaseHTTPServer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/CGIHTTPServer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/MySQLdb.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/Queue.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/SimpleHTTPServer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/SocketServer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/_socket_nodns.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/asynchat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/asyncore.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/builtin.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/ftplib.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/httplib.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/os.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/profile.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/select.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/selectors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/socket.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/ssl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/subprocess.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/thread.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/threading.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/time.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/urllib2.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/__pycache__/zmq.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/_socket_nodns.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/asynchat.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/asyncore.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/builtin.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/ftplib.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/http/__init__.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/http/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/http/__pycache__/client.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/http/__pycache__/cookiejar.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/http/__pycache__/cookies.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/http/__pycache__/server.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/http/client.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/http/cookiejar.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/http/cookies.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/http/server.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/httplib.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/os.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/profile.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/select.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/selectors.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/socket.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/ssl.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/subprocess.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/thread.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/threading.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/time.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/urllib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/urllib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/urllib/__pycache__/error.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/urllib/__pycache__/parse.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/urllib/__pycache__/request.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/urllib/__pycache__/response.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/urllib/error.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/urllib/parse.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/urllib/request.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/urllib/response.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/urllib2.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/green/zmq.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/greenio/__init__.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/greenio/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/greenio/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/greenio/__pycache__/py2.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/greenio/__pycache__/py3.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/greenio/base.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/greenio/py2.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/greenio/py3.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/greenpool.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/greenthread.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/__init__.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/epolls.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/hub.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/kqueue.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/poll.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/pyevent.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/selects.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/timer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/epolls.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/hub.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/kqueue.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/poll.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/pyevent.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/selects.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/hubs/timer.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/lock.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/patcher.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/pools.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/queue.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/semaphore.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/__init__.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/__pycache__/greendns.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/__pycache__/greenlets.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/__pycache__/psycopg2_patcher.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/__pycache__/pylib.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/__pycache__/stacklesspypys.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/__pycache__/stacklesss.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/greendns.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/greenlets.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/psycopg2_patcher.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/pylib.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/stacklesspypys.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/support/stacklesss.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/timeout.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/tpool.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/websocket.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/wsgi.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/__init__.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/client.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/greenthread.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/http.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/log.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/patcher.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/wsgi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/__init__.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/__init__.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/__pycache__/constants.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/__pycache__/ttypes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/constants.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/ttypes.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/api.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/client.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/greenthread.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/http.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/log.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/patcher.py create mode 100644 venv/lib/python3.12/site-packages/eventlet/zipkin/wsgi.py create mode 100644 venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/LICENSE.rst create mode 100644 venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/flask/__init__.py create mode 100644 venv/lib/python3.12/site-packages/flask/__main__.py create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/app.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/blueprints.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/cli.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/config.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/ctx.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/globals.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/helpers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/logging.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/scaffold.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/sessions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/signals.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/templating.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/testing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/typing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/views.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/__pycache__/wrappers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/app.py create mode 100644 venv/lib/python3.12/site-packages/flask/blueprints.py create mode 100644 venv/lib/python3.12/site-packages/flask/cli.py create mode 100644 venv/lib/python3.12/site-packages/flask/config.py create mode 100644 venv/lib/python3.12/site-packages/flask/ctx.py create mode 100644 venv/lib/python3.12/site-packages/flask/debughelpers.py create mode 100644 venv/lib/python3.12/site-packages/flask/globals.py create mode 100644 venv/lib/python3.12/site-packages/flask/helpers.py create mode 100644 venv/lib/python3.12/site-packages/flask/json/__init__.py create mode 100644 venv/lib/python3.12/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/json/__pycache__/provider.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/json/__pycache__/tag.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask/json/provider.py create mode 100644 venv/lib/python3.12/site-packages/flask/json/tag.py create mode 100644 venv/lib/python3.12/site-packages/flask/logging.py create mode 100644 venv/lib/python3.12/site-packages/flask/py.typed create mode 100644 venv/lib/python3.12/site-packages/flask/scaffold.py create mode 100644 venv/lib/python3.12/site-packages/flask/sessions.py create mode 100644 venv/lib/python3.12/site-packages/flask/signals.py create mode 100644 venv/lib/python3.12/site-packages/flask/templating.py create mode 100644 venv/lib/python3.12/site-packages/flask/testing.py create mode 100644 venv/lib/python3.12/site-packages/flask/typing.py create mode 100644 venv/lib/python3.12/site-packages/flask/views.py create mode 100644 venv/lib/python3.12/site-packages/flask/wrappers.py create mode 100644 venv/lib/python3.12/site-packages/flask_socketio/__init__.py create mode 100644 venv/lib/python3.12/site-packages/flask_socketio/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask_socketio/__pycache__/namespace.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask_socketio/__pycache__/test_client.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/flask_socketio/namespace.py create mode 100644 venv/lib/python3.12/site-packages/flask_socketio/test_client.py create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/licenses/LICENSE.PSF create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/greenlet/CObjects.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/PyGreenlet.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/PyGreenlet.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/PyGreenletUnswitchable.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/PyModule.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TBrokenGreenlet.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TExceptionState.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TGreenlet.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TGreenlet.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TGreenletGlobals.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TMainGreenlet.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TPythonState.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TStackState.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TThreadState.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TThreadStateCreator.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TThreadStateDestroy.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TUserGreenlet.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/__init__.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc create mode 100755 venv/lib/python3.12/site-packages/greenlet/_greenlet.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_allocator.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_compiler_compat.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_cpython_compat.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_exceptions.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_internal.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_msvc_compat.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_refs.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_slp_switch.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_thread_support.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/__init__.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/setup_switch_x64_masm.cmd create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_aarch64_gcc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_alpha_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_amd64_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_arm32_gcc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_arm32_ios.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_masm.asm create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_masm.obj create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_msvc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_csky_gcc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_loongarch64_linux.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_m68k_gcc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_mips_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc64_aix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc64_linux.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_aix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_linux.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_macosx.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_riscv_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_s390_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_sh_gcc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_sparc_sun_gcc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_x32_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_masm.asm create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_masm.obj create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_msvc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_x86_msvc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_x86_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/slp_platformselect.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__init__.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_cpp_exception.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_slp_switch.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/leakcheck.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_contextvars.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_cpp.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_extension_interface.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_gc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_generator.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_generator_nested.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_greenlet.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_greenlet_trash.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_leaks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_stack_saved.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_throw.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_tracing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_weakref.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/_test_extension.c create mode 100755 venv/lib/python3.12/site-packages/greenlet/tests/_test_extension.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/_test_extension_cpp.cpp create mode 100755 venv/lib/python3.12/site-packages/greenlet/tests/_test_extension_cpp.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_clearing_run_switches.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_cpp_exception.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_initialstub_already_started.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_slp_switch.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_three_greenlets.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_three_greenlets2.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_two_greenlets.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/leakcheck.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_contextvars.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_cpp.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_extension_interface.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_gc.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_generator.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_generator_nested.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_greenlet.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_greenlet_trash.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_leaks.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_stack_saved.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_throw.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_tracing.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_version.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_weakref.py create mode 100644 venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/h11/__init__.py create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_abnf.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_events.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_headers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_readers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_receivebuffer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_state.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_writers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/_abnf.py create mode 100644 venv/lib/python3.12/site-packages/h11/_connection.py create mode 100644 venv/lib/python3.12/site-packages/h11/_events.py create mode 100644 venv/lib/python3.12/site-packages/h11/_headers.py create mode 100644 venv/lib/python3.12/site-packages/h11/_readers.py create mode 100644 venv/lib/python3.12/site-packages/h11/_receivebuffer.py create mode 100644 venv/lib/python3.12/site-packages/h11/_state.py create mode 100644 venv/lib/python3.12/site-packages/h11/_util.py create mode 100644 venv/lib/python3.12/site-packages/h11/_version.py create mode 100644 venv/lib/python3.12/site-packages/h11/_writers.py create mode 100644 venv/lib/python3.12/site-packages/h11/py.typed create mode 100644 venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/__init__.py create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/__pycache__/_json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/__pycache__/encoding.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/__pycache__/exc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/__pycache__/serializer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/__pycache__/signer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/__pycache__/timed.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/__pycache__/url_safe.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/_json.py create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/encoding.py create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/exc.py create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/py.typed create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/serializer.py create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/signer.py create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/timed.py create mode 100644 venv/lib/python3.12/site-packages/itsdangerous/url_safe.py create mode 100644 venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/licenses/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/jinja2/__init__.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/_identifier.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/async_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/bccache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/compiler.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/constants.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/debug.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/defaults.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/environment.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/ext.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/filters.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/idtracking.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/lexer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/loaders.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/meta.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/nativetypes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/nodes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/optimizer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/parser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/runtime.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/sandbox.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/tests.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/__pycache__/visitor.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/jinja2/_identifier.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/async_utils.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/bccache.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/compiler.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/constants.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/debug.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/defaults.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/environment.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/ext.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/filters.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/idtracking.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/lexer.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/loaders.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/meta.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/nativetypes.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/nodes.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/optimizer.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/parser.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/py.typed create mode 100644 venv/lib/python3.12/site-packages/jinja2/runtime.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/sandbox.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/tests.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/utils.py create mode 100644 venv/lib/python3.12/site-packages/jinja2/visitor.py create mode 100644 venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/licenses/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/markupsafe/__init__.py create mode 100644 venv/lib/python3.12/site-packages/markupsafe/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/markupsafe/__pycache__/_native.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/markupsafe/_native.py create mode 100644 venv/lib/python3.12/site-packages/markupsafe/_speedups.c create mode 100755 venv/lib/python3.12/site-packages/markupsafe/_speedups.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/markupsafe/_speedups.pyi create mode 100644 venv/lib/python3.12/site-packages/markupsafe/py.typed create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/AUTHORS.txt create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/pip-24.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/pip/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/__pip-runner__.py create mode 100644 venv/lib/python3.12/site-packages/pip/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/__pycache__/__pip-runner__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/build_env.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/configuration.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/main.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/pyproject.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/self_outdated_check.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/wheel_builder.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/build_env.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/autocompletion.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/base_command.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/cmdoptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/command_context.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main_parser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/parser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/progress_bars.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/req_command.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/spinners.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/status_codes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/autocompletion.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/base_command.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/cmdoptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/command_context.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/main.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/main_parser.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/parser.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/progress_bars.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/req_command.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/spinners.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/status_codes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/check.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/completion.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/debug.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/download.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/freeze.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/hash.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/help.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/index.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/inspect.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/install.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/list.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/search.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/show.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/check.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/completion.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/configuration.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/debug.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/download.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/freeze.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/hash.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/help.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/index.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/inspect.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/install.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/list.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/search.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/show.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/uninstall.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/configuration.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/installed.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/sdist.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/installed.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/sdist.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/collector.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/package_finder.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/sources.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/collector.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/package_finder.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/sources.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/_distutils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/_sysconfig.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/_distutils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/_sysconfig.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/main.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/_json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/_json.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_envs.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/_compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/_dists.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/_envs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/pkg_resources.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/candidate.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/direct_url.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/format_control.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/index.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/installation_report.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/link.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/scheme.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/search_scope.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/selection_prefs.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/target_python.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/candidate.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/direct_url.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/format_control.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/index.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/installation_report.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/link.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/scheme.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/search_scope.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/selection_prefs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/target_python.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/auth.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/download.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/session.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/xmlrpc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/auth.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/download.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/lazy_wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/session.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/xmlrpc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/check.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/freeze.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/prepare.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/build_tracker.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/metadata.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/metadata_editable.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/wheel_editable.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/build_tracker.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/metadata.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/metadata_editable.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/metadata_legacy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/wheel_editable.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/wheel_legacy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/check.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/freeze.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/editable_legacy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/editable_legacy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/prepare.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/pyproject.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/constructors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_file.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_install.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_set.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_uninstall.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/constructors.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/req_file.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/req_install.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/req_set.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/req_uninstall.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__pycache__/resolver.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/resolver.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/candidates.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/factory.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/provider.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/reporter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/requirements.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/resolver.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/self_outdated_check.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/_jaraco_text.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/_log.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/encoding.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/logging.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/misc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/models.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/setuptools_build.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/urls.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/_jaraco_text.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/_log.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/appdirs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/compatibility_tags.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/datetime.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/deprecation.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/direct_url_helpers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/egg_link.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/encoding.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/entrypoints.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/filesystem.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/filetypes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/glibc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/hashes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/logging.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/misc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/models.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/packaging.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/setuptools_build.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/subprocess.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/temp_dir.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/unpacking.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/urls.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/virtualenv.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/git.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/mercurial.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/subversion.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/versioncontrol.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/bazaar.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/git.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/mercurial.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/subversion.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/versioncontrol.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/wheel_builder.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/__pycache__/six.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/__pycache__/typing_extensions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/adapter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/serialize.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/_cmd.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/adapter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/controller.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/filewrapper.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/heuristics.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/serialize.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/wrapper.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/cacert.pem create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/core.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/big5freq.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/big5prober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/chardistribution.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/charsetgroupprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/charsetprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/codingstatemachine.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/codingstatemachinedict.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/cp949prober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/enums.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/escprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/escsm.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/eucjpprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/euckrfreq.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/euckrprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/euctwfreq.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/euctwprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/gb2312freq.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/gb2312prober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/hebrewprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/jisfreq.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/johabfreq.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/johabprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/jpcntx.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langbulgarianmodel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langgreekmodel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langhebrewmodel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langhungarianmodel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langrussianmodel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langthaimodel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langturkishmodel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/latin1prober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/macromanprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/mbcharsetprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/mbcsgroupprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/mbcssm.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/resultdict.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/sbcharsetprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/sbcsgroupprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/sjisprober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/universaldetector.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/utf1632prober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/utf8prober.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/big5freq.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/big5prober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/chardistribution.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/charsetgroupprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/charsetprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/cli/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/cli/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/cli/__pycache__/chardetect.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/cli/chardetect.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/codingstatemachine.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/codingstatemachinedict.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/cp949prober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/enums.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/escprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/escsm.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/eucjpprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/euckrfreq.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/euckrprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/euctwfreq.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/euctwprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/gb2312freq.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/gb2312prober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/hebrewprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/jisfreq.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/johabfreq.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/johabprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/jpcntx.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langbulgarianmodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langgreekmodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langhebrewmodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langhungarianmodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langrussianmodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langthaimodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/langturkishmodel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/latin1prober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/macromanprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/mbcharsetprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/mbcsgroupprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/mbcssm.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/metadata/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/metadata/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/metadata/__pycache__/languages.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/metadata/languages.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/resultdict.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/sbcharsetprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/sbcsgroupprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/sjisprober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/universaldetector.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/utf1632prober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/utf8prober.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/chardet/version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/__pycache__/ansi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/__pycache__/ansitowin32.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/__pycache__/initialise.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/__pycache__/win32.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/__pycache__/winterm.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/ansi.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/ansitowin32.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/initialise.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/ansi_test.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/ansitowin32_test.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/initialise_test.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/isatty_test.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/winterm_test.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/ansi_test.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/ansitowin32_test.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/initialise_test.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/isatty_test.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/winterm_test.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/win32.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/colorama/winterm.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/database.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/index.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/locators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/manifest.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/markers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/metadata.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/resources.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/scripts.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/database.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/index.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/locators.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/manifest.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/markers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/metadata.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/resources.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/scripts.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/util.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/distro.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/codec.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/core.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/idnadata.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/intranges.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/package_data.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/uts46data.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/codec.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/core.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/idnadata.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/intranges.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/package_data.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/uts46data.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/ext.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/fallback.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/ext.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/fallback.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__about__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/__about__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/markers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/requirements.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/_manylinux.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/_musllinux.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/_structures.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/markers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/requirements.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/specifiers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/tags.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pkg_resources/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pkg_resources/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/android.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/android.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/api.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/macos.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/unix.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/windows.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/cmdline.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/console.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/formatter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/scanner.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/sphinxext.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/cmdline.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/console.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/filter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/filters/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/groff.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/html.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/img.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/irc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/latex.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/other.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/pangomarkup.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/rtf.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/svg.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/_mapping.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/bbcode.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/groff.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/html.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/img.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/irc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/latex.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/other.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/rtf.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/svg.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal256.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexer.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/_mapping.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/python.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/modeline.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/plugin.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/regexopt.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/scanner.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/sphinxext.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/style.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/token.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/unistring.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/util.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/actions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/common.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/core.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/helpers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/results.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/testing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/unicode.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/actions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/common.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/core.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/diagram/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/diagram/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/helpers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/results.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/testing.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/unicode.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/util.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_impl.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__version__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/_internal_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/adapters.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/auth.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/certs.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/cookies.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/help.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/hooks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/models.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/packages.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/sessions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/status_codes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/structures.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__version__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/_internal_utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/adapters.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/api.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/auth.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/certs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/cookies.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/help.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/hooks.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/models.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/packages.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/sessions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/status_codes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/structures.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/providers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/reporters.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/resolvers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/structs.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__pycache__/collections_abc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_cell_widths.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_codes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_replace.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_export_format.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_extension.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_fileno.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_inspect.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_log_render.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_loop.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_null_file.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_palettes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_pick.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_ratio.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_spinners.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_stack.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_timer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_win32_console.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_windows.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_windows_renderer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_wrap.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/abc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/align.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/ansi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/bar.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/box.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/cells.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color_triplet.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/columns.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/console.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/constrain.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/containers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/control.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/default_styles.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/diagnose.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/emoji.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/errors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/file_proxy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/filesize.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/highlighter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/jupyter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/layout.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live_render.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/logging.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/markup.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/measure.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/padding.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/palette.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/panel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pretty.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress_bar.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/prompt.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/protocol.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/region.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/repr.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/rule.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/scope.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/screen.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/segment.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/spinner.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/status.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/style.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/styled.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/syntax.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/table.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/terminal_theme.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/text.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/theme.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/themes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/traceback.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/tree.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_cell_widths.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_codes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_replace.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_export_format.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_extension.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_fileno.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_inspect.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_log_render.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_loop.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_null_file.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_palettes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_pick.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_ratio.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_spinners.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_stack.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_timer.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_win32_console.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows_renderer.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_wrap.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/abc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/align.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/ansi.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/bar.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/box.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/cells.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/color.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/color_triplet.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/columns.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/console.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/constrain.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/containers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/control.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/default_styles.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/diagnose.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/emoji.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/errors.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/file_proxy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/filesize.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/highlighter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/json.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/jupyter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/layout.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/live.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/live_render.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/logging.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/markup.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/measure.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/padding.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/pager.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/palette.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/panel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/pretty.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/progress.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/progress_bar.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/prompt.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/protocol.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/region.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/repr.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/rule.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/scope.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/screen.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/segment.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/spinner.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/status.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/style.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/styled.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/syntax.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/table.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/terminal_theme.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/text.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/theme.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/themes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/traceback.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/tree.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/six.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/_asyncio.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/after.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/before.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/before_sleep.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/nap.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/retry.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/stop.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/tornadoweb.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/wait.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/_asyncio.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/_utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/after.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/before.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/before_sleep.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/nap.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/retry.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/stop.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/tornadoweb.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tenacity/wait.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_parser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_re.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/_parser.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/_re.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/_types.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_api.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_macos.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_openssl.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_ssl_constants.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_windows.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/typing_extensions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/_collections.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/_version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/connection.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/connectionpool.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/appengine.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/securetransport.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/socks.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/fields.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/filepost.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/weakref_finalize.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/six.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/poolmanager.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/request.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/response.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/connection.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/proxy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/queue.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/request.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/response.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/retry.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssltransport.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/timeout.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/url.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/wait.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/vendor.txt create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/__pycache__/labels.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/__pycache__/mklabels.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/__pycache__/tests.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/__pycache__/x_user_defined.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/labels.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/mklabels.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/tests.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/webencodings/x_user_defined.py create mode 100644 venv/lib/python3.12/site-packages/pip/py.typed create mode 100644 venv/lib/python3.12/site-packages/python_engineio-4.12.3.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/python_engineio-4.12.3.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/python_engineio-4.12.3.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/python_engineio-4.12.3.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/python_engineio-4.12.3.dist-info/licenses/LICENSE create mode 100755 venv/lib/python3.12/site-packages/python_engineio-4.12.3.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/python_socketio-5.9.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/python_socketio-5.9.0.dist-info/LICENSE create mode 100644 venv/lib/python3.12/site-packages/python_socketio-5.9.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/python_socketio-5.9.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/python_socketio-5.9.0.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/python_socketio-5.9.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/python_socketio-5.9.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/simple_websocket-1.1.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/simple_websocket-1.1.0.dist-info/LICENSE create mode 100644 venv/lib/python3.12/site-packages/simple_websocket-1.1.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/simple_websocket-1.1.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/simple_websocket-1.1.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/simple_websocket-1.1.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/simple_websocket/__init__.py create mode 100644 venv/lib/python3.12/site-packages/simple_websocket/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/simple_websocket/__pycache__/aiows.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/simple_websocket/__pycache__/asgi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/simple_websocket/__pycache__/errors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/simple_websocket/__pycache__/ws.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/simple_websocket/aiows.py create mode 100644 venv/lib/python3.12/site-packages/simple_websocket/asgi.py create mode 100644 venv/lib/python3.12/site-packages/simple_websocket/errors.py create mode 100644 venv/lib/python3.12/site-packages/simple_websocket/ws.py create mode 100644 venv/lib/python3.12/site-packages/six-1.17.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/six-1.17.0.dist-info/LICENSE create mode 100644 venv/lib/python3.12/site-packages/six-1.17.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/six-1.17.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/six-1.17.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/six-1.17.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/six.py create mode 100644 venv/lib/python3.12/site-packages/socketio/__init__.py create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/asgi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/asyncio_aiopika_manager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/asyncio_client.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/asyncio_manager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/asyncio_namespace.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/asyncio_pubsub_manager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/asyncio_redis_manager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/asyncio_server.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/base_manager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/client.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/kafka_manager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/kombu_manager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/middleware.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/msgpack_packet.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/namespace.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/packet.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/pubsub_manager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/redis_manager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/server.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/tornado.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/__pycache__/zmq_manager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/socketio/asgi.py create mode 100644 venv/lib/python3.12/site-packages/socketio/asyncio_aiopika_manager.py create mode 100644 venv/lib/python3.12/site-packages/socketio/asyncio_client.py create mode 100644 venv/lib/python3.12/site-packages/socketio/asyncio_manager.py create mode 100644 venv/lib/python3.12/site-packages/socketio/asyncio_namespace.py create mode 100644 venv/lib/python3.12/site-packages/socketio/asyncio_pubsub_manager.py create mode 100644 venv/lib/python3.12/site-packages/socketio/asyncio_redis_manager.py create mode 100644 venv/lib/python3.12/site-packages/socketio/asyncio_server.py create mode 100644 venv/lib/python3.12/site-packages/socketio/base_manager.py create mode 100644 venv/lib/python3.12/site-packages/socketio/client.py create mode 100644 venv/lib/python3.12/site-packages/socketio/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/socketio/kafka_manager.py create mode 100644 venv/lib/python3.12/site-packages/socketio/kombu_manager.py create mode 100644 venv/lib/python3.12/site-packages/socketio/middleware.py create mode 100644 venv/lib/python3.12/site-packages/socketio/msgpack_packet.py create mode 100644 venv/lib/python3.12/site-packages/socketio/namespace.py create mode 100644 venv/lib/python3.12/site-packages/socketio/packet.py create mode 100644 venv/lib/python3.12/site-packages/socketio/pubsub_manager.py create mode 100644 venv/lib/python3.12/site-packages/socketio/redis_manager.py create mode 100644 venv/lib/python3.12/site-packages/socketio/server.py create mode 100644 venv/lib/python3.12/site-packages/socketio/tornado.py create mode 100644 venv/lib/python3.12/site-packages/socketio/zmq_manager.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug-3.1.3.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/werkzeug-3.1.3.dist-info/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/werkzeug-3.1.3.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/werkzeug-3.1.3.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/werkzeug-3.1.3.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__init__.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/_internal.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/_reloader.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/formparser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/http.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/local.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/security.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/serving.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/test.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/testapp.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/urls.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/user_agent.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/__pycache__/wsgi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/_internal.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/_reloader.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/__init__.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/accept.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/auth.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/cache_control.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/csp.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/etag.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/file_storage.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/headers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/mixins.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/range.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/structures.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/accept.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/auth.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/cache_control.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/csp.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/etag.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/file_storage.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/headers.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/mixins.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/range.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/datastructures/structures.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/__init__.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/__pycache__/console.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/__pycache__/repr.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/__pycache__/tbtools.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/console.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/repr.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/shared/ICON_LICENSE.md create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/shared/console.png create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/shared/debugger.js create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/shared/less.png create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/shared/more.png create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/shared/style.css create mode 100644 venv/lib/python3.12/site-packages/werkzeug/debug/tbtools.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/formparser.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/http.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/local.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/__init__.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/lint.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/dispatcher.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/http_proxy.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/lint.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/profiler.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/proxy_fix.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/middleware/shared_data.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/py.typed create mode 100644 venv/lib/python3.12/site-packages/werkzeug/routing/__init__.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/routing/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/routing/__pycache__/converters.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/routing/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/routing/__pycache__/map.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/routing/__pycache__/matcher.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/routing/__pycache__/rules.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/routing/converters.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/routing/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/routing/map.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/routing/matcher.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/routing/rules.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/sansio/__init__.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/sansio/__pycache__/http.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/sansio/__pycache__/request.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/sansio/__pycache__/response.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/sansio/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/sansio/http.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/sansio/multipart.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/sansio/request.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/sansio/response.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/sansio/utils.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/security.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/serving.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/test.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/testapp.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/urls.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/user_agent.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/utils.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/wrappers/__init__.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/wrappers/__pycache__/request.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/wrappers/__pycache__/response.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/werkzeug/wrappers/request.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/wrappers/response.py create mode 100644 venv/lib/python3.12/site-packages/werkzeug/wsgi.py create mode 100644 venv/lib/python3.12/site-packages/wsproto-1.2.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/wsproto-1.2.0.dist-info/LICENSE create mode 100644 venv/lib/python3.12/site-packages/wsproto-1.2.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/wsproto-1.2.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/wsproto-1.2.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/wsproto-1.2.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/wsproto/__init__.py create mode 100644 venv/lib/python3.12/site-packages/wsproto/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/wsproto/__pycache__/connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/wsproto/__pycache__/events.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/wsproto/__pycache__/extensions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/wsproto/__pycache__/frame_protocol.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/wsproto/__pycache__/handshake.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/wsproto/__pycache__/typing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/wsproto/__pycache__/utilities.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/wsproto/connection.py create mode 100644 venv/lib/python3.12/site-packages/wsproto/events.py create mode 100644 venv/lib/python3.12/site-packages/wsproto/extensions.py create mode 100644 venv/lib/python3.12/site-packages/wsproto/frame_protocol.py create mode 100644 venv/lib/python3.12/site-packages/wsproto/handshake.py create mode 100644 venv/lib/python3.12/site-packages/wsproto/py.typed create mode 100644 venv/lib/python3.12/site-packages/wsproto/typing.py create mode 100644 venv/lib/python3.12/site-packages/wsproto/utilities.py create mode 120000 venv/lib64 create mode 100644 venv/pyvenv.cfg diff --git a/app.py b/app.py index ed4f1f6..04fd126 100644 --- a/app.py +++ b/app.py @@ -90,4 +90,4 @@ if __name__ == '__main__': context.load_cert_chain('cert.pem', 'key.pem') print("Starting Flask-SocketIO server on https://localhost:3000") - socketio.run(app, host='localhost', port=3000, ssl_context=context, debug=True) \ No newline at end of file + socketio.run(app, host='0.0.0.0', port=3232, ssl_context=context, debug=True) diff --git a/setup.sh b/setup.sh old mode 100644 new mode 100755 diff --git a/main.js b/static/main.js similarity index 100% rename from main.js rename to static/main.js diff --git a/styles.css b/static/styles.css similarity index 100% rename from styles.css rename to static/styles.css diff --git a/index.html b/templates/index.html similarity index 100% rename from index.html rename to templates/index.html diff --git a/venv/bin/Activate.ps1 b/venv/bin/Activate.ps1 new file mode 100644 index 0000000..b49d77b --- /dev/null +++ b/venv/bin/Activate.ps1 @@ -0,0 +1,247 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/venv/bin/activate b/venv/bin/activate new file mode 100644 index 0000000..ea3384e --- /dev/null +++ b/venv/bin/activate @@ -0,0 +1,70 @@ +# This file must be used with "source bin/activate" *from bash* +# You cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # Call hash to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + hash -r 2> /dev/null + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +# on Windows, a path can contain colons and backslashes and has to be converted: +if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then + # transform D:\path\to\venv to /d/path/to/venv on MSYS + # and to /cygdrive/d/path/to/venv on Cygwin + export VIRTUAL_ENV=$(cygpath /home/colby/chatty/venv) +else + # use the path as-is + export VIRTUAL_ENV=/home/colby/chatty/venv +fi + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/"bin":$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1='(venv) '"${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT='(venv) ' + export VIRTUAL_ENV_PROMPT +fi + +# Call hash to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +hash -r 2> /dev/null diff --git a/venv/bin/activate.csh b/venv/bin/activate.csh new file mode 100644 index 0000000..0ede6cd --- /dev/null +++ b/venv/bin/activate.csh @@ -0,0 +1,27 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. + +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV /home/colby/chatty/venv + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/"bin":$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + set prompt = '(venv) '"$prompt" + setenv VIRTUAL_ENV_PROMPT '(venv) ' +endif + +alias pydoc python -m pydoc + +rehash diff --git a/venv/bin/activate.fish b/venv/bin/activate.fish new file mode 100644 index 0000000..a78a9c4 --- /dev/null +++ b/venv/bin/activate.fish @@ -0,0 +1,69 @@ +# This file must be used with "source /bin/activate.fish" *from fish* +# (https://fishshell.com/). You cannot run it directly. + +function deactivate -d "Exit virtual environment and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + set -e _OLD_FISH_PROMPT_OVERRIDE + # prevents error when using nested fish instances (Issue #93858) + if functions -q _old_fish_prompt + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + end + + set -e VIRTUAL_ENV + set -e VIRTUAL_ENV_PROMPT + if test "$argv[1]" != "nondestructive" + # Self-destruct! + functions -e deactivate + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV /home/colby/chatty/venv + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/"bin $PATH + +# Unset PYTHONHOME if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # Save the current fish_prompt function as the function _old_fish_prompt. + functions -c fish_prompt _old_fish_prompt + + # With the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command. + set -l old_status $status + + # Output the venv prompt; color taken from the blue of the Python logo. + printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal) + + # Restore the return status of the previous command. + echo "exit $old_status" | . + # Output the original/"old" prompt. + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" + set -gx VIRTUAL_ENV_PROMPT '(venv) ' +end diff --git a/venv/bin/flask b/venv/bin/flask new file mode 100755 index 0000000..0d86f4f --- /dev/null +++ b/venv/bin/flask @@ -0,0 +1,8 @@ +#!/home/colby/chatty/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from flask.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/pip b/venv/bin/pip new file mode 100755 index 0000000..a7f9523 --- /dev/null +++ b/venv/bin/pip @@ -0,0 +1,8 @@ +#!/home/colby/chatty/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/pip3 b/venv/bin/pip3 new file mode 100755 index 0000000..a7f9523 --- /dev/null +++ b/venv/bin/pip3 @@ -0,0 +1,8 @@ +#!/home/colby/chatty/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/pip3.12 b/venv/bin/pip3.12 new file mode 100755 index 0000000..a7f9523 --- /dev/null +++ b/venv/bin/pip3.12 @@ -0,0 +1,8 @@ +#!/home/colby/chatty/venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/python b/venv/bin/python new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/venv/bin/python @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/venv/bin/python3 b/venv/bin/python3 new file mode 120000 index 0000000..ae65fda --- /dev/null +++ b/venv/bin/python3 @@ -0,0 +1 @@ +/usr/bin/python3 \ No newline at end of file diff --git a/venv/bin/python3.12 b/venv/bin/python3.12 new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/venv/bin/python3.12 @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/venv/include/site/python3.12/greenlet/greenlet.h b/venv/include/site/python3.12/greenlet/greenlet.h new file mode 100644 index 0000000..d02a16e --- /dev/null +++ b/venv/include/site/python3.12/greenlet/greenlet.h @@ -0,0 +1,164 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ + +/* Greenlet object interface */ + +#ifndef Py_GREENLETOBJECT_H +#define Py_GREENLETOBJECT_H + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is deprecated and undocumented. It does not change. */ +#define GREENLET_VERSION "1.0.0" + +#ifndef GREENLET_MODULE +#define implementation_ptr_t void* +#endif + +typedef struct _greenlet { + PyObject_HEAD + PyObject* weakreflist; + PyObject* dict; + implementation_ptr_t pimpl; +} PyGreenlet; + +#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type)) + + +/* C API functions */ + +/* Total number of symbols that are exported */ +#define PyGreenlet_API_pointers 12 + +#define PyGreenlet_Type_NUM 0 +#define PyExc_GreenletError_NUM 1 +#define PyExc_GreenletExit_NUM 2 + +#define PyGreenlet_New_NUM 3 +#define PyGreenlet_GetCurrent_NUM 4 +#define PyGreenlet_Throw_NUM 5 +#define PyGreenlet_Switch_NUM 6 +#define PyGreenlet_SetParent_NUM 7 + +#define PyGreenlet_MAIN_NUM 8 +#define PyGreenlet_STARTED_NUM 9 +#define PyGreenlet_ACTIVE_NUM 10 +#define PyGreenlet_GET_PARENT_NUM 11 + +#ifndef GREENLET_MODULE +/* This section is used by modules that uses the greenlet C API */ +static void** _PyGreenlet_API = NULL; + +# define PyGreenlet_Type \ + (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM]) + +# define PyExc_GreenletError \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM]) + +# define PyExc_GreenletExit \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM]) + +/* + * PyGreenlet_New(PyObject *args) + * + * greenlet.greenlet(run, parent=None) + */ +# define PyGreenlet_New \ + (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \ + _PyGreenlet_API[PyGreenlet_New_NUM]) + +/* + * PyGreenlet_GetCurrent(void) + * + * greenlet.getcurrent() + */ +# define PyGreenlet_GetCurrent \ + (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM]) + +/* + * PyGreenlet_Throw( + * PyGreenlet *greenlet, + * PyObject *typ, + * PyObject *val, + * PyObject *tb) + * + * g.throw(...) + */ +# define PyGreenlet_Throw \ + (*(PyObject * (*)(PyGreenlet * self, \ + PyObject * typ, \ + PyObject * val, \ + PyObject * tb)) \ + _PyGreenlet_API[PyGreenlet_Throw_NUM]) + +/* + * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args) + * + * g.switch(*args, **kwargs) + */ +# define PyGreenlet_Switch \ + (*(PyObject * \ + (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \ + _PyGreenlet_API[PyGreenlet_Switch_NUM]) + +/* + * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent) + * + * g.parent = new_parent + */ +# define PyGreenlet_SetParent \ + (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \ + _PyGreenlet_API[PyGreenlet_SetParent_NUM]) + +/* + * PyGreenlet_GetParent(PyObject* greenlet) + * + * return greenlet.parent; + * + * This could return NULL even if there is no exception active. + * If it does not return NULL, you are responsible for decrementing the + * reference count. + */ +# define PyGreenlet_GetParent \ + (*(PyGreenlet* (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM]) + +/* + * deprecated, undocumented alias. + */ +# define PyGreenlet_GET_PARENT PyGreenlet_GetParent + +# define PyGreenlet_MAIN \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_MAIN_NUM]) + +# define PyGreenlet_STARTED \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_STARTED_NUM]) + +# define PyGreenlet_ACTIVE \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_ACTIVE_NUM]) + + + + +/* Macro that imports greenlet and initializes C API */ +/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we + keep the older definition to be sure older code that might have a copy of + the header still works. */ +# define PyGreenlet_Import() \ + { \ + _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \ + } + +#endif /* GREENLET_MODULE */ + +#ifdef __cplusplus +} +#endif +#endif /* !Py_GREENLETOBJECT_H */ diff --git a/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/LICENSE b/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/LICENSE new file mode 100644 index 0000000..f5c10ab --- /dev/null +++ b/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Miguel Grinberg + +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/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/METADATA b/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/METADATA new file mode 100644 index 0000000..ddec6b0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/METADATA @@ -0,0 +1,77 @@ +Metadata-Version: 2.1 +Name: Flask-SocketIO +Version: 5.3.6 +Summary: Socket.IO integration for Flask applications +Home-page: https://github.com/miguelgrinberg/flask-socketio +Author: Miguel Grinberg +Author-email: miguel.grinberg@gmail.com +Project-URL: Bug Tracker, https://github.com/miguelgrinberg/flask-socketio/issues +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python :: 3 +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Requires-Python: >=3.6 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: Flask >=0.9 +Requires-Dist: python-socketio >=5.0.2 +Provides-Extra: docs +Requires-Dist: sphinx ; extra == 'docs' + +Flask-SocketIO +============== + +[![Build status](https://github.com/miguelgrinberg/flask-socketio/workflows/build/badge.svg)](https://github.com/miguelgrinberg/Flask-SocketIO/actions) [![codecov](https://codecov.io/gh/miguelgrinberg/flask-socketio/branch/main/graph/badge.svg)](https://codecov.io/gh/miguelgrinberg/flask-socketio) + +Socket.IO integration for Flask applications. + +Sponsors +-------- + +The following organizations are funding this project: + +![Socket.IO](https://images.opencollective.com/socketio/050e5eb/logo/64.png)
[Socket.IO](https://socket.io) | [Add your company here!](https://github.com/sponsors/miguelgrinberg)| +-|- + +Many individual sponsors also support this project through small ongoing contributions. Why not [join them](https://github.com/sponsors/miguelgrinberg)? + +Installation +------------ + +You can install this package as usual with pip: + + pip install flask-socketio + +Example +------- + +```py +from flask import Flask, render_template +from flask_socketio import SocketIO, emit + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'secret!' +socketio = SocketIO(app) + +@app.route('/') +def index(): + return render_template('index.html') + +@socketio.event +def my_event(message): + emit('my response', {'data': 'got it!'}) + +if __name__ == '__main__': + socketio.run(app) +``` + +Resources +--------- + +- [Tutorial](http://blog.miguelgrinberg.com/post/easy-websockets-with-flask-and-gevent) +- [Documentation](http://flask-socketio.readthedocs.io/en/latest/) +- [PyPI](https://pypi.python.org/pypi/Flask-SocketIO) +- [Change Log](https://github.com/miguelgrinberg/Flask-SocketIO/blob/main/CHANGES.md) +- Questions? See the [questions](https://stackoverflow.com/questions/tagged/flask-socketio) others have asked on Stack Overflow, or [ask](https://stackoverflow.com/questions/ask?tags=python+flask-socketio+python-socketio) your own question. + diff --git a/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/RECORD b/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/RECORD new file mode 100644 index 0000000..3d931b9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/RECORD @@ -0,0 +1,13 @@ +Flask_SocketIO-5.3.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +Flask_SocketIO-5.3.6.dist-info/LICENSE,sha256=aNCWbkgKjS_T1cJtACyZbvCM36KxWnfQ0LWTuavuYKQ,1082 +Flask_SocketIO-5.3.6.dist-info/METADATA,sha256=vmIOzjkNLXRjmocRXtso6hLV27aiJgH7_A55TVJyD4k,2631 +Flask_SocketIO-5.3.6.dist-info/RECORD,, +Flask_SocketIO-5.3.6.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +Flask_SocketIO-5.3.6.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92 +Flask_SocketIO-5.3.6.dist-info/top_level.txt,sha256=C1ugzQBJ3HHUJsWGzyt70XRVOX-y4CUAR8MWKjwJOQ8,15 +flask_socketio/__init__.py,sha256=ea3QXRYKBje4JQGcNSEOmj42qlf2peRNbCzZZWfD9DE,54731 +flask_socketio/__pycache__/__init__.cpython-312.pyc,, +flask_socketio/__pycache__/namespace.cpython-312.pyc,, +flask_socketio/__pycache__/test_client.cpython-312.pyc,, +flask_socketio/namespace.py,sha256=b3oyXEemu2po-wpoy4ILTHQMVuVQqicogCDxfymfz_w,2020 +flask_socketio/test_client.py,sha256=9_R1y_vP8yr8wzimQUEMAUyVqX12FMXurLj8t1ecDdc,11034 diff --git a/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/REQUESTED b/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/WHEEL b/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/WHEEL new file mode 100644 index 0000000..7e68873 --- /dev/null +++ b/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.41.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/top_level.txt b/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/top_level.txt new file mode 100644 index 0000000..ba82ec3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/Flask_SocketIO-5.3.6.dist-info/top_level.txt @@ -0,0 +1 @@ +flask_socketio diff --git a/venv/lib/python3.12/site-packages/__pycache__/six.cpython-312.pyc b/venv/lib/python3.12/site-packages/__pycache__/six.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c0a0e56edc1f4b411c491c27277b50e4de66a89 GIT binary patch literal 41392 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 +License: MPL 2.0 +Project-URL: Changelog, https://bidict.readthedocs.io/changelog.html +Project-URL: Documentation, https://bidict.readthedocs.io +Project-URL: Funding, https://bidict.readthedocs.io/#sponsoring +Project-URL: Repository, https://github.com/jab/bidict +Keywords: bidict,bimap,bidirectional,dict,dictionary,mapping,collections +Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Typing :: Typed +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst +License-File: LICENSE + +.. role:: doc +.. (Forward declaration for the "doc" role that Sphinx defines for interop with renderers that + are often used to show this doc and that are unaware of Sphinx (GitHub.com, PyPI.org, etc.). + Use :doc: rather than :ref: here for better interop as well.) + + +bidict +====== + +*The bidirectional mapping library for Python.* + + +Status +------ + +.. image:: https://img.shields.io/pypi/v/bidict.svg + :target: https://pypi.org/project/bidict + :alt: Latest release + +.. image:: https://img.shields.io/readthedocs/bidict/main.svg + :target: https://bidict.readthedocs.io/en/main/ + :alt: Documentation + +.. image:: https://github.com/jab/bidict/actions/workflows/test.yml/badge.svg + :target: https://github.com/jab/bidict/actions/workflows/test.yml?query=branch%3Amain + :alt: GitHub Actions CI status + +.. image:: https://img.shields.io/pypi/l/bidict.svg + :target: https://raw.githubusercontent.com/jab/bidict/main/LICENSE + :alt: License + +.. image:: https://static.pepy.tech/badge/bidict + :target: https://pepy.tech/project/bidict + :alt: PyPI Downloads + +.. image:: https://img.shields.io/badge/GitHub-sponsor-ff69b4 + :target: https://github.com/sponsors/jab + :alt: Sponsor + + +Features +-------- + +- Mature: Depended on by + Google, Venmo, CERN, Baidu, Tencent, + and teams across the world since 2009 + +- Familiar, Pythonic APIs + that are carefully designed for + safety, simplicity, flexibility, and ergonomics + +- Lightweight, with no runtime dependencies + outside Python's standard library + +- Implemented in + concise, well-factored, fully type-hinted Python code + that is optimized for running efficiently + as well as for long-term maintenance and stability + (as well as `joy <#learning-from-bidict>`__) + +- Extensively `documented `__ + +- 100% test coverage + running continuously across all supported Python versions + (including property-based tests and benchmarks) + + +Installation +------------ + +``pip install bidict`` + + +Quick Start +----------- + +.. code:: python + + >>> from bidict import bidict + >>> element_by_symbol = bidict({'H': 'hydrogen'}) + >>> element_by_symbol['H'] + 'hydrogen' + >>> element_by_symbol.inverse['hydrogen'] + 'H' + + +For more usage documentation, +head to the :doc:`intro` [#fn-intro]_ +and proceed from there. + + +Enterprise Support +------------------ + +Enterprise-level support for bidict can be obtained via the +`Tidelift subscription `__ +or by `contacting me directly `__. + +I have a US-based LLC set up for invoicing, +and I have 15+ years of professional experience +delivering software and support to companies successfully. + +You can also sponsor my work through several platforms, including GitHub Sponsors. +See the `Sponsoring <#sponsoring>`__ section below for details, +including rationale and examples of companies +supporting the open source projects they depend on. + + +Voluntary Community Support +--------------------------- + +Please search through already-asked questions and answers +in `GitHub Discussions `__ +and the `issue tracker `__ +in case your question has already been addressed. + +Otherwise, please feel free to +`start a new discussion `__ +or `create a new issue `__ on GitHub +for voluntary community support. + + +Notice of Usage +--------------- + +If you use bidict, +and especially if your usage or your organization is significant in some way, +please let me know in any of the following ways: + +- `star bidict on GitHub `__ +- post in `GitHub Discussions `__ +- `email me `__ + + +Changelog +--------- + +For bidict release notes, see the :doc:`changelog`. [#fn-changelog]_ + + +Release Notifications +--------------------- + +.. duplicated in CHANGELOG.rst: + (Would use `.. include::` but GitHub's renderer doesn't support it.) + +Watch `bidict releases on GitHub `__ +to be notified when new versions of bidict are published. +Click the "Watch" dropdown, choose "Custom", and then choose "Releases". + + +Learning from bidict +-------------------- + +One of the best things about bidict +is that it touches a surprising number of +interesting Python corners, +especially given its small size and scope. + +Check out :doc:`learning-from-bidict` [#fn-learning]_ +if you're interested in learning more. + + +Contributing +------------ + +I have been bidict's sole maintainer +and `active contributor `__ +since I started the project ~15 years ago. + +Your help would be most welcome! +See the :doc:`contributors-guide` [#fn-contributing]_ +for more information. + + +Sponsoring +---------- + +.. duplicated in CONTRIBUTING.rst + (Would use `.. include::` but GitHub's renderer doesn't support it.) + +.. image:: https://img.shields.io/badge/GitHub-sponsor-ff69b4 + :target: https://github.com/sponsors/jab + :alt: Sponsor through GitHub + +Bidict is the product of thousands of hours of my unpaid work +over the 15+ years that I've been the sole maintainer. + +If bidict has helped you or your company accomplish your work, +please sponsor my work through one of the following, +and/or ask your company to do the same: + +- `GitHub `__ +- `PayPal `__ +- `Tidelift `__ +- `thanks.dev `__ +- `Gumroad `__ +- `a support engagement with my LLC <#enterprise-support>`__ + +If you're not sure which to use, GitHub is an easy option, +especially if you already have a GitHub account. +Just choose a monthly or one-time amount, and GitHub handles everything else. +Your bidict sponsorship on GitHub will automatically go +on the same regular bill as any other GitHub charges you pay for. +PayPal is another easy option for one-time contributions. + +See the following for rationale and examples of companies +supporting the open source projects they depend on +in this manner: + +- ``__ +- ``__ +- ``__ + +.. - ``__ +.. - ``__ +.. - ``__ + + +Finding Documentation +--------------------- + +If you're viewing this on ``__, +note that multiple versions of the documentation are available, +and you can choose a different version using the popup menu at the bottom-right. +Please make sure you're viewing the version of the documentation +that corresponds to the version of bidict you'd like to use. + +If you're viewing this on GitHub, PyPI, or some other place +that can't render and link this documentation properly +and are seeing broken links, +try these alternate links instead: + +.. [#fn-intro] ``__ | ``__ + +.. [#fn-changelog] ``__ | ``__ + +.. [#fn-learning] ``__ | ``__ + +.. [#fn-contributing] ``__ | ``__ diff --git a/venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/RECORD b/venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/RECORD new file mode 100644 index 0000000..86bd147 --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/RECORD @@ -0,0 +1,31 @@ +bidict-0.23.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +bidict-0.23.1.dist-info/LICENSE,sha256=8_U63OyqSNc6ZuI4-lupBstBh2eDtF0ooTRrMULuvZo,16784 +bidict-0.23.1.dist-info/METADATA,sha256=2ovIRm6Df8gdwAMekGqkeBSF5TWj2mv1jpmh4W4ks7o,8704 +bidict-0.23.1.dist-info/RECORD,, +bidict-0.23.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92 +bidict-0.23.1.dist-info/top_level.txt,sha256=WuQO02jp0ODioS7sJoaHg3JJ5_3h6Sxo9RITvNGPYmc,7 +bidict/__init__.py,sha256=pL87KsrDpBsl3AG09LQk1t1TSFt0hIJVYa2POMdErN8,4398 +bidict/__pycache__/__init__.cpython-312.pyc,, +bidict/__pycache__/_abc.cpython-312.pyc,, +bidict/__pycache__/_base.cpython-312.pyc,, +bidict/__pycache__/_bidict.cpython-312.pyc,, +bidict/__pycache__/_dup.cpython-312.pyc,, +bidict/__pycache__/_exc.cpython-312.pyc,, +bidict/__pycache__/_frozen.cpython-312.pyc,, +bidict/__pycache__/_iter.cpython-312.pyc,, +bidict/__pycache__/_orderedbase.cpython-312.pyc,, +bidict/__pycache__/_orderedbidict.cpython-312.pyc,, +bidict/__pycache__/_typing.cpython-312.pyc,, +bidict/__pycache__/metadata.cpython-312.pyc,, +bidict/_abc.py,sha256=SMCNdCsmqSWg0OGnMZtnnXY8edjXcyZup5tva4HBm_c,3172 +bidict/_base.py,sha256=YiauA0aj52fNB6cfZ4gBt6OV-CRQoZm7WVhuw1nT-Cg,24439 +bidict/_bidict.py,sha256=Sr-RoEzWOaxpnDRbDJ7ngaGRIsyGnqZgzvR-NyT4jl4,6923 +bidict/_dup.py,sha256=YAn5gWA6lwMBA5A6ebVF19UTZyambGS8WxmbK4TN1Ww,2079 +bidict/_exc.py,sha256=HnD_WgteI5PrXa3zBx9RUiGlgnZTO6CF4nIU9p3-njk,1066 +bidict/_frozen.py,sha256=p4TaRHKeyTs0KmlpwSnZiTlN_CR4J97kAgBpNdZHQMs,1771 +bidict/_iter.py,sha256=zVUx-hJ1M4YuJROoFWRjPKlcaFnyo1AAuRpOaKAFhOQ,1530 +bidict/_orderedbase.py,sha256=M7v5rHa7vrym9Z3DxQBFQDxjnrr39Z8p26V0c1PggoE,8942 +bidict/_orderedbidict.py,sha256=pPnmC19mIISrj8_yjnb-4r_ti1B74tD5eTd08DETNuI,7080 +bidict/_typing.py,sha256=AylMZpBhEFTQegfziPSxfKkKLk7oUsH6o3awDIg2z_k,1289 +bidict/metadata.py,sha256=BMIKu6fBY_OKeV_q48EpumE7MdmFw8rFcdaUz8kcIYk,573 +bidict/py.typed,sha256=RJao5SVFYIp8IfbxhL_SpZkBQYe3XXzPlobSRdh4B_c,16 diff --git a/venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/WHEEL b/venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/WHEEL new file mode 100644 index 0000000..98c0d20 --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.42.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/top_level.txt b/venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/top_level.txt new file mode 100644 index 0000000..6ff5b04 --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict-0.23.1.dist-info/top_level.txt @@ -0,0 +1 @@ +bidict diff --git a/venv/lib/python3.12/site-packages/bidict/__init__.py b/venv/lib/python3.12/site-packages/bidict/__init__.py new file mode 100644 index 0000000..07e5ba5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict/__init__.py @@ -0,0 +1,103 @@ +# Copyright 2009-2024 Joshua Bronson. All rights reserved. +# +# 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/. + + +# ============================================================================ +# * Welcome to the bidict source code * +# ============================================================================ + +# Reading through the code? You'll find a "Code review nav" comment like the one +# below at the top and bottom of the key source files. Follow these cues to take +# a path through the code that's optimized for familiarizing yourself with it. +# +# If you're not reading this on https://github.com/jab/bidict already, go there +# to ensure you have the latest version of the code. While there, you can also +# star the project, watch it for updates, fork the code, and submit an issue or +# pull request with any proposed changes. More information can be found linked +# from README.rst, which is also shown on https://github.com/jab/bidict. + +# * Code review nav * +# ============================================================================ +# Current: __init__.py Next: _abc.py → +# ============================================================================ + + +"""The bidirectional mapping library for Python. + +---- + +bidict by example: + +.. code-block:: python + + >>> from bidict import bidict + >>> element_by_symbol = bidict({'H': 'hydrogen'}) + >>> element_by_symbol['H'] + 'hydrogen' + >>> element_by_symbol.inverse['hydrogen'] + 'H' + + +Please see https://github.com/jab/bidict for the most up-to-date code and +https://bidict.readthedocs.io for the most up-to-date documentation +if you are reading this elsewhere. + +---- + +.. :copyright: (c) 2009-2024 Joshua Bronson. +.. :license: MPLv2. See LICENSE for details. +""" + +# Use private aliases to not re-export these publicly (for Sphinx automodule with imported-members). +from __future__ import annotations as _annotations + +from contextlib import suppress as _suppress + +from ._abc import BidirectionalMapping as BidirectionalMapping +from ._abc import MutableBidirectionalMapping as MutableBidirectionalMapping +from ._base import BidictBase as BidictBase +from ._base import BidictKeysView as BidictKeysView +from ._base import GeneratedBidictInverse as GeneratedBidictInverse +from ._bidict import MutableBidict as MutableBidict +from ._bidict import bidict as bidict +from ._dup import DROP_NEW as DROP_NEW +from ._dup import DROP_OLD as DROP_OLD +from ._dup import ON_DUP_DEFAULT as ON_DUP_DEFAULT +from ._dup import ON_DUP_DROP_OLD as ON_DUP_DROP_OLD +from ._dup import ON_DUP_RAISE as ON_DUP_RAISE +from ._dup import RAISE as RAISE +from ._dup import OnDup as OnDup +from ._dup import OnDupAction as OnDupAction +from ._exc import BidictException as BidictException +from ._exc import DuplicationError as DuplicationError +from ._exc import KeyAndValueDuplicationError as KeyAndValueDuplicationError +from ._exc import KeyDuplicationError as KeyDuplicationError +from ._exc import ValueDuplicationError as ValueDuplicationError +from ._frozen import frozenbidict as frozenbidict +from ._iter import inverted as inverted +from ._orderedbase import OrderedBidictBase as OrderedBidictBase +from ._orderedbidict import OrderedBidict as OrderedBidict +from .metadata import __author__ as __author__ +from .metadata import __copyright__ as __copyright__ +from .metadata import __description__ as __description__ +from .metadata import __license__ as __license__ +from .metadata import __url__ as __url__ +from .metadata import __version__ as __version__ + + +# Set __module__ of re-exported classes to the 'bidict' top-level module, so that e.g. +# 'bidict.bidict' shows up as 'bidict.bidict` rather than 'bidict._bidict.bidict'. +for _obj in tuple(locals().values()): # pragma: no cover + if not getattr(_obj, '__module__', '').startswith('bidict.'): + continue + with _suppress(AttributeError): + _obj.__module__ = 'bidict' + + +# * Code review nav * +# ============================================================================ +# Current: __init__.py Next: _abc.py → +# ============================================================================ diff --git a/venv/lib/python3.12/site-packages/bidict/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/bidict/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62a6d65d368b7ff81cf58498f9a98342d8dd3128 GIT binary patch literal 2483 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/bidict/__pycache__/_base.cpython-312.pyc b/venv/lib/python3.12/site-packages/bidict/__pycache__/_base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89b7bbf00e348e46e6c1d54d6c8ab227df320d04 GIT binary patch literal 23195 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/bidict/__pycache__/_bidict.cpython-312.pyc b/venv/lib/python3.12/site-packages/bidict/__pycache__/_bidict.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf2cebe489a6731bedf949a66980aba41ab32849 GIT binary patch literal 8497 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/bidict/__pycache__/_dup.cpython-312.pyc b/venv/lib/python3.12/site-packages/bidict/__pycache__/_dup.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b892bbd58bcb8fcfc699319553679b11f45fab98 GIT binary patch literal 2228 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 Q!cP0a^ecZO*e8Yl1GqYDB>(^b literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/bidict/__pycache__/_iter.cpython-312.pyc b/venv/lib/python3.12/site-packages/bidict/__pycache__/_iter.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eceb990ec45412b526906ace2ae0e845ef6d3ee1 GIT binary patch literal 2341 zcmaJ@&2JM&6rWkI?X{i6ag5BTV1dxqA=sobiU?6E(n5uz234d;xT5QgcWrO7-gRd; zNu4@sBv6}3NCc6RRO$h#96(W3wf_NJxin2A=r&YH6sg>3;gIIkH?!U(h^iyc>~B8a zdoyp|?~U&_Hii+D`}ck?t@{yrLOWjJU4UQA0hmW0Aq8cTf)!53WiHEMiha5-%fpw~ z{pCP50Aqe7pa;uBR$w>?IFt=BEC3E?!wiQ2N3s!y!+;yI4S=Icvl6-N&o**M4S*)R z$hmvSHYp8iRB7yit`bw4F7w&usxR3xarE@4k+(I|u*8B%M9o%-Y->hQG{;odEnA{y zh_YCgD-{?|J);Vf*onx7VcM`3L@MN@2VsIcPutKLn6{uH&wac(^~O6l**SxT zgIi}@yjUMP1$Xed51|{}JolyF4_1txSF8x0?1shz)2IMuG)udFVfE zglEv}qc-frEyE2S+Wgv09=4k|P>Y8O+)jWs{C{WlPVlS?Hv?|tJq z>6_@fpsBhd+9j1n282R^m}Rj|CdD>UHWaaKWW0@r2VGzi1tO-Xjj2XzfZe6HIeI^x z^e)>0e2v|^tf7nGy-8fdGh7YR-50nUcphVPrIANf#CCuO?x3X4;ZjE(JmUDku0@?B zic{|G7TXW#vH`$}m4Z}H6S_zyeo>{#5j63;vkGD||{aXfLgX}W1O z+I)3vW^7Ko#og+=8CXiZdAr43xg5jI~{~oe0{g)0!dc z4sVUil~*E&`UWl@vH^gtd7#Gf>0+HZlX#OuHN05Q79_+0Gdwm}G_2!)VKNcs0^=#h$ycU|s)YInw$w-}(YX;dLX8^b=sl*IPE9sE_*x z+r{&P7YR)TjI2(;_4P#TjQ7BKi;_H(Cx__TH=xhUY!X5f4M^1|H`0ciMQ98Y`j>?Hk9pFGO463c8njbS zu_F@dFP2@_g`TphjOuDHIRaypI@UYTJojOY|87RO>nS?$1hqUu`<|lhkLYjjBeVy) hF9QNT_E!SMb}w`+$ji;*6hBp5M&ipr6dz&+{sq{NK%)Qv literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/bidict/__pycache__/_orderedbase.cpython-312.pyc b/venv/lib/python3.12/site-packages/bidict/__pycache__/_orderedbase.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9fe7a129e2a3c0a6f43d74ccd6eb8f0c81b9dcc GIT binary patch literal 11152 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 Sy3Cdjmb&*m5U7Du_rC#X(B8@b literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/bidict/__pycache__/_orderedbidict.cpython-312.pyc b/venv/lib/python3.12/site-packages/bidict/__pycache__/_orderedbidict.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5082326a58db5f738fb8d6b7fa1527994a31ee48 GIT binary patch literal 7621 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 BidirectionalMapping[VT, KT]: + """The inverse of this bidirectional mapping instance. + + *See also* :attr:`bidict.BidictBase.inverse`, :attr:`bidict.BidictBase.inv` + + :raises NotImplementedError: Meant to be overridden in subclasses. + """ + # The @abstractmethod decorator prevents subclasses from being instantiated unless they + # override this method. But an overriding implementation may merely return super().inverse, + # in which case this implementation is used. Raise NotImplementedError to indicate that + # subclasses must actually provide their own implementation. + raise NotImplementedError + + def __inverted__(self) -> t.Iterator[tuple[VT, KT]]: + """Get an iterator over the items in :attr:`inverse`. + + This is functionally equivalent to iterating over the items in the + forward mapping and inverting each one on the fly, but this provides a + more efficient implementation: Assuming the already-inverted items + are stored in :attr:`inverse`, just return an iterator over them directly. + + Providing this default implementation enables external functions, + particularly :func:`~bidict.inverted`, to use this optimized + implementation when available, instead of having to invert on the fly. + + *See also* :func:`bidict.inverted` + """ + return iter(self.inverse.items()) + + +class MutableBidirectionalMapping(BidirectionalMapping[KT, VT], t.MutableMapping[KT, VT]): + """Abstract base class for mutable bidirectional mapping types.""" + + __slots__ = () + + +# * Code review nav * +# ============================================================================ +# ← Prev: __init__.py Current: _abc.py Next: _base.py → +# ============================================================================ diff --git a/venv/lib/python3.12/site-packages/bidict/_base.py b/venv/lib/python3.12/site-packages/bidict/_base.py new file mode 100644 index 0000000..848a376 --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict/_base.py @@ -0,0 +1,556 @@ +# Copyright 2009-2024 Joshua Bronson. All rights reserved. +# +# 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/. + + +# * Code review nav * +# (see comments in __init__.py) +# ============================================================================ +# ← Prev: _abc.py Current: _base.py Next: _frozen.py → +# ============================================================================ + + +"""Provide :class:`BidictBase`.""" + +from __future__ import annotations + +import typing as t +import weakref +from itertools import starmap +from operator import eq +from types import MappingProxyType + +from ._abc import BidirectionalMapping +from ._dup import DROP_NEW +from ._dup import DROP_OLD +from ._dup import ON_DUP_DEFAULT +from ._dup import RAISE +from ._dup import OnDup +from ._exc import DuplicationError +from ._exc import KeyAndValueDuplicationError +from ._exc import KeyDuplicationError +from ._exc import ValueDuplicationError +from ._iter import inverted +from ._iter import iteritems +from ._typing import KT +from ._typing import MISSING +from ._typing import OKT +from ._typing import OVT +from ._typing import VT +from ._typing import Maplike +from ._typing import MapOrItems + + +OldKV = t.Tuple[OKT[KT], OVT[VT]] +DedupResult = t.Optional[OldKV[KT, VT]] +Unwrites = t.List[t.Tuple[t.Any, ...]] +BT = t.TypeVar('BT', bound='BidictBase[t.Any, t.Any]') + + +class BidictKeysView(t.KeysView[KT], t.ValuesView[KT]): + """Since the keys of a bidict are the values of its inverse (and vice versa), + the :class:`~collections.abc.ValuesView` result of calling *bi.values()* + is also a :class:`~collections.abc.KeysView` of *bi.inverse*. + """ + + +class BidictBase(BidirectionalMapping[KT, VT]): + """Base class implementing :class:`BidirectionalMapping`.""" + + #: The default :class:`~bidict.OnDup` + #: that governs behavior when a provided item + #: duplicates the key or value of other item(s). + #: + #: *See also* + #: :ref:`basic-usage:Values Must Be Unique` (https://bidict.rtfd.io/basic-usage.html#values-must-be-unique), + #: :doc:`extending` (https://bidict.rtfd.io/extending.html) + on_dup = ON_DUP_DEFAULT + + _fwdm: t.MutableMapping[KT, VT] #: the backing forward mapping (*key* → *val*) + _invm: t.MutableMapping[VT, KT] #: the backing inverse mapping (*val* → *key*) + + # Use Any rather than KT/VT in the following to avoid "ClassVar cannot contain type variables" errors: + _fwdm_cls: t.ClassVar[type[t.MutableMapping[t.Any, t.Any]]] = dict #: class of the backing forward mapping + _invm_cls: t.ClassVar[type[t.MutableMapping[t.Any, t.Any]]] = dict #: class of the backing inverse mapping + + #: The class of the inverse bidict instance. + _inv_cls: t.ClassVar[type[BidictBase[t.Any, t.Any]]] + + def __init_subclass__(cls) -> None: + super().__init_subclass__() + cls._init_class() + + @classmethod + def _init_class(cls) -> None: + cls._ensure_inv_cls() + cls._set_reversed() + + __reversed__: t.ClassVar[t.Any] + + @classmethod + def _set_reversed(cls) -> None: + """Set __reversed__ for subclasses that do not set it explicitly + according to whether backing mappings are reversible. + """ + if cls is not BidictBase: + resolved = cls.__reversed__ + overridden = resolved is not BidictBase.__reversed__ + if overridden: # E.g. OrderedBidictBase, OrderedBidict + return + backing_reversible = all(issubclass(i, t.Reversible) for i in (cls._fwdm_cls, cls._invm_cls)) + cls.__reversed__ = _fwdm_reversed if backing_reversible else None + + @classmethod + def _ensure_inv_cls(cls) -> None: + """Ensure :attr:`_inv_cls` is set, computing it dynamically if necessary. + + All subclasses provided in :mod:`bidict` are their own inverse classes, + i.e., their backing forward and inverse mappings are both the same type, + but users may define subclasses where this is not the case. + This method ensures that the inverse class is computed correctly regardless. + + See: :ref:`extending:Dynamic Inverse Class Generation` + (https://bidict.rtfd.io/extending.html#dynamic-inverse-class-generation) + """ + # This _ensure_inv_cls() method is (indirectly) corecursive with _make_inv_cls() below + # in the case that we need to dynamically generate the inverse class: + # 1. _ensure_inv_cls() calls cls._make_inv_cls() + # 2. cls._make_inv_cls() calls type(..., (cls, ...), ...) to dynamically generate inv_cls + # 3. Our __init_subclass__ hook (see above) is automatically called on inv_cls + # 4. inv_cls.__init_subclass__() calls inv_cls._ensure_inv_cls() + # 5. inv_cls._ensure_inv_cls() resolves to this implementation + # (inv_cls deliberately does not override this), so we're back where we started. + # But since the _make_inv_cls() call will have set inv_cls.__dict__._inv_cls, + # just check if it's already set before calling _make_inv_cls() to prevent infinite recursion. + if getattr(cls, '__dict__', {}).get('_inv_cls'): # Don't assume cls.__dict__ (e.g. mypyc native class) + return + cls._inv_cls = cls._make_inv_cls() + + @classmethod + def _make_inv_cls(cls: type[BT]) -> type[BT]: + diff = cls._inv_cls_dict_diff() + cls_is_own_inv = all(getattr(cls, k, MISSING) == v for (k, v) in diff.items()) + if cls_is_own_inv: + return cls + # Suppress auto-calculation of _inv_cls's _inv_cls since we know it already. + # Works with the guard in BidictBase._ensure_inv_cls() to prevent infinite recursion. + diff['_inv_cls'] = cls + inv_cls = type(f'{cls.__name__}Inv', (cls, GeneratedBidictInverse), diff) + inv_cls.__module__ = cls.__module__ + return t.cast(t.Type[BT], inv_cls) + + @classmethod + def _inv_cls_dict_diff(cls) -> dict[str, t.Any]: + return { + '_fwdm_cls': cls._invm_cls, + '_invm_cls': cls._fwdm_cls, + } + + def __init__(self, arg: MapOrItems[KT, VT] = (), /, **kw: VT) -> None: + """Make a new bidirectional mapping. + The signature behaves like that of :class:`dict`. + ktems passed via positional arg are processed first, + followed by any items passed via keyword argument. + Any duplication encountered along the way + is handled as per :attr:`on_dup`. + """ + self._fwdm = self._fwdm_cls() + self._invm = self._invm_cls() + self._update(arg, kw, rollback=False) + + # If Python ever adds support for higher-kinded types, `inverse` could use them, e.g. + # def inverse(self: BT[KT, VT]) -> BT[VT, KT]: + # Ref: https://github.com/python/typing/issues/548#issuecomment-621571821 + @property + def inverse(self) -> BidictBase[VT, KT]: + """The inverse of this bidirectional mapping instance.""" + # When `bi.inverse` is called for the first time, this method + # computes the inverse instance, stores it for subsequent use, and then + # returns it. It also stores a reference on `bi.inverse` back to `bi`, + # but uses a weakref to avoid creating a reference cycle. Strong references + # to inverse instances are stored in ._inv, and weak references are stored + # in ._invweak. + + # First check if a strong reference is already stored. + inv: BidictBase[VT, KT] | None = getattr(self, '_inv', None) + if inv is not None: + return inv + # Next check if a weak reference is already stored. + invweak = getattr(self, '_invweak', None) + if invweak is not None: + inv = invweak() # Try to resolve a strong reference and return it. + if inv is not None: + return inv + # No luck. Compute the inverse reference and store it for subsequent use. + inv = self._make_inverse() + self._inv: BidictBase[VT, KT] | None = inv + self._invweak: weakref.ReferenceType[BidictBase[VT, KT]] | None = None + # Also store a weak reference back to `instance` on its inverse instance, so that + # the second `.inverse` access in `bi.inverse.inverse` hits the cached weakref. + inv._inv = None + inv._invweak = weakref.ref(self) + # In e.g. `bidict().inverse.inverse`, this design ensures that a strong reference + # back to the original instance is retained before its refcount drops to zero, + # avoiding an unintended potential deallocation. + return inv + + def _make_inverse(self) -> BidictBase[VT, KT]: + inv: BidictBase[VT, KT] = self._inv_cls() + inv._fwdm = self._invm + inv._invm = self._fwdm + return inv + + @property + def inv(self) -> BidictBase[VT, KT]: + """Alias for :attr:`inverse`.""" + return self.inverse + + def __repr__(self) -> str: + """See :func:`repr`.""" + clsname = self.__class__.__name__ + items = dict(self.items()) if self else '' + return f'{clsname}({items})' + + def values(self) -> BidictKeysView[VT]: + """A set-like object providing a view on the contained values. + + Since the values of a bidict are equivalent to the keys of its inverse, + this method returns a set-like object for this bidict's values + rather than just a collections.abc.ValuesView. + This object supports set operations like union and difference, + and constant- rather than linear-time containment checks, + and is no more expensive to provide than the less capable + collections.abc.ValuesView would be. + + See :meth:`keys` for more information. + """ + return t.cast(BidictKeysView[VT], self.inverse.keys()) + + def keys(self) -> t.KeysView[KT]: + """A set-like object providing a view on the contained keys. + + When *b._fwdm* is a :class:`dict`, *b.keys()* returns a + *dict_keys* object that behaves exactly the same as + *collections.abc.KeysView(b)*, except for + + - offering better performance + + - being reversible on Python 3.8+ + + - having a .mapping attribute in Python 3.10+ + that exposes a mappingproxy to *b._fwdm*. + """ + fwdm, fwdm_cls = self._fwdm, self._fwdm_cls + return fwdm.keys() if fwdm_cls is dict else BidictKeysView(self) + + def items(self) -> t.ItemsView[KT, VT]: + """A set-like object providing a view on the contained items. + + When *b._fwdm* is a :class:`dict`, *b.items()* returns a + *dict_items* object that behaves exactly the same as + *collections.abc.ItemsView(b)*, except for: + + - offering better performance + + - being reversible on Python 3.8+ + + - having a .mapping attribute in Python 3.10+ + that exposes a mappingproxy to *b._fwdm*. + """ + return self._fwdm.items() if self._fwdm_cls is dict else super().items() + + # The inherited collections.abc.Mapping.__contains__() method is implemented by doing a `try` + # `except KeyError` around `self[key]`. The following implementation is much faster, + # especially in the missing case. + def __contains__(self, key: t.Any) -> bool: + """True if the mapping contains the specified key, else False.""" + return key in self._fwdm + + # The inherited collections.abc.Mapping.__eq__() method is implemented in terms of an inefficient + # `dict(self.items()) == dict(other.items())` comparison, so override it with a + # more efficient implementation. + def __eq__(self, other: object) -> bool: + """*x.__eq__(other) ⟺ x == other* + + Equivalent to *dict(x.items()) == dict(other.items())* + but more efficient. + + Note that :meth:`bidict's __eq__() ` implementation + is inherited by subclasses, + in particular by the ordered bidict subclasses, + so even with ordered bidicts, + :ref:`== comparison is order-insensitive ` + (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive). + + *See also* :meth:`equals_order_sensitive` + """ + if isinstance(other, t.Mapping): + return self._fwdm.items() == other.items() + # Ref: https://docs.python.org/3/library/constants.html#NotImplemented + return NotImplemented + + def equals_order_sensitive(self, other: object) -> bool: + """Order-sensitive equality check. + + *See also* :ref:`eq-order-insensitive` + (https://bidict.rtfd.io/other-bidict-types.html#eq-is-order-insensitive) + """ + if not isinstance(other, t.Mapping) or len(self) != len(other): + return False + return all(starmap(eq, zip(self.items(), other.items()))) + + def _dedup(self, key: KT, val: VT, on_dup: OnDup) -> DedupResult[KT, VT]: + """Check *key* and *val* for any duplication in self. + + Handle any duplication as per the passed in *on_dup*. + + If (key, val) is already present, return None + since writing (key, val) would be a no-op. + + If duplication is found and the corresponding :class:`~bidict.OnDupAction` is + :attr:`~bidict.DROP_NEW`, return None. + + If duplication is found and the corresponding :class:`~bidict.OnDupAction` is + :attr:`~bidict.RAISE`, raise the appropriate exception. + + If duplication is found and the corresponding :class:`~bidict.OnDupAction` is + :attr:`~bidict.DROP_OLD`, or if no duplication is found, + return *(oldkey, oldval)*. + """ + fwdm, invm = self._fwdm, self._invm + oldval: OVT[VT] = fwdm.get(key, MISSING) + oldkey: OKT[KT] = invm.get(val, MISSING) + isdupkey, isdupval = oldval is not MISSING, oldkey is not MISSING + if isdupkey and isdupval: + if key == oldkey: + assert val == oldval + # (key, val) duplicates an existing item -> no-op. + return None + # key and val each duplicate a different existing item. + if on_dup.val is RAISE: + raise KeyAndValueDuplicationError(key, val) + if on_dup.val is DROP_NEW: + return None + assert on_dup.val is DROP_OLD + # Fall through to the return statement on the last line. + elif isdupkey: + if on_dup.key is RAISE: + raise KeyDuplicationError(key) + if on_dup.key is DROP_NEW: + return None + assert on_dup.key is DROP_OLD + # Fall through to the return statement on the last line. + elif isdupval: + if on_dup.val is RAISE: + raise ValueDuplicationError(val) + if on_dup.val is DROP_NEW: + return None + assert on_dup.val is DROP_OLD + # Fall through to the return statement on the last line. + # else neither isdupkey nor isdupval. + return oldkey, oldval + + def _write(self, newkey: KT, newval: VT, oldkey: OKT[KT], oldval: OVT[VT], unwrites: Unwrites | None) -> None: + """Insert (newkey, newval), extending *unwrites* with associated inverse operations if provided. + + *oldkey* and *oldval* are as returned by :meth:`_dedup`. + + If *unwrites* is not None, it is extended with the inverse operations necessary to undo the write. + This design allows :meth:`_update` to roll back a partially applied update that fails part-way through + when necessary. + + This design also allows subclasses that require additional operations to easily extend this implementation. + For example, :class:`bidict.OrderedBidictBase` calls this inherited implementation, and then extends *unwrites* + with additional operations needed to keep its internal linked list nodes consistent with its items' order + as changes are made. + """ + fwdm, invm = self._fwdm, self._invm + fwdm_set, invm_set = fwdm.__setitem__, invm.__setitem__ + fwdm_del, invm_del = fwdm.__delitem__, invm.__delitem__ + # Always perform the following writes regardless of duplication. + fwdm_set(newkey, newval) + invm_set(newval, newkey) + if oldval is MISSING and oldkey is MISSING: # no key or value duplication + # {0: 1, 2: 3} | {4: 5} => {0: 1, 2: 3, 4: 5} + if unwrites is not None: + unwrites.extend(( + (fwdm_del, newkey), + (invm_del, newval), + )) + elif oldval is not MISSING and oldkey is not MISSING: # key and value duplication across two different items + # {0: 1, 2: 3} | {0: 3} => {0: 3} + fwdm_del(oldkey) + invm_del(oldval) + if unwrites is not None: + unwrites.extend(( + (fwdm_set, newkey, oldval), + (invm_set, oldval, newkey), + (fwdm_set, oldkey, newval), + (invm_set, newval, oldkey), + )) + elif oldval is not MISSING: # just key duplication + # {0: 1, 2: 3} | {2: 4} => {0: 1, 2: 4} + invm_del(oldval) + if unwrites is not None: + unwrites.extend(( + (fwdm_set, newkey, oldval), + (invm_set, oldval, newkey), + (invm_del, newval), + )) + else: + assert oldkey is not MISSING # just value duplication + # {0: 1, 2: 3} | {4: 3} => {0: 1, 4: 3} + fwdm_del(oldkey) + if unwrites is not None: + unwrites.extend(( + (fwdm_set, oldkey, newval), + (invm_set, newval, oldkey), + (fwdm_del, newkey), + )) + + def _update( + self, + arg: MapOrItems[KT, VT], + kw: t.Mapping[str, VT] = MappingProxyType({}), + *, + rollback: bool | None = None, + on_dup: OnDup | None = None, + ) -> None: + """Update with the items from *arg* and *kw*, maybe failing and rolling back as per *on_dup* and *rollback*.""" + # Note: We must process input in a single pass, since arg may be a generator. + if not isinstance(arg, (t.Iterable, Maplike)): + raise TypeError(f"'{arg.__class__.__name__}' object is not iterable") + if not arg and not kw: + return + if on_dup is None: + on_dup = self.on_dup + if rollback is None: + rollback = RAISE in on_dup + + # Fast path when we're empty and updating only from another bidict (i.e. no dup vals in new items). + if not self and not kw and isinstance(arg, BidictBase): + self._init_from(arg) + return + + # Fast path when we're adding more items than we contain already and rollback is enabled: + # Update a copy of self with rollback disabled. Fail if that fails, otherwise become the copy. + if rollback and isinstance(arg, t.Sized) and len(arg) + len(kw) > len(self): + tmp = self.copy() + tmp._update(arg, kw, rollback=False, on_dup=on_dup) + self._init_from(tmp) + return + + # In all other cases, benchmarking has indicated that the update is best implemented as follows: + # For each new item, perform a dup check (raising if necessary), and apply the associated writes we need to + # perform on our backing _fwdm and _invm mappings. If rollback is enabled, also compute the associated unwrites + # as we go. If the update results in a DuplicationError and rollback is enabled, apply the accumulated unwrites + # before raising, to ensure that we fail clean. + write = self._write + unwrites: Unwrites | None = [] if rollback else None + for key, val in iteritems(arg, **kw): + try: + dedup_result = self._dedup(key, val, on_dup) + except DuplicationError: + if unwrites is not None: + for fn, *args in reversed(unwrites): + fn(*args) + raise + if dedup_result is not None: + write(key, val, *dedup_result, unwrites=unwrites) + + def __copy__(self: BT) -> BT: + """Used for the copy protocol. See the :mod:`copy` module.""" + return self.copy() + + def copy(self: BT) -> BT: + """Make a (shallow) copy of this bidict.""" + # Could just `return self.__class__(self)` here, but the below is faster. The former + # would copy this bidict's items into a new instance one at a time (checking for duplication + # for each item), whereas the below copies from the backing mappings all at once, and foregoes + # item-by-item duplication checking since the backing mappings have been checked already. + return self._from_other(self.__class__, self) + + @staticmethod + def _from_other(bt: type[BT], other: MapOrItems[KT, VT], inv: bool = False) -> BT: + """Fast, private constructor based on :meth:`_init_from`. + + If *inv* is true, return the inverse of the instance instead of the instance itself. + (Useful for pickling with dynamically-generated inverse classes -- see :meth:`__reduce__`.) + """ + inst = bt() + inst._init_from(other) + return t.cast(BT, inst.inverse) if inv else inst + + def _init_from(self, other: MapOrItems[KT, VT]) -> None: + """Fast init from *other*, bypassing item-by-item duplication checking.""" + self._fwdm.clear() + self._invm.clear() + self._fwdm.update(other) + # If other is a bidict, use its existing backing inverse mapping, otherwise + # other could be a generator that's now exhausted, so invert self._fwdm on the fly. + inv = other.inverse if isinstance(other, BidictBase) else inverted(self._fwdm) + self._invm.update(inv) + + # other's type is Mapping rather than Maplike since bidict() | SupportsKeysAndGetItem({}) + # raises a TypeError, just like dict() | SupportsKeysAndGetItem({}) does. + def __or__(self: BT, other: t.Mapping[KT, VT]) -> BT: + """Return self|other.""" + if not isinstance(other, t.Mapping): + return NotImplemented + new = self.copy() + new._update(other, rollback=False) + return new + + def __ror__(self: BT, other: t.Mapping[KT, VT]) -> BT: + """Return other|self.""" + if not isinstance(other, t.Mapping): + return NotImplemented + new = self.__class__(other) + new._update(self, rollback=False) + return new + + def __len__(self) -> int: + """The number of contained items.""" + return len(self._fwdm) + + def __iter__(self) -> t.Iterator[KT]: + """Iterator over the contained keys.""" + return iter(self._fwdm) + + def __getitem__(self, key: KT) -> VT: + """*x.__getitem__(key) ⟺ x[key]*""" + return self._fwdm[key] + + def __reduce__(self) -> tuple[t.Any, ...]: + """Return state information for pickling.""" + cls = self.__class__ + inst: t.Mapping[t.Any, t.Any] = self + # If this bidict's class is dynamically generated, pickle the inverse instead, whose (presumably not + # dynamically generated) class the caller is more likely to have a reference to somewhere in sys.modules + # that pickle can discover. + if should_invert := isinstance(self, GeneratedBidictInverse): + cls = self._inv_cls + inst = self.inverse + return self._from_other, (cls, dict(inst), should_invert) + + +# See BidictBase._set_reversed() above. +def _fwdm_reversed(self: BidictBase[KT, t.Any]) -> t.Iterator[KT]: + """Iterator over the contained keys in reverse order.""" + assert isinstance(self._fwdm, t.Reversible) + return reversed(self._fwdm) + + +BidictBase._init_class() + + +class GeneratedBidictInverse: + """Base class for dynamically-generated inverse bidict classes.""" + + +# * Code review nav * +# ============================================================================ +# ← Prev: _abc.py Current: _base.py Next: _frozen.py → +# ============================================================================ diff --git a/venv/lib/python3.12/site-packages/bidict/_bidict.py b/venv/lib/python3.12/site-packages/bidict/_bidict.py new file mode 100644 index 0000000..94dd3db --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict/_bidict.py @@ -0,0 +1,194 @@ +# Copyright 2009-2024 Joshua Bronson. All rights reserved. +# +# 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/. + + +# * Code review nav * +# (see comments in __init__.py) +# ============================================================================ +# ← Prev: _frozen.py Current: _bidict.py Next: _orderedbase.py → +# ============================================================================ + + +"""Provide :class:`MutableBidict` and :class:`bidict`.""" + +from __future__ import annotations + +import typing as t + +from ._abc import MutableBidirectionalMapping +from ._base import BidictBase +from ._dup import ON_DUP_DROP_OLD +from ._dup import ON_DUP_RAISE +from ._dup import OnDup +from ._typing import DT +from ._typing import KT +from ._typing import MISSING +from ._typing import ODT +from ._typing import VT +from ._typing import MapOrItems + + +class MutableBidict(BidictBase[KT, VT], MutableBidirectionalMapping[KT, VT]): + """Base class for mutable bidirectional mappings.""" + + if t.TYPE_CHECKING: + + @property + def inverse(self) -> MutableBidict[VT, KT]: ... + + @property + def inv(self) -> MutableBidict[VT, KT]: ... + + def _pop(self, key: KT) -> VT: + val = self._fwdm.pop(key) + del self._invm[val] + return val + + def __delitem__(self, key: KT) -> None: + """*x.__delitem__(y) ⟺ del x[y]*""" + self._pop(key) + + def __setitem__(self, key: KT, val: VT) -> None: + """Set the value for *key* to *val*. + + If *key* is already associated with *val*, this is a no-op. + + If *key* is already associated with a different value, + the old value will be replaced with *val*, + as with dict's :meth:`__setitem__`. + + If *val* is already associated with a different key, + an exception is raised + to protect against accidental removal of the key + that's currently associated with *val*. + + Use :meth:`put` instead if you want to specify different behavior in + the case that the provided key or value duplicates an existing one. + Or use :meth:`forceput` to unconditionally associate *key* with *val*, + replacing any existing items as necessary to preserve uniqueness. + + :raises bidict.ValueDuplicationError: if *val* duplicates that of an + existing item. + + :raises bidict.KeyAndValueDuplicationError: if *key* duplicates the key of an + existing item and *val* duplicates the value of a different + existing item. + """ + self.put(key, val, on_dup=self.on_dup) + + def put(self, key: KT, val: VT, on_dup: OnDup = ON_DUP_RAISE) -> None: + """Associate *key* with *val*, honoring the :class:`OnDup` given in *on_dup*. + + For example, if *on_dup* is :attr:`~bidict.ON_DUP_RAISE`, + then *key* will be associated with *val* if and only if + *key* is not already associated with an existing value and + *val* is not already associated with an existing key, + otherwise an exception will be raised. + + If *key* is already associated with *val*, this is a no-op. + + :raises bidict.KeyDuplicationError: if attempting to insert an item + whose key only duplicates an existing item's, and *on_dup.key* is + :attr:`~bidict.RAISE`. + + :raises bidict.ValueDuplicationError: if attempting to insert an item + whose value only duplicates an existing item's, and *on_dup.val* is + :attr:`~bidict.RAISE`. + + :raises bidict.KeyAndValueDuplicationError: if attempting to insert an + item whose key duplicates one existing item's, and whose value + duplicates another existing item's, and *on_dup.val* is + :attr:`~bidict.RAISE`. + """ + self._update(((key, val),), on_dup=on_dup) + + def forceput(self, key: KT, val: VT) -> None: + """Associate *key* with *val* unconditionally. + + Replace any existing mappings containing key *key* or value *val* + as necessary to preserve uniqueness. + """ + self.put(key, val, on_dup=ON_DUP_DROP_OLD) + + def clear(self) -> None: + """Remove all items.""" + self._fwdm.clear() + self._invm.clear() + + @t.overload + def pop(self, key: KT, /) -> VT: ... + @t.overload + def pop(self, key: KT, default: DT = ..., /) -> VT | DT: ... + + def pop(self, key: KT, default: ODT[DT] = MISSING, /) -> VT | DT: + """*x.pop(k[, d]) → v* + + Remove specified key and return the corresponding value. + + :raises KeyError: if *key* is not found and no *default* is provided. + """ + try: + return self._pop(key) + except KeyError: + if default is MISSING: + raise + return default + + def popitem(self) -> tuple[KT, VT]: + """*x.popitem() → (k, v)* + + Remove and return some item as a (key, value) pair. + + :raises KeyError: if *x* is empty. + """ + key, val = self._fwdm.popitem() + del self._invm[val] + return key, val + + def update(self, arg: MapOrItems[KT, VT] = (), /, **kw: VT) -> None: + """Like calling :meth:`putall` with *self.on_dup* passed for *on_dup*.""" + self._update(arg, kw=kw) + + def forceupdate(self, arg: MapOrItems[KT, VT] = (), /, **kw: VT) -> None: + """Like a bulk :meth:`forceput`.""" + self._update(arg, kw=kw, on_dup=ON_DUP_DROP_OLD) + + def putall(self, items: MapOrItems[KT, VT], on_dup: OnDup = ON_DUP_RAISE) -> None: + """Like a bulk :meth:`put`. + + If one of the given items causes an exception to be raised, + none of the items is inserted. + """ + self._update(items, on_dup=on_dup) + + # other's type is Mapping rather than Maplike since bidict() |= SupportsKeysAndGetItem({}) + # raises a TypeError, just like dict() |= SupportsKeysAndGetItem({}) does. + def __ior__(self, other: t.Mapping[KT, VT]) -> MutableBidict[KT, VT]: + """Return self|=other.""" + self.update(other) + return self + + +class bidict(MutableBidict[KT, VT]): + """The main bidirectional mapping type. + + See :ref:`intro:Introduction` and :ref:`basic-usage:Basic Usage` + to get started (also available at https://bidict.rtfd.io). + """ + + if t.TYPE_CHECKING: + + @property + def inverse(self) -> bidict[VT, KT]: ... + + @property + def inv(self) -> bidict[VT, KT]: ... + + +# * Code review nav * +# ============================================================================ +# ← Prev: _frozen.py Current: _bidict.py Next: _orderedbase.py → +# ============================================================================ diff --git a/venv/lib/python3.12/site-packages/bidict/_dup.py b/venv/lib/python3.12/site-packages/bidict/_dup.py new file mode 100644 index 0000000..fd25b61 --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict/_dup.py @@ -0,0 +1,61 @@ +# Copyright 2009-2024 Joshua Bronson. All rights reserved. +# +# 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/. + + +"""Provide :class:`OnDup` and related functionality.""" + +from __future__ import annotations + +import typing as t +from enum import Enum + + +class OnDupAction(Enum): + """An action to take to prevent duplication from occurring.""" + + #: Raise a :class:`~bidict.DuplicationError`. + RAISE = 'RAISE' + #: Overwrite existing items with new items. + DROP_OLD = 'DROP_OLD' + #: Keep existing items and drop new items. + DROP_NEW = 'DROP_NEW' + + def __repr__(self) -> str: + return f'{self.__class__.__name__}.{self.name}' + + +RAISE: t.Final[OnDupAction] = OnDupAction.RAISE +DROP_OLD: t.Final[OnDupAction] = OnDupAction.DROP_OLD +DROP_NEW: t.Final[OnDupAction] = OnDupAction.DROP_NEW + + +class OnDup(t.NamedTuple): + r"""A combination of :class:`~bidict.OnDupAction`\s specifying how to handle various types of duplication. + + The :attr:`~OnDup.key` field specifies what action to take when a duplicate key is encountered. + + The :attr:`~OnDup.val` field specifies what action to take when a duplicate value is encountered. + + In the case of both key and value duplication across two different items, + only :attr:`~OnDup.val` is used. + + *See also* :ref:`basic-usage:Values Must Be Unique` + (https://bidict.rtfd.io/basic-usage.html#values-must-be-unique) + """ + + key: OnDupAction = DROP_OLD + val: OnDupAction = RAISE + + +#: Default :class:`OnDup` used for the +#: :meth:`~bidict.bidict.__init__`, +#: :meth:`~bidict.bidict.__setitem__`, and +#: :meth:`~bidict.bidict.update` methods. +ON_DUP_DEFAULT: t.Final[OnDup] = OnDup(key=DROP_OLD, val=RAISE) +#: An :class:`OnDup` whose members are all :obj:`RAISE`. +ON_DUP_RAISE: t.Final[OnDup] = OnDup(key=RAISE, val=RAISE) +#: An :class:`OnDup` whose members are all :obj:`DROP_OLD`. +ON_DUP_DROP_OLD: t.Final[OnDup] = OnDup(key=DROP_OLD, val=DROP_OLD) diff --git a/venv/lib/python3.12/site-packages/bidict/_exc.py b/venv/lib/python3.12/site-packages/bidict/_exc.py new file mode 100644 index 0000000..e2a96f3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict/_exc.py @@ -0,0 +1,36 @@ +# Copyright 2009-2024 Joshua Bronson. All rights reserved. +# +# 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/. + + +"""Provide all bidict exceptions.""" + +from __future__ import annotations + + +class BidictException(Exception): + """Base class for bidict exceptions.""" + + +class DuplicationError(BidictException): + """Base class for exceptions raised when uniqueness is violated + as per the :attr:`~bidict.RAISE` :class:`~bidict.OnDupAction`. + """ + + +class KeyDuplicationError(DuplicationError): + """Raised when a given key is not unique.""" + + +class ValueDuplicationError(DuplicationError): + """Raised when a given value is not unique.""" + + +class KeyAndValueDuplicationError(KeyDuplicationError, ValueDuplicationError): + """Raised when a given item's key and value are not unique. + + That is, its key duplicates that of another item, + and its value duplicates that of a different other item. + """ diff --git a/venv/lib/python3.12/site-packages/bidict/_frozen.py b/venv/lib/python3.12/site-packages/bidict/_frozen.py new file mode 100644 index 0000000..e2f789d --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict/_frozen.py @@ -0,0 +1,50 @@ +# Copyright 2009-2024 Joshua Bronson. All rights reserved. +# +# 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/. + + +# * Code review nav * +# (see comments in __init__.py) +# ============================================================================ +# ← Prev: _base.py Current: _frozen.py Next: _bidict.py → +# ============================================================================ + +"""Provide :class:`frozenbidict`, an immutable, hashable bidirectional mapping type.""" + +from __future__ import annotations + +import typing as t + +from ._base import BidictBase +from ._typing import KT +from ._typing import VT + + +class frozenbidict(BidictBase[KT, VT]): + """Immutable, hashable bidict type.""" + + _hash: int + + if t.TYPE_CHECKING: + + @property + def inverse(self) -> frozenbidict[VT, KT]: ... + + @property + def inv(self) -> frozenbidict[VT, KT]: ... + + def __hash__(self) -> int: + """The hash of this bidict as determined by its items.""" + if getattr(self, '_hash', None) is None: + # The following is like hash(frozenset(self.items())) + # but more memory efficient. See also: https://bugs.python.org/issue46684 + self._hash = t.ItemsView(self)._hash() + return self._hash + + +# * Code review nav * +# ============================================================================ +# ← Prev: _base.py Current: _frozen.py Next: _bidict.py → +# ============================================================================ diff --git a/venv/lib/python3.12/site-packages/bidict/_iter.py b/venv/lib/python3.12/site-packages/bidict/_iter.py new file mode 100644 index 0000000..53ad25d --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict/_iter.py @@ -0,0 +1,51 @@ +# Copyright 2009-2024 Joshua Bronson. All rights reserved. +# +# 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/. + + +"""Functions for iterating over items in a mapping.""" + +from __future__ import annotations + +import typing as t +from operator import itemgetter + +from ._typing import KT +from ._typing import VT +from ._typing import ItemsIter +from ._typing import Maplike +from ._typing import MapOrItems + + +def iteritems(arg: MapOrItems[KT, VT] = (), /, **kw: VT) -> ItemsIter[KT, VT]: + """Yield the items from *arg* and *kw* in the order given.""" + if isinstance(arg, t.Mapping): + yield from arg.items() + elif isinstance(arg, Maplike): + yield from ((k, arg[k]) for k in arg.keys()) + else: + yield from arg + yield from t.cast(ItemsIter[KT, VT], kw.items()) + + +swap: t.Final = itemgetter(1, 0) + + +def inverted(arg: MapOrItems[KT, VT]) -> ItemsIter[VT, KT]: + """Yield the inverse items of the provided object. + + If *arg* has a :func:`callable` ``__inverted__`` attribute, + return the result of calling it. + + Otherwise, return an iterator over the items in `arg`, + inverting each item on the fly. + + *See also* :attr:`bidict.BidirectionalMapping.__inverted__` + """ + invattr = getattr(arg, '__inverted__', None) + if callable(invattr): + inv: ItemsIter[VT, KT] = invattr() + return inv + return map(swap, iteritems(arg)) diff --git a/venv/lib/python3.12/site-packages/bidict/_orderedbase.py b/venv/lib/python3.12/site-packages/bidict/_orderedbase.py new file mode 100644 index 0000000..92f2633 --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict/_orderedbase.py @@ -0,0 +1,238 @@ +# Copyright 2009-2024 Joshua Bronson. All rights reserved. +# +# 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/. + + +# * Code review nav * +# (see comments in __init__.py) +# ============================================================================ +# ← Prev: _bidict.py Current: _orderedbase.py Next: _orderedbidict.py → +# ============================================================================ + + +"""Provide :class:`OrderedBidictBase`.""" + +from __future__ import annotations + +import typing as t +from weakref import ref as weakref + +from ._base import BidictBase +from ._base import Unwrites +from ._bidict import bidict +from ._iter import iteritems +from ._typing import KT +from ._typing import MISSING +from ._typing import OKT +from ._typing import OVT +from ._typing import VT +from ._typing import MapOrItems + + +AT = t.TypeVar('AT') # attr type + + +class WeakAttr(t.Generic[AT]): + """Descriptor to automatically manage (de)referencing the given slot as a weakref. + + See https://docs.python.org/3/howto/descriptor.html#managed-attributes + for an intro to using descriptors like this for managed attributes. + """ + + def __init__(self, *, slot: str) -> None: + self.slot = slot + + def __set__(self, instance: t.Any, value: AT) -> None: + setattr(instance, self.slot, weakref(value)) + + def __get__(self, instance: t.Any, __owner: t.Any = None) -> AT: + return t.cast(AT, getattr(instance, self.slot)()) + + +class Node: + """A node in a circular doubly-linked list + used to encode the order of items in an ordered bidict. + + A weak reference to the previous node is stored + to avoid creating strong reference cycles. + Referencing/dereferencing the weakref is handled automatically by :class:`WeakAttr`. + """ + + prv: WeakAttr[Node] = WeakAttr(slot='_prv_weak') + __slots__ = ('__weakref__', '_prv_weak', 'nxt') + + nxt: Node | WeakAttr[Node] # Allow subclasses to use a WeakAttr for nxt too (see SentinelNode) + + def __init__(self, prv: Node, nxt: Node) -> None: + self.prv = prv + self.nxt = nxt + + def unlink(self) -> None: + """Remove self from in between prv and nxt. + Self's references to prv and nxt are retained so it can be relinked (see below). + """ + self.prv.nxt = self.nxt + self.nxt.prv = self.prv + + def relink(self) -> None: + """Restore self between prv and nxt after unlinking (see above).""" + self.prv.nxt = self.nxt.prv = self + + +class SentinelNode(Node): + """Special node in a circular doubly-linked list + that links the first node with the last node. + When its next and previous references point back to itself + it represents an empty list. + """ + + nxt: WeakAttr[Node] = WeakAttr(slot='_nxt_weak') + __slots__ = ('_nxt_weak',) + + def __init__(self) -> None: + super().__init__(self, self) + + def iternodes(self, *, reverse: bool = False) -> t.Iterator[Node]: + """Iterator yielding nodes in the requested order.""" + attr = 'prv' if reverse else 'nxt' + node = getattr(self, attr) + while node is not self: + yield node + node = getattr(node, attr) + + def new_last_node(self) -> Node: + """Create and return a new terminal node.""" + old_last = self.prv + new_last = Node(old_last, self) + old_last.nxt = self.prv = new_last + return new_last + + +class OrderedBidictBase(BidictBase[KT, VT]): + """Base class implementing an ordered :class:`BidirectionalMapping`.""" + + _node_by_korv: bidict[t.Any, Node] + _bykey: bool + + def __init__(self, arg: MapOrItems[KT, VT] = (), /, **kw: VT) -> None: + """Make a new ordered bidirectional mapping. + The signature behaves like that of :class:`dict`. + Items passed in are added in the order they are passed, + respecting the :attr:`~bidict.BidictBase.on_dup` + class attribute in the process. + + The order in which items are inserted is remembered, + similar to :class:`collections.OrderedDict`. + """ + self._sntl = SentinelNode() + self._node_by_korv = bidict() + self._bykey = True + super().__init__(arg, **kw) + + if t.TYPE_CHECKING: + + @property + def inverse(self) -> OrderedBidictBase[VT, KT]: ... + + @property + def inv(self) -> OrderedBidictBase[VT, KT]: ... + + def _make_inverse(self) -> OrderedBidictBase[VT, KT]: + inv = t.cast(OrderedBidictBase[VT, KT], super()._make_inverse()) + inv._sntl = self._sntl + inv._node_by_korv = self._node_by_korv + inv._bykey = not self._bykey + return inv + + def _assoc_node(self, node: Node, key: KT, val: VT) -> None: + korv = key if self._bykey else val + self._node_by_korv.forceput(korv, node) + + def _dissoc_node(self, node: Node) -> None: + del self._node_by_korv.inverse[node] + node.unlink() + + def _init_from(self, other: MapOrItems[KT, VT]) -> None: + """See :meth:`BidictBase._init_from`.""" + super()._init_from(other) + bykey = self._bykey + korv_by_node = self._node_by_korv.inverse + korv_by_node.clear() + korv_by_node_set = korv_by_node.__setitem__ + self._sntl.nxt = self._sntl.prv = self._sntl + new_node = self._sntl.new_last_node + for k, v in iteritems(other): + korv_by_node_set(new_node(), k if bykey else v) + + def _write(self, newkey: KT, newval: VT, oldkey: OKT[KT], oldval: OVT[VT], unwrites: Unwrites | None) -> None: + """See :meth:`bidict.BidictBase._spec_write`.""" + super()._write(newkey, newval, oldkey, oldval, unwrites) + assoc, dissoc = self._assoc_node, self._dissoc_node + node_by_korv, bykey = self._node_by_korv, self._bykey + if oldval is MISSING and oldkey is MISSING: # no key or value duplication + # {0: 1, 2: 3} | {4: 5} => {0: 1, 2: 3, 4: 5} + newnode = self._sntl.new_last_node() + assoc(newnode, newkey, newval) + if unwrites is not None: + unwrites.append((dissoc, newnode)) + elif oldval is not MISSING and oldkey is not MISSING: # key and value duplication across two different items + # {0: 1, 2: 3} | {0: 3} => {0: 3} + # n1, n2 => n1 (collapse n1 and n2 into n1) + # oldkey: 2, oldval: 1, oldnode: n2, newkey: 0, newval: 3, newnode: n1 + if bykey: + oldnode = node_by_korv[oldkey] + newnode = node_by_korv[newkey] + else: + oldnode = node_by_korv[newval] + newnode = node_by_korv[oldval] + dissoc(oldnode) + assoc(newnode, newkey, newval) + if unwrites is not None: + unwrites.extend(( + (assoc, newnode, newkey, oldval), + (assoc, oldnode, oldkey, newval), + (oldnode.relink,), + )) + elif oldval is not MISSING: # just key duplication + # {0: 1, 2: 3} | {2: 4} => {0: 1, 2: 4} + # oldkey: MISSING, oldval: 3, newkey: 2, newval: 4 + node = node_by_korv[newkey if bykey else oldval] + assoc(node, newkey, newval) + if unwrites is not None: + unwrites.append((assoc, node, newkey, oldval)) + else: + assert oldkey is not MISSING # just value duplication + # {0: 1, 2: 3} | {4: 3} => {0: 1, 4: 3} + # oldkey: 2, oldval: MISSING, newkey: 4, newval: 3 + node = node_by_korv[oldkey if bykey else newval] + assoc(node, newkey, newval) + if unwrites is not None: + unwrites.append((assoc, node, oldkey, newval)) + + def __iter__(self) -> t.Iterator[KT]: + """Iterator over the contained keys in insertion order.""" + return self._iter(reverse=False) + + def __reversed__(self) -> t.Iterator[KT]: + """Iterator over the contained keys in reverse insertion order.""" + return self._iter(reverse=True) + + def _iter(self, *, reverse: bool = False) -> t.Iterator[KT]: + nodes = self._sntl.iternodes(reverse=reverse) + korv_by_node = self._node_by_korv.inverse + if self._bykey: + for node in nodes: + yield korv_by_node[node] + else: + key_by_val = self._invm + for node in nodes: + val = korv_by_node[node] + yield key_by_val[val] + + +# * Code review nav * +# ============================================================================ +# ← Prev: _bidict.py Current: _orderedbase.py Next: _orderedbidict.py → +# ============================================================================ diff --git a/venv/lib/python3.12/site-packages/bidict/_orderedbidict.py b/venv/lib/python3.12/site-packages/bidict/_orderedbidict.py new file mode 100644 index 0000000..2fb1757 --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict/_orderedbidict.py @@ -0,0 +1,172 @@ +# Copyright 2009-2024 Joshua Bronson. All rights reserved. +# +# 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/. + + +# * Code review nav * +# (see comments in __init__.py) +# ============================================================================ +# ← Prev: _orderedbase.py Current: _orderedbidict.py +# ============================================================================ + + +"""Provide :class:`OrderedBidict`.""" + +from __future__ import annotations + +import typing as t +from collections.abc import Set + +from ._base import BidictKeysView +from ._bidict import MutableBidict +from ._orderedbase import OrderedBidictBase +from ._typing import KT +from ._typing import VT + + +class OrderedBidict(OrderedBidictBase[KT, VT], MutableBidict[KT, VT]): + """Mutable bidict type that maintains items in insertion order.""" + + if t.TYPE_CHECKING: + + @property + def inverse(self) -> OrderedBidict[VT, KT]: ... + + @property + def inv(self) -> OrderedBidict[VT, KT]: ... + + def clear(self) -> None: + """Remove all items.""" + super().clear() + self._node_by_korv.clear() + self._sntl.nxt = self._sntl.prv = self._sntl + + def _pop(self, key: KT) -> VT: + val = super()._pop(key) + node = self._node_by_korv[key if self._bykey else val] + self._dissoc_node(node) + return val + + def popitem(self, last: bool = True) -> tuple[KT, VT]: + """*b.popitem() → (k, v)* + + If *last* is true, + remove and return the most recently added item as a (key, value) pair. + Otherwise, remove and return the least recently added item. + + :raises KeyError: if *b* is empty. + """ + if not self: + raise KeyError('OrderedBidict is empty') + node = getattr(self._sntl, 'prv' if last else 'nxt') + korv = self._node_by_korv.inverse[node] + if self._bykey: + return korv, self._pop(korv) + return self.inverse._pop(korv), korv + + def move_to_end(self, key: KT, last: bool = True) -> None: + """Move the item with the given key to the end if *last* is true, else to the beginning. + + :raises KeyError: if *key* is missing + """ + korv = key if self._bykey else self._fwdm[key] + node = self._node_by_korv[korv] + node.prv.nxt = node.nxt + node.nxt.prv = node.prv + sntl = self._sntl + if last: + lastnode = sntl.prv + node.prv = lastnode + node.nxt = sntl + sntl.prv = lastnode.nxt = node + else: + firstnode = sntl.nxt + node.prv = sntl + node.nxt = firstnode + sntl.nxt = firstnode.prv = node + + # Override the keys() and items() implementations inherited from BidictBase, + # which may delegate to the backing _fwdm dict, since this is a mutable ordered bidict, + # and therefore the ordering of items can get out of sync with the backing mappings + # after mutation. (Need not override values() because it delegates to .inverse.keys().) + def keys(self) -> t.KeysView[KT]: + """A set-like object providing a view on the contained keys.""" + return _OrderedBidictKeysView(self) + + def items(self) -> t.ItemsView[KT, VT]: + """A set-like object providing a view on the contained items.""" + return _OrderedBidictItemsView(self) + + +# The following MappingView implementations use the __iter__ implementations +# inherited from their superclass counterparts in collections.abc, so they +# continue to yield items in the correct order even after an OrderedBidict +# is mutated. They also provide a __reversed__ implementation, which is not +# provided by the collections.abc superclasses. +class _OrderedBidictKeysView(BidictKeysView[KT]): + _mapping: OrderedBidict[KT, t.Any] + + def __reversed__(self) -> t.Iterator[KT]: + return reversed(self._mapping) + + +class _OrderedBidictItemsView(t.ItemsView[KT, VT]): + _mapping: OrderedBidict[KT, VT] + + def __reversed__(self) -> t.Iterator[tuple[KT, VT]]: + ob = self._mapping + for key in reversed(ob): + yield key, ob[key] + + +# For better performance, make _OrderedBidictKeysView and _OrderedBidictItemsView delegate +# to backing dicts for the methods they inherit from collections.abc.Set. (Cannot delegate +# for __iter__ and __reversed__ since they are order-sensitive.) See also: https://bugs.python.org/issue46713 +_OView = t.Union[t.Type[_OrderedBidictKeysView[KT]], t.Type[_OrderedBidictItemsView[KT, t.Any]]] +_setmethodnames: t.Iterable[str] = ( + '__lt__ __le__ __gt__ __ge__ __eq__ __ne__ __sub__ __rsub__ ' + '__or__ __ror__ __xor__ __rxor__ __and__ __rand__ isdisjoint' +).split() + + +def _override_set_methods_to_use_backing_dict(cls: _OView[KT], viewname: str) -> None: + def make_proxy_method(methodname: str) -> t.Any: + def method(self: _OrderedBidictKeysView[KT] | _OrderedBidictItemsView[KT, t.Any], *args: t.Any) -> t.Any: + fwdm = self._mapping._fwdm + if not isinstance(fwdm, dict): # dict view speedup not available, fall back to Set's implementation. + return getattr(Set, methodname)(self, *args) + fwdm_dict_view = getattr(fwdm, viewname)() + fwdm_dict_view_method = getattr(fwdm_dict_view, methodname) + if ( + len(args) != 1 + or not isinstance((arg := args[0]), self.__class__) + or not isinstance(arg._mapping._fwdm, dict) + ): + return fwdm_dict_view_method(*args) + # self and arg are both _OrderedBidictKeysViews or _OrderedBidictItemsViews whose bidicts are backed by + # a dict. Use arg's backing dict's corresponding view instead of arg. Otherwise, e.g. `ob1.keys() + # < ob2.keys()` would give "TypeError: '<' not supported between instances of '_OrderedBidictKeysView' and + # '_OrderedBidictKeysView'", because both `dict_keys(ob1).__lt__(ob2.keys()) is NotImplemented` and + # `dict_keys(ob2).__gt__(ob1.keys()) is NotImplemented`. + arg_dict = arg._mapping._fwdm + arg_dict_view = getattr(arg_dict, viewname)() + return fwdm_dict_view_method(arg_dict_view) + + method.__name__ = methodname + method.__qualname__ = f'{cls.__qualname__}.{methodname}' + return method + + for name in _setmethodnames: + setattr(cls, name, make_proxy_method(name)) + + +_override_set_methods_to_use_backing_dict(_OrderedBidictKeysView, 'keys') +_override_set_methods_to_use_backing_dict(_OrderedBidictItemsView, 'items') + + +# * Code review nav * +# ============================================================================ +# ← Prev: _orderedbase.py Current: _orderedbidict.py +# ============================================================================ diff --git a/venv/lib/python3.12/site-packages/bidict/_typing.py b/venv/lib/python3.12/site-packages/bidict/_typing.py new file mode 100644 index 0000000..ce95053 --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict/_typing.py @@ -0,0 +1,49 @@ +# Copyright 2009-2024 Joshua Bronson. All rights reserved. +# +# 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/. + + +"""Provide typing-related objects.""" + +from __future__ import annotations + +import typing as t +from enum import Enum + + +KT = t.TypeVar('KT') +VT = t.TypeVar('VT') +VT_co = t.TypeVar('VT_co', covariant=True) + + +Items = t.Iterable[t.Tuple[KT, VT]] + + +@t.runtime_checkable +class Maplike(t.Protocol[KT, VT_co]): + """Like typeshed's SupportsKeysAndGetItem, but usable at runtime.""" + + def keys(self) -> t.Iterable[KT]: ... + + def __getitem__(self, __key: KT) -> VT_co: ... + + +MapOrItems = t.Union[Maplike[KT, VT], Items[KT, VT]] +MappOrItems = t.Union[t.Mapping[KT, VT], Items[KT, VT]] +ItemsIter = t.Iterator[t.Tuple[KT, VT]] + + +class MissingT(Enum): + """Sentinel used to represent none/missing when None itself can't be used.""" + + MISSING = 'MISSING' + + +MISSING: t.Final[t.Literal[MissingT.MISSING]] = MissingT.MISSING +OKT = t.Union[KT, MissingT] #: optional key type +OVT = t.Union[VT, MissingT] #: optional value type + +DT = t.TypeVar('DT') #: for default arguments +ODT = t.Union[DT, MissingT] #: optional default arg type diff --git a/venv/lib/python3.12/site-packages/bidict/metadata.py b/venv/lib/python3.12/site-packages/bidict/metadata.py new file mode 100644 index 0000000..30ad836 --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict/metadata.py @@ -0,0 +1,14 @@ +# Copyright 2009-2024 Joshua Bronson. All rights reserved. +# +# 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/. + +"""Define bidict package metadata.""" + +__version__ = '0.23.1' +__author__ = {'name': 'Joshua Bronson', 'email': 'jabronson@gmail.com'} +__copyright__ = '© 2009-2024 Joshua Bronson' +__description__ = 'The bidirectional mapping library for Python.' +__license__ = 'MPL 2.0' +__url__ = 'https://bidict.readthedocs.io' diff --git a/venv/lib/python3.12/site-packages/bidict/py.typed b/venv/lib/python3.12/site-packages/bidict/py.typed new file mode 100644 index 0000000..342ea76 --- /dev/null +++ b/venv/lib/python3.12/site-packages/bidict/py.typed @@ -0,0 +1 @@ +PEP-561 marker. diff --git a/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/LICENSE.txt b/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/LICENSE.txt new file mode 100644 index 0000000..79c9825 --- /dev/null +++ b/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright 2010 Jason Kirtland + +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/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/METADATA b/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/METADATA new file mode 100644 index 0000000..6d343f5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/METADATA @@ -0,0 +1,60 @@ +Metadata-Version: 2.3 +Name: blinker +Version: 1.9.0 +Summary: Fast, simple object-to-object and broadcast signaling +Author: Jason Kirtland +Maintainer-email: Pallets Ecosystem +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Classifier: Typing :: Typed +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://blinker.readthedocs.io +Project-URL: Source, https://github.com/pallets-eco/blinker/ + +# Blinker + +Blinker provides a fast dispatching system that allows any number of +interested parties to subscribe to events, or "signals". + + +## Pallets Community Ecosystem + +> [!IMPORTANT]\ +> This project is part of the Pallets Community Ecosystem. Pallets is the open +> source organization that maintains Flask; Pallets-Eco enables community +> maintenance of related projects. If you are interested in helping maintain +> this project, please reach out on [the Pallets Discord server][discord]. +> +> [discord]: https://discord.gg/pallets + + +## Example + +Signal receivers can subscribe to specific senders or receive signals +sent by any sender. + +```pycon +>>> from blinker import signal +>>> started = signal('round-started') +>>> def each(round): +... print(f"Round {round}") +... +>>> started.connect(each) + +>>> def round_two(round): +... print("This is round two.") +... +>>> started.connect(round_two, sender=2) + +>>> for round in range(1, 4): +... started.send(round) +... +Round 1! +Round 2! +This is round two. +Round 3! +``` + diff --git a/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/RECORD b/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/RECORD new file mode 100644 index 0000000..7cfb714 --- /dev/null +++ b/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/RECORD @@ -0,0 +1,12 @@ +blinker-1.9.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +blinker-1.9.0.dist-info/LICENSE.txt,sha256=nrc6HzhZekqhcCXSrhvjg5Ykx5XphdTw6Xac4p-spGc,1054 +blinker-1.9.0.dist-info/METADATA,sha256=uIRiM8wjjbHkCtbCyTvctU37IAZk0kEe5kxAld1dvzA,1633 +blinker-1.9.0.dist-info/RECORD,, +blinker-1.9.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82 +blinker/__init__.py,sha256=I2EdZqpy4LyjX17Hn1yzJGWCjeLaVaPzsMgHkLfj_cQ,317 +blinker/__pycache__/__init__.cpython-312.pyc,, +blinker/__pycache__/_utilities.cpython-312.pyc,, +blinker/__pycache__/base.cpython-312.pyc,, +blinker/_utilities.py,sha256=0J7eeXXTUx0Ivf8asfpx0ycVkp0Eqfqnj117x2mYX9E,1675 +blinker/base.py,sha256=QpDuvXXcwJF49lUBcH5BiST46Rz9wSG7VW_p7N_027M,19132 +blinker/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/WHEEL b/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/WHEEL new file mode 100644 index 0000000..e3c6fee --- /dev/null +++ b/venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.10.1 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/venv/lib/python3.12/site-packages/blinker/__init__.py b/venv/lib/python3.12/site-packages/blinker/__init__.py new file mode 100644 index 0000000..1772fa4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/blinker/__init__.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from .base import ANY +from .base import default_namespace +from .base import NamedSignal +from .base import Namespace +from .base import Signal +from .base import signal + +__all__ = [ + "ANY", + "default_namespace", + "NamedSignal", + "Namespace", + "Signal", + "signal", +] diff --git a/venv/lib/python3.12/site-packages/blinker/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/blinker/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e59192c9795d439bffee07b6b57f9fbb9f0d1f41 GIT binary patch literal 481 zcmaKoy-ve05XaBWcd808AR!?JVCq0EUV2Fz_`0J_b=?0+XT08uw~&I z@r?LJ0wZP;{cgAn85_>@tPqS97RHu#%uDfq-O^)>OUc+9tg!?(_jH_Rf(~*SS9CDu zT32)~)SSvRrqfD~b9L3bxTIyO#X0_D!bhT{u}sxO6qK=4rJAwcwA%RBS)R7U6{3Gd54~^+3R2MmRi)BHZ=ircocd)!&ij4Whf2bFLhW6p=1P)W+mZ9be23t1$u zz$`gxDVmQ8oeX>|AHyhwh%&dmbNKVw3SY;;DxV0UlgNrJAWOL(YRi&V%u*NRe9DSj z2`jn~&ZldkO!9B8&R{=oxNeyl%r3j$7_22qiZI1KCC2;*gw2*MA5WOUSpk)T39p|4 z<^o)dpgguvKJ+nMFfZ9CFIyoJwxpw=x6~FYVoP2~6dF7cqf>SBerqwqSQc_q6jt6^>#{p5o&~9wVTJM5gJF=@K{EounqZPuUaA0 zS9IMa=X5cIM|Iugchz-|=QlXG`Rx7NY`H{oX4xs!a^|eTSS@#! zxMy>YUC32xkVNj`?C6_0&t~L}ieb(hGsMdk9NV2IG^baY?bysFUba%BNjS8g3WAeu z!`J~b?;Rjdo9K2ty>xi_$dw~E<3qRmwsUTU-R#@Hn%I9o)wejaSY9#LQhQs{?Sb7b z>2iEAzVzNrb?5DLZ!5MAN1#nn;GPpv=mjQ-fzFM@s395$<{GAHm@*+>_J~sy3y*Kq zG|IR6s;=9v&2+t<-oPCQG*Elov4DH)j+T5WO8a1Cwu1vKWB~jGgx7!p2ro^7@c;_l z1~!0)cL9%Ye2##U3f+Vt5bWcY2_S$V-FLaRSX)(h{0E2uFntw>BJPGkdGd9GNa^|v zVURZloq_bIZ`*K2xA=9wD-{6h*}5FP5^XhB)m;KoMi$QXC8!@i64WtO!M3fSgTT`v zAig|Sc1b2A7DC+S>swg0;*d#}J?_>3BpV0NstA^9o zK+aobQ`dt+Nb7o6V+1MBOWMx~j}zs7@{{daFp1+pv0%Cdq{1cdip&t$1>q6!GC-bw zgC2&Iu==1EWe+t^t|kwz$+^dJB;2>&gTp)5BS4-284wN;dpBiE7|syc@+&aQh;3xj z+(r`sMSDDN1N0!{JH>zugO}>X(P z^`Pw=9@y}{8-s47&666pvt525VOb%}sJ;SkKn30mU$wnb@UVq!i>F4h>O$N($LpIX ziS2bB43S&DWV;N)+XJ`i&UmF{%oE+VXciO)x$;9GP4q}bYWg>&|EAKvBHdC39z{@3 z@6x{Ikt-u>>D@o}et+VpGe4YJ+cUbF9$i&O?<-rD_Fa{(jxW`3DSID)abGJ2dlIRZ zdGW30SWvFr@#URY;*ZB?!2FLaNOVV_fjiNG$S81-7!7!cp36l8gz7s0Iy%e=4+!KXs;9G)@g3(daJXHdC5dnnqq^5^})c2U#699@^OiluZ$%(0{ z5si0@DXk&q;3Q}>wTkV| z_?Y?1IrvcG_vAhi-tm=*T7|&3K!AwXkVp{WiEAh*Z2oXOQ;!C{=osY=;-&1p4dj7@ zF}{cP{)KkkL8&{a_a5rMhlc);lg;B7PJeZ}Wqv)@#7`o-@W4X^Wc_Fw_dVV(;~{bO EKkidnz5oCK literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/blinker/__pycache__/base.cpython-312.pyc b/venv/lib/python3.12/site-packages/blinker/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9c51572f5e31117f0718b2ab9359351fde62d08 GIT binary patch literal 21985 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 u1*zk$mllP}xx-6B=eolox^P5zYj~yUK&I)yv>> Symbol('foo') is Symbol('foo') + True + >>> Symbol('foo') + foo + """ + + symbols: t.ClassVar[dict[str, Symbol]] = {} + + def __new__(cls, name: str) -> Symbol: + if name in cls.symbols: + return cls.symbols[name] + + obj = super().__new__(cls) + cls.symbols[name] = obj + return obj + + def __init__(self, name: str) -> None: + self.name = name + + def __repr__(self) -> str: + return self.name + + def __getnewargs__(self) -> tuple[t.Any, ...]: + return (self.name,) + + +def make_id(obj: object) -> c.Hashable: + """Get a stable identifier for a receiver or sender, to be used as a dict + key or in a set. + """ + if inspect.ismethod(obj): + # The id of a bound method is not stable, but the id of the unbound + # function and instance are. + return id(obj.__func__), id(obj.__self__) + + if isinstance(obj, (str, int)): + # Instances with the same value always compare equal and have the same + # hash, even if the id may change. + return obj + + # Assume other types are not hashable but will always be the same instance. + return id(obj) + + +def make_ref(obj: T, callback: c.Callable[[ref[T]], None] | None = None) -> ref[T]: + if inspect.ismethod(obj): + return WeakMethod(obj, callback) # type: ignore[arg-type, return-value] + + return ref(obj, callback) diff --git a/venv/lib/python3.12/site-packages/blinker/base.py b/venv/lib/python3.12/site-packages/blinker/base.py new file mode 100644 index 0000000..d051b94 --- /dev/null +++ b/venv/lib/python3.12/site-packages/blinker/base.py @@ -0,0 +1,512 @@ +from __future__ import annotations + +import collections.abc as c +import sys +import typing as t +import weakref +from collections import defaultdict +from contextlib import contextmanager +from functools import cached_property +from inspect import iscoroutinefunction + +from ._utilities import make_id +from ._utilities import make_ref +from ._utilities import Symbol + +F = t.TypeVar("F", bound=c.Callable[..., t.Any]) + +ANY = Symbol("ANY") +"""Symbol for "any sender".""" + +ANY_ID = 0 + + +class Signal: + """A notification emitter. + + :param doc: The docstring for the signal. + """ + + ANY = ANY + """An alias for the :data:`~blinker.ANY` sender symbol.""" + + set_class: type[set[t.Any]] = set + """The set class to use for tracking connected receivers and senders. + Python's ``set`` is unordered. If receivers must be dispatched in the order + they were connected, an ordered set implementation can be used. + + .. versionadded:: 1.7 + """ + + @cached_property + def receiver_connected(self) -> Signal: + """Emitted at the end of each :meth:`connect` call. + + The signal sender is the signal instance, and the :meth:`connect` + arguments are passed through: ``receiver``, ``sender``, and ``weak``. + + .. versionadded:: 1.2 + """ + return Signal(doc="Emitted after a receiver connects.") + + @cached_property + def receiver_disconnected(self) -> Signal: + """Emitted at the end of each :meth:`disconnect` call. + + The sender is the signal instance, and the :meth:`disconnect` arguments + are passed through: ``receiver`` and ``sender``. + + This signal is emitted **only** when :meth:`disconnect` is called + explicitly. This signal cannot be emitted by an automatic disconnect + when a weakly referenced receiver or sender goes out of scope, as the + instance is no longer be available to be used as the sender for this + signal. + + An alternative approach is available by subscribing to + :attr:`receiver_connected` and setting up a custom weakref cleanup + callback on weak receivers and senders. + + .. versionadded:: 1.2 + """ + return Signal(doc="Emitted after a receiver disconnects.") + + def __init__(self, doc: str | None = None) -> None: + if doc: + self.__doc__ = doc + + self.receivers: dict[ + t.Any, weakref.ref[c.Callable[..., t.Any]] | c.Callable[..., t.Any] + ] = {} + """The map of connected receivers. Useful to quickly check if any + receivers are connected to the signal: ``if s.receivers:``. The + structure and data is not part of the public API, but checking its + boolean value is. + """ + + self.is_muted: bool = False + self._by_receiver: dict[t.Any, set[t.Any]] = defaultdict(self.set_class) + self._by_sender: dict[t.Any, set[t.Any]] = defaultdict(self.set_class) + self._weak_senders: dict[t.Any, weakref.ref[t.Any]] = {} + + def connect(self, receiver: F, sender: t.Any = ANY, weak: bool = True) -> F: + """Connect ``receiver`` to be called when the signal is sent by + ``sender``. + + :param receiver: The callable to call when :meth:`send` is called with + the given ``sender``, passing ``sender`` as a positional argument + along with any extra keyword arguments. + :param sender: Any object or :data:`ANY`. ``receiver`` will only be + called when :meth:`send` is called with this sender. If ``ANY``, the + receiver will be called for any sender. A receiver may be connected + to multiple senders by calling :meth:`connect` multiple times. + :param weak: Track the receiver with a :mod:`weakref`. The receiver will + be automatically disconnected when it is garbage collected. When + connecting a receiver defined within a function, set to ``False``, + otherwise it will be disconnected when the function scope ends. + """ + receiver_id = make_id(receiver) + sender_id = ANY_ID if sender is ANY else make_id(sender) + + if weak: + self.receivers[receiver_id] = make_ref( + receiver, self._make_cleanup_receiver(receiver_id) + ) + else: + self.receivers[receiver_id] = receiver + + self._by_sender[sender_id].add(receiver_id) + self._by_receiver[receiver_id].add(sender_id) + + if sender is not ANY and sender_id not in self._weak_senders: + # store a cleanup for weakref-able senders + try: + self._weak_senders[sender_id] = make_ref( + sender, self._make_cleanup_sender(sender_id) + ) + except TypeError: + pass + + if "receiver_connected" in self.__dict__ and self.receiver_connected.receivers: + try: + self.receiver_connected.send( + self, receiver=receiver, sender=sender, weak=weak + ) + except TypeError: + # TODO no explanation or test for this + self.disconnect(receiver, sender) + raise + + return receiver + + def connect_via(self, sender: t.Any, weak: bool = False) -> c.Callable[[F], F]: + """Connect the decorated function to be called when the signal is sent + by ``sender``. + + The decorated function will be called when :meth:`send` is called with + the given ``sender``, passing ``sender`` as a positional argument along + with any extra keyword arguments. + + :param sender: Any object or :data:`ANY`. ``receiver`` will only be + called when :meth:`send` is called with this sender. If ``ANY``, the + receiver will be called for any sender. A receiver may be connected + to multiple senders by calling :meth:`connect` multiple times. + :param weak: Track the receiver with a :mod:`weakref`. The receiver will + be automatically disconnected when it is garbage collected. When + connecting a receiver defined within a function, set to ``False``, + otherwise it will be disconnected when the function scope ends.= + + .. versionadded:: 1.1 + """ + + def decorator(fn: F) -> F: + self.connect(fn, sender, weak) + return fn + + return decorator + + @contextmanager + def connected_to( + self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY + ) -> c.Generator[None, None, None]: + """A context manager that temporarily connects ``receiver`` to the + signal while a ``with`` block executes. When the block exits, the + receiver is disconnected. Useful for tests. + + :param receiver: The callable to call when :meth:`send` is called with + the given ``sender``, passing ``sender`` as a positional argument + along with any extra keyword arguments. + :param sender: Any object or :data:`ANY`. ``receiver`` will only be + called when :meth:`send` is called with this sender. If ``ANY``, the + receiver will be called for any sender. + + .. versionadded:: 1.1 + """ + self.connect(receiver, sender=sender, weak=False) + + try: + yield None + finally: + self.disconnect(receiver) + + @contextmanager + def muted(self) -> c.Generator[None, None, None]: + """A context manager that temporarily disables the signal. No receivers + will be called if the signal is sent, until the ``with`` block exits. + Useful for tests. + """ + self.is_muted = True + + try: + yield None + finally: + self.is_muted = False + + def send( + self, + sender: t.Any | None = None, + /, + *, + _async_wrapper: c.Callable[ + [c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]]], c.Callable[..., t.Any] + ] + | None = None, + **kwargs: t.Any, + ) -> list[tuple[c.Callable[..., t.Any], t.Any]]: + """Call all receivers that are connected to the given ``sender`` + or :data:`ANY`. Each receiver is called with ``sender`` as a positional + argument along with any extra keyword arguments. Return a list of + ``(receiver, return value)`` tuples. + + The order receivers are called is undefined, but can be influenced by + setting :attr:`set_class`. + + If a receiver raises an exception, that exception will propagate up. + This makes debugging straightforward, with an assumption that correctly + implemented receivers will not raise. + + :param sender: Call receivers connected to this sender, in addition to + those connected to :data:`ANY`. + :param _async_wrapper: Will be called on any receivers that are async + coroutines to turn them into sync callables. For example, could run + the receiver with an event loop. + :param kwargs: Extra keyword arguments to pass to each receiver. + + .. versionchanged:: 1.7 + Added the ``_async_wrapper`` argument. + """ + if self.is_muted: + return [] + + results = [] + + for receiver in self.receivers_for(sender): + if iscoroutinefunction(receiver): + if _async_wrapper is None: + raise RuntimeError("Cannot send to a coroutine function.") + + result = _async_wrapper(receiver)(sender, **kwargs) + else: + result = receiver(sender, **kwargs) + + results.append((receiver, result)) + + return results + + async def send_async( + self, + sender: t.Any | None = None, + /, + *, + _sync_wrapper: c.Callable[ + [c.Callable[..., t.Any]], c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]] + ] + | None = None, + **kwargs: t.Any, + ) -> list[tuple[c.Callable[..., t.Any], t.Any]]: + """Await all receivers that are connected to the given ``sender`` + or :data:`ANY`. Each receiver is called with ``sender`` as a positional + argument along with any extra keyword arguments. Return a list of + ``(receiver, return value)`` tuples. + + The order receivers are called is undefined, but can be influenced by + setting :attr:`set_class`. + + If a receiver raises an exception, that exception will propagate up. + This makes debugging straightforward, with an assumption that correctly + implemented receivers will not raise. + + :param sender: Call receivers connected to this sender, in addition to + those connected to :data:`ANY`. + :param _sync_wrapper: Will be called on any receivers that are sync + callables to turn them into async coroutines. For example, + could call the receiver in a thread. + :param kwargs: Extra keyword arguments to pass to each receiver. + + .. versionadded:: 1.7 + """ + if self.is_muted: + return [] + + results = [] + + for receiver in self.receivers_for(sender): + if not iscoroutinefunction(receiver): + if _sync_wrapper is None: + raise RuntimeError("Cannot send to a non-coroutine function.") + + result = await _sync_wrapper(receiver)(sender, **kwargs) + else: + result = await receiver(sender, **kwargs) + + results.append((receiver, result)) + + return results + + def has_receivers_for(self, sender: t.Any) -> bool: + """Check if there is at least one receiver that will be called with the + given ``sender``. A receiver connected to :data:`ANY` will always be + called, regardless of sender. Does not check if weakly referenced + receivers are still live. See :meth:`receivers_for` for a stronger + search. + + :param sender: Check for receivers connected to this sender, in addition + to those connected to :data:`ANY`. + """ + if not self.receivers: + return False + + if self._by_sender[ANY_ID]: + return True + + if sender is ANY: + return False + + return make_id(sender) in self._by_sender + + def receivers_for( + self, sender: t.Any + ) -> c.Generator[c.Callable[..., t.Any], None, None]: + """Yield each receiver to be called for ``sender``, in addition to those + to be called for :data:`ANY`. Weakly referenced receivers that are not + live will be disconnected and skipped. + + :param sender: Yield receivers connected to this sender, in addition + to those connected to :data:`ANY`. + """ + # TODO: test receivers_for(ANY) + if not self.receivers: + return + + sender_id = make_id(sender) + + if sender_id in self._by_sender: + ids = self._by_sender[ANY_ID] | self._by_sender[sender_id] + else: + ids = self._by_sender[ANY_ID].copy() + + for receiver_id in ids: + receiver = self.receivers.get(receiver_id) + + if receiver is None: + continue + + if isinstance(receiver, weakref.ref): + strong = receiver() + + if strong is None: + self._disconnect(receiver_id, ANY_ID) + continue + + yield strong + else: + yield receiver + + def disconnect(self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY) -> None: + """Disconnect ``receiver`` from being called when the signal is sent by + ``sender``. + + :param receiver: A connected receiver callable. + :param sender: Disconnect from only this sender. By default, disconnect + from all senders. + """ + sender_id: c.Hashable + + if sender is ANY: + sender_id = ANY_ID + else: + sender_id = make_id(sender) + + receiver_id = make_id(receiver) + self._disconnect(receiver_id, sender_id) + + if ( + "receiver_disconnected" in self.__dict__ + and self.receiver_disconnected.receivers + ): + self.receiver_disconnected.send(self, receiver=receiver, sender=sender) + + def _disconnect(self, receiver_id: c.Hashable, sender_id: c.Hashable) -> None: + if sender_id == ANY_ID: + if self._by_receiver.pop(receiver_id, None) is not None: + for bucket in self._by_sender.values(): + bucket.discard(receiver_id) + + self.receivers.pop(receiver_id, None) + else: + self._by_sender[sender_id].discard(receiver_id) + self._by_receiver[receiver_id].discard(sender_id) + + def _make_cleanup_receiver( + self, receiver_id: c.Hashable + ) -> c.Callable[[weakref.ref[c.Callable[..., t.Any]]], None]: + """Create a callback function to disconnect a weakly referenced + receiver when it is garbage collected. + """ + + def cleanup(ref: weakref.ref[c.Callable[..., t.Any]]) -> None: + # If the interpreter is shutting down, disconnecting can result in a + # weird ignored exception. Don't call it in that case. + if not sys.is_finalizing(): + self._disconnect(receiver_id, ANY_ID) + + return cleanup + + def _make_cleanup_sender( + self, sender_id: c.Hashable + ) -> c.Callable[[weakref.ref[t.Any]], None]: + """Create a callback function to disconnect all receivers for a weakly + referenced sender when it is garbage collected. + """ + assert sender_id != ANY_ID + + def cleanup(ref: weakref.ref[t.Any]) -> None: + self._weak_senders.pop(sender_id, None) + + for receiver_id in self._by_sender.pop(sender_id, ()): + self._by_receiver[receiver_id].discard(sender_id) + + return cleanup + + def _cleanup_bookkeeping(self) -> None: + """Prune unused sender/receiver bookkeeping. Not threadsafe. + + Connecting & disconnecting leaves behind a small amount of bookkeeping + data. Typical workloads using Blinker, for example in most web apps, + Flask, CLI scripts, etc., are not adversely affected by this + bookkeeping. + + With a long-running process performing dynamic signal routing with high + volume, e.g. connecting to function closures, senders are all unique + object instances. Doing all of this over and over may cause memory usage + to grow due to extraneous bookkeeping. (An empty ``set`` for each stale + sender/receiver pair.) + + This method will prune that bookkeeping away, with the caveat that such + pruning is not threadsafe. The risk is that cleanup of a fully + disconnected receiver/sender pair occurs while another thread is + connecting that same pair. If you are in the highly dynamic, unique + receiver/sender situation that has lead you to this method, that failure + mode is perhaps not a big deal for you. + """ + for mapping in (self._by_sender, self._by_receiver): + for ident, bucket in list(mapping.items()): + if not bucket: + mapping.pop(ident, None) + + def _clear_state(self) -> None: + """Disconnect all receivers and senders. Useful for tests.""" + self._weak_senders.clear() + self.receivers.clear() + self._by_sender.clear() + self._by_receiver.clear() + + +class NamedSignal(Signal): + """A named generic notification emitter. The name is not used by the signal + itself, but matches the key in the :class:`Namespace` that it belongs to. + + :param name: The name of the signal within the namespace. + :param doc: The docstring for the signal. + """ + + def __init__(self, name: str, doc: str | None = None) -> None: + super().__init__(doc) + + #: The name of this signal. + self.name: str = name + + def __repr__(self) -> str: + base = super().__repr__() + return f"{base[:-1]}; {self.name!r}>" # noqa: E702 + + +class Namespace(dict[str, NamedSignal]): + """A dict mapping names to signals.""" + + def signal(self, name: str, doc: str | None = None) -> NamedSignal: + """Return the :class:`NamedSignal` for the given ``name``, creating it + if required. Repeated calls with the same name return the same signal. + + :param name: The name of the signal. + :param doc: The docstring of the signal. + """ + if name not in self: + self[name] = NamedSignal(name, doc) + + return self[name] + + +class _PNamespaceSignal(t.Protocol): + def __call__(self, name: str, doc: str | None = None) -> NamedSignal: ... + + +default_namespace: Namespace = Namespace() +"""A default :class:`Namespace` for creating named signals. :func:`signal` +creates a :class:`NamedSignal` in this namespace. +""" + +signal: _PNamespaceSignal = default_namespace.signal +"""Return a :class:`NamedSignal` in :data:`default_namespace` with the given +``name``, creating it if required. Repeated calls with the same name return the +same signal. +""" diff --git a/venv/lib/python3.12/site-packages/blinker/py.typed b/venv/lib/python3.12/site-packages/blinker/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/METADATA b/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/METADATA new file mode 100644 index 0000000..534eb57 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/METADATA @@ -0,0 +1,84 @@ +Metadata-Version: 2.4 +Name: click +Version: 8.3.0 +Summary: Composable command line interface toolkit +Maintainer-email: Pallets +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +License-Expression: BSD-3-Clause +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Typing :: Typed +License-File: LICENSE.txt +Requires-Dist: colorama; platform_system == 'Windows' +Project-URL: Changes, https://click.palletsprojects.com/page/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://click.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Source, https://github.com/pallets/click/ + +
+ +# Click + +Click is a Python package for creating beautiful command line interfaces +in a composable way with as little code as necessary. It's the "Command +Line Interface Creation Kit". It's highly configurable but comes with +sensible defaults out of the box. + +It aims to make the process of writing command line tools quick and fun +while also preventing any frustration caused by the inability to +implement an intended CLI API. + +Click in three points: + +- Arbitrary nesting of commands +- Automatic help page generation +- Supports lazy loading of subcommands at runtime + + +## A Simple Example + +```python +import click + +@click.command() +@click.option("--count", default=1, help="Number of greetings.") +@click.option("--name", prompt="Your name", help="The person to greet.") +def hello(count, name): + """Simple program that greets NAME for a total of COUNT times.""" + for _ in range(count): + click.echo(f"Hello, {name}!") + +if __name__ == '__main__': + hello() +``` + +``` +$ python hello.py --count=3 +Your name: Click +Hello, Click! +Hello, Click! +Hello, Click! +``` + + +## Donate + +The Pallets organization develops and supports Click and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, [please +donate today][]. + +[please donate today]: https://palletsprojects.com/donate + +## Contributing + +See our [detailed contributing documentation][contrib] for many ways to +contribute, including reporting issues, requesting features, asking or answering +questions, and making PRs. + +[contrib]: https://palletsprojects.com/contributing/ + diff --git a/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/RECORD b/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/RECORD new file mode 100644 index 0000000..dc3c2be --- /dev/null +++ b/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/RECORD @@ -0,0 +1,40 @@ +click-8.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +click-8.3.0.dist-info/METADATA,sha256=P6vpEHZ_MLBt4SO2eB-QaadcOdiznkzaZtJImRo7_V4,2621 +click-8.3.0.dist-info/RECORD,, +click-8.3.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 +click-8.3.0.dist-info/licenses/LICENSE.txt,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475 +click/__init__.py,sha256=6YyS1aeyknZ0LYweWozNZy0A9nZ_11wmYIhv3cbQrYo,4473 +click/__pycache__/__init__.cpython-312.pyc,, +click/__pycache__/_compat.cpython-312.pyc,, +click/__pycache__/_termui_impl.cpython-312.pyc,, +click/__pycache__/_textwrap.cpython-312.pyc,, +click/__pycache__/_utils.cpython-312.pyc,, +click/__pycache__/_winconsole.cpython-312.pyc,, +click/__pycache__/core.cpython-312.pyc,, +click/__pycache__/decorators.cpython-312.pyc,, +click/__pycache__/exceptions.cpython-312.pyc,, +click/__pycache__/formatting.cpython-312.pyc,, +click/__pycache__/globals.cpython-312.pyc,, +click/__pycache__/parser.cpython-312.pyc,, +click/__pycache__/shell_completion.cpython-312.pyc,, +click/__pycache__/termui.cpython-312.pyc,, +click/__pycache__/testing.cpython-312.pyc,, +click/__pycache__/types.cpython-312.pyc,, +click/__pycache__/utils.cpython-312.pyc,, +click/_compat.py,sha256=v3xBZkFbvA1BXPRkFfBJc6-pIwPI7345m-kQEnpVAs4,18693 +click/_termui_impl.py,sha256=ktpAHyJtNkhyR-x64CQFD6xJQI11fTA3qg2AV3iCToU,26799 +click/_textwrap.py,sha256=BOae0RQ6vg3FkNgSJyOoGzG1meGMxJ_ukWVZKx_v-0o,1400 +click/_utils.py,sha256=kZwtTf5gMuCilJJceS2iTCvRvCY-0aN5rJq8gKw7p8g,943 +click/_winconsole.py,sha256=_vxUuUaxwBhoR0vUWCNuHY8VUefiMdCIyU2SXPqoF-A,8465 +click/core.py,sha256=1A5T8UoAXklIGPTJ83_DJbVi35ehtJS2FTkP_wQ7es0,128855 +click/decorators.py,sha256=5P7abhJtAQYp_KHgjUvhMv464ERwOzrv2enNknlwHyQ,18461 +click/exceptions.py,sha256=8utf8w6V5hJXMnO_ic1FNrtbwuEn1NUu1aDwV8UqnG4,9954 +click/formatting.py,sha256=RVfwwr0rwWNpgGr8NaHodPzkIr7_tUyVh_nDdanLMNc,9730 +click/globals.py,sha256=gM-Nh6A4M0HB_SgkaF5M4ncGGMDHc_flHXu9_oh4GEU,1923 +click/parser.py,sha256=Q31pH0FlQZEq-UXE_ABRzlygEfvxPTuZbWNh4xfXmzw,19010 +click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +click/shell_completion.py,sha256=Cc4GQUFuWpfQBa9sF5qXeeYI7n3tI_1k6ZdSn4BZbT0,20994 +click/termui.py,sha256=vAYrKC2a7f_NfEIhAThEVYfa__ib5XQbTSCGtJlABRA,30847 +click/testing.py,sha256=EERbzcl1br0mW0qBS9EqkknfNfXB9WQEW0ELIpkvuSs,19102 +click/types.py,sha256=ek54BNSFwPKsqtfT7jsqcc4WHui8AIFVMKM4oVZIXhc,39927 +click/utils.py,sha256=gCUoewdAhA-QLBUUHxrLh4uj6m7T1WjZZMNPvR0I7YA,20257 diff --git a/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/WHEEL b/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/WHEEL new file mode 100644 index 0000000..d8b9936 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click-8.3.0.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/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/licenses/LICENSE.txt b/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..d12a849 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click-8.3.0.dist-info/licenses/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright 2014 Pallets + +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/venv/lib/python3.12/site-packages/click/__init__.py b/venv/lib/python3.12/site-packages/click/__init__.py new file mode 100644 index 0000000..1aa547c --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/__init__.py @@ -0,0 +1,123 @@ +""" +Click is a simple Python module inspired by the stdlib optparse to make +writing command line scripts fun. Unlike other modules, it's based +around a simple API that does not come with too much magic and is +composable. +""" + +from __future__ import annotations + +from .core import Argument as Argument +from .core import Command as Command +from .core import CommandCollection as CommandCollection +from .core import Context as Context +from .core import Group as Group +from .core import Option as Option +from .core import Parameter as Parameter +from .decorators import argument as argument +from .decorators import command as command +from .decorators import confirmation_option as confirmation_option +from .decorators import group as group +from .decorators import help_option as help_option +from .decorators import make_pass_decorator as make_pass_decorator +from .decorators import option as option +from .decorators import pass_context as pass_context +from .decorators import pass_obj as pass_obj +from .decorators import password_option as password_option +from .decorators import version_option as version_option +from .exceptions import Abort as Abort +from .exceptions import BadArgumentUsage as BadArgumentUsage +from .exceptions import BadOptionUsage as BadOptionUsage +from .exceptions import BadParameter as BadParameter +from .exceptions import ClickException as ClickException +from .exceptions import FileError as FileError +from .exceptions import MissingParameter as MissingParameter +from .exceptions import NoSuchOption as NoSuchOption +from .exceptions import UsageError as UsageError +from .formatting import HelpFormatter as HelpFormatter +from .formatting import wrap_text as wrap_text +from .globals import get_current_context as get_current_context +from .termui import clear as clear +from .termui import confirm as confirm +from .termui import echo_via_pager as echo_via_pager +from .termui import edit as edit +from .termui import getchar as getchar +from .termui import launch as launch +from .termui import pause as pause +from .termui import progressbar as progressbar +from .termui import prompt as prompt +from .termui import secho as secho +from .termui import style as style +from .termui import unstyle as unstyle +from .types import BOOL as BOOL +from .types import Choice as Choice +from .types import DateTime as DateTime +from .types import File as File +from .types import FLOAT as FLOAT +from .types import FloatRange as FloatRange +from .types import INT as INT +from .types import IntRange as IntRange +from .types import ParamType as ParamType +from .types import Path as Path +from .types import STRING as STRING +from .types import Tuple as Tuple +from .types import UNPROCESSED as UNPROCESSED +from .types import UUID as UUID +from .utils import echo as echo +from .utils import format_filename as format_filename +from .utils import get_app_dir as get_app_dir +from .utils import get_binary_stream as get_binary_stream +from .utils import get_text_stream as get_text_stream +from .utils import open_file as open_file + + +def __getattr__(name: str) -> object: + import warnings + + if name == "BaseCommand": + from .core import _BaseCommand + + warnings.warn( + "'BaseCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Command' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _BaseCommand + + if name == "MultiCommand": + from .core import _MultiCommand + + warnings.warn( + "'MultiCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Group' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _MultiCommand + + if name == "OptionParser": + from .parser import _OptionParser + + warnings.warn( + "'OptionParser' is deprecated and will be removed in Click 9.0. The" + " old parser is available in 'optparse'.", + DeprecationWarning, + stacklevel=2, + ) + return _OptionParser + + if name == "__version__": + import importlib.metadata + import warnings + + warnings.warn( + "The '__version__' attribute is deprecated and will be removed in" + " Click 9.1. Use feature detection or" + " 'importlib.metadata.version(\"click\")' instead.", + DeprecationWarning, + stacklevel=2, + ) + return importlib.metadata.version("click") + + raise AttributeError(name) diff --git a/venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40859fa808e4be40576cd5da45ad0d2d0fefdaf5 GIT binary patch literal 4053 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 aF3%agpBjqtQk5yG;JUflVk~77^xpswRS?7g literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/click/__pycache__/_textwrap.cpython-312.pyc b/venv/lib/python3.12/site-packages/click/__pycache__/_textwrap.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc79385c79d208b3de7ff46f8858d3d58e5051e0 GIT binary patch literal 2406 zcmaJ@TWcFf6rR0kwYu1{?3NZvm4;=l-9(aCD`$3P zTh0nX@q^qJ;-^|A6x^q_nEr_Lv4LXQF;XW@F|>u|O~fe#Q)tgfYe#m{j^OM!b8d6_ z=8PURHF*)V2S5Cpm~kQW8|`d2=+nk*#y~D26{(Dl(oBqDC}wpw&Bb^JyJ9XF<8&eI zj=34cqN_;d=aA~sT&b2?m|bF-t9N5!j+KNbv?DRZrx-@YQmkagFh`-^9M2e*He;m~ zL%FHpcw-1B0JC`+Mi-HWVvLGntOvdPQ(Y=E$HzF0R|S=wUUWb-yR3;U-s%WQot@3K+K!P{2!iwA5*WkZ1S zf-R`Xb{#-@w=LMPYk)+XO*Lk8Ty6KVlZ!#NNF!VTO%BxyX+E}jK}HPiG!`p9iCWPBnj zQ=Ux2;SEjZNUNMs3{{7u+sl{@HyrNJOKLg~6N9*eo;9P+P_bia-s>loY8xmFSAv}jw~OADU|-=1#j^{y>)79ZcWi#Fc;%r#Qt9n4j1@1h z`Vaox8mc&ov2u5`)E!;xKC$jUS>;ghz$Rox@kYu}y7f`I{Q}?}!~na2RMc!w%d@X0 zTb{Gg4D%`vo0)B*+2ox_HOt2Aj%LSlhwL7>^k1-xaQSV>+YkY=$vaj#nELAi&_?)BIefenKE5Izs6-BzBWFvI zvnyh_BKpf>*Q(f6oGOX^>ta`Jpl4O=SsEybgX>~XmFGR@ANgB1dQTUIANmhg`p*@H z*ZsZE;o$qu>>B#p?16lrT;eVrW$p|7r4YLB4Nx3vxg>G-B|5%~B(z%8YSK>t7STj zgJO_+CP3Ulm8L*}{XNvXbo!f%Ute77JG~Y<^ArglX?~<|Wud*&5h`~KmO2ItZ*K&G z3-5h8T?vKDp=c=-T@W_>&38xUM~a8$U431FoI*9L3ZSJ+(hUsS4gb2WV7sMQ@vMsJt8=>B+3%*qWwRKnB6p1L< zQT0&dMJ;VrA4PuTZQg96=r9u6pR!)p`ArT`m7-_+eXejl8O;L^6HN3*7B&DTiYfa^ z03H%ui`V{548ht7M+Hx}frykM^tm8j=RXF#G4;jNrhN(myjaSXc%i-rZ{c&GpvBIF n3XQ59!!VCg?;oh=5ehs;{m+CWOn70i92_VG2mV6PsRj8Lb0jl2 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc b/venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebd75a9facad60592c4f50f9bb908f8d7a2e1b91 GIT binary patch literal 1181 zcmZ`&%}*0S6rb6Twv>WU1fm9Axvha+@B;`EA_)~qNJGL$!d`}Dcc3lX-MTX+mU!Sm zjKl*H4;+Yh2nYTFPhPo{6SziW!pR#1PMn>Ox z2Oj1Mf)p8*=V$34tk~b|ldWVrl{J-{C#a>?$RQWd#S7A_Zx~@k}n)O_g zwrX<1zXuOa`VMQq6y)+Hla5>+{fO<|X2UF@vBOS#3o zQY6?`sHfO?k+BN7wkj*8Q^Z7z#}&3TjTx}%5N~iWWH>uNnVMAk`g)aarJ7Wkt%NB< z8A=OeP}d8%K(s=MW;|E({3Wh;_plg!cphn$+!m~9kzWD(;Kf9x!?z$;W30m5^*K~^ zzVEP$XWmjR7vdf8S)2l@kiLSNDh723S&m5vLO~26*UV#vB)+qwmk4$i2`@;w8r3si z9_yCpER=LBV^UhuSFpRHJJ|(2?C6Qs`&cJgin|M@wQMe7qFYYZTGmmKW*y!)|A&tY zK0>R_)ictn6rUH;$;28sNw#fkTiS6_JxZ$O=102^cSjG#_r`ahmv8l#WBnJrm$WJb z5g*f{?*<~Fz8drE7#+kB%6oRv;hiK9dR;V~>%O80*`CGdh_{f#77gT?^i*m(XnHv@ zGKSKr)aVqN9hsO(`3)@59`O0t`w1@i!>WiV1EhnAb#N}oa_qbjG_-tpwU+wU-tp;8 zS-O2GL3tpIj} literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/click/__pycache__/_winconsole.cpython-312.pyc b/venv/lib/python3.12/site-packages/click/__pycache__/_winconsole.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f3ec66b33c6c5fc1650103131c1318d1672807f GIT binary patch literal 11751 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 a3wfu?d2F5eon{Vci|yD}^E+EOp#K9Zrx+yw literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc b/venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59acd7ca5cb56616d83adeb83d1c100e3de2b049 GIT binary patch literal 133412 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/btreezone.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/btreezone.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9543038d9226987e6d736d6920ec96a956bfbe38 GIT binary patch literal 21013 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 HQ#Xh;DQaw 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 Ylh%x@H0`Rp;i{XoE8Z9FjIj9s0AGj6NB{r; literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/dnssectypes.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/dnssectypes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27edd1a17cbce9888d626cfde70669fe816393e1 GIT binary patch literal 1961 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/edns.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/edns.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63413a8f09e9b7084ad9271f9a03a97793d178fc GIT binary patch literal 26109 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 RykV()W4e6fJBmBd{|ADXdv^c; literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/entropy.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/entropy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c58c5d7c785d7c2a89429d8ab00e2daf69b7015 GIT binary patch literal 5959 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 kTPfuIxIooMNAV2R&t!bw{i*ao75S{ndZ3Bdf8` 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/exception.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/exception.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd9e9fe6540f1e7de2a6dcb0306014f9f41d586d GIT binary patch literal 7192 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/flags.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/flags.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..947d43895865fae699e1a521759c67edce2105d4 GIT binary patch literal 3063 zcmb_e&2JM&6rb4-+wn(YCm}#u${Hmut{`q2)TSz;3JFk9NmWt_X`yl!? zMpGW=%guRiDXt4WvB%B(rG zhe?xBYV%`yKGFOJAy7&-_VM>Oh(@58MCMc~^C~9`DldzwAiGpicB?Mgqq^meyeHaW z3%KweDH4(-0t$|hJzQ-MTx3r|5d<+A_1d0zTvsw$JZ^jA@k~}-NTcqL$L}sE>3W3w z_J+-kjoI9H<2H9=0zROR!8|_kgzUpYGV(TA8?YJX`Kr!7Q*bbdN zVAvKc?FY;DYWhMZo=7XEX}b)~S}^nk6rnK?D;~9d6$Bhty37?w%BgoFjgYOnzRVZ+ z3G)3hLY9RBSD*zURX?gN$&GB>$x<)mau53jQsAaYl$$hg15wTv&0IQZ+2TSjrx{Vv zcn{ph`ygzQ)xvgJ*?CPjZEBggPYH*C1VD?-7}-o5AsTpwM&M(90isAM{y_0kB@kMd z*W?GcivxJuoP$t^!D*49)Pivgxm(;%RKN#3c?a_tkU_^9~f@-qDfJ3XeP=+g8wWKK^rca&ot$S#a0lr^!5 zyQ_C(DJuQJ}R|5|DcGDt0awPSyM zz1bc7aU1eZ4PKI#yXjMi-#knJmi((--{7_|_$~-A8U*N41F8An3g5_Q4741Cb_QNw zeZ~O0d0YD3&lqqTU=*+`hw)|N`~~s-LHZ87oiw<5ZvFh)`2zy#!D$X` zYrU{mI3S=_y&?^jrm6&{%>OE6s9+JOQ04Nlz%6o+>3p7F6jT9;s)#t` zVo#bMrrv2mb$u%=y32gnT^WhbFUX%H<`Z{k?j>ZbXc;7@bZre`Bd2ST1C}NXZSh{I zm`C%rK+rm|T^NxP)>4rD?TSdhJbe$RIfoFeLKRjym8fRP{q|T z0PqFU4F_O_#+{TEhI&F&*VAs^O=yBR_&N(f)n&T6h(o5R3J&%F=@ki>LNA80Vr;mo zxb+^5asQ7e=Xgrg^SbF;={rG#noqk^*kuZ)tKz3Tgsr(#tfIdK@>Q0FGC2 znhJSdAyFL8S)=c*)!?)s^^ll{!F>ug;ak0Ulcy+O^?Xc7EBu;ea;x!E?xpuNrqJX} zKFu$Hu&=Tgb9#`-B#UI$b8=e81zAtaO1DXK8da0Y%e~%BCS`*t1Qn1*qjXu(JTjG> zMosQg;FFI*o5o0KyIgBSf+oAX>76<=OIbcm}1I)Vq2u@TkE--&NrD&Q?Iu zXs**uQl#+^OoJ%NDEZco)tQVHpmSJ{fu-BNSW!nIqAn^BZB-&p8M!fCy(ZS=} zh_!wGjl@d{NWP}f{QRN2HTivDFEIF^P%k_WjP3+Rt+CH`19vRx&c1)}FW?j7um)*( zlW$D5#O=W!JbR%tE&kD5Ezu5!YO!``sGey11IR><^Wc8{{??am zUw`vjeXbt+BRKSU;^D;e;MJYrRVy<6?3-sPYkYP$7_)q_&M-^ZIS(X%6K+;c5S!Tz z%v#cHyD!kZ*f_V>A8by3w_X?a`$H|*s94f)tT6DHn5E2J>{@ z_S0>M9JdCh_ZqNydR*`T)ts&P8Q^nFKTC%r=~h8kOL;VnWoojvHX?NB@DRe6mjQhL lPjL1H2z6Xhc%d2V0QzYqwl8fPPh#5>JLf<6o6R_Q{{ri*zc2s* literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/immutable.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/immutable.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..303d97e0736226d8adf391b6bb78a1aa9ee4ccc8 GIT binary patch literal 3773 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/ipv6.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/ipv6.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2415aafa25bfd32312c4e28534fde5157ddb215e GIT binary patch literal 6679 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 Q{|zD^Es8Htrp@(#0og+CM*si- literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/message.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/message.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e33c3c3f40eebed54f929c77e5a6c11421427a72 GIT binary patch literal 88757 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/name.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/name.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a23c05a4dbd253a5f7fb5027c1e517e95d15c756 GIT binary patch literal 49166 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 NiOP*{2>vq-{{uoOuY3Ri literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/nameserver.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/nameserver.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..231fc85208fc330fc256cfd8d9a24aba79a10549 GIT binary patch literal 14186 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 y&B5RB#wNpk^KD6#KBp45A^!{F@GyP= literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/node.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e309caaee7634dc081854bca7d3b9be4b35f30cd GIT binary patch literal 16573 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/rdata.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/rdata.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ab1c9a8e20613517fbecd170994f189d130f0a2 GIT binary patch literal 39787 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/rdataclass.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/rdataclass.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34181158f3b8516d37637766021ae2502eda7b99 GIT binary patch literal 3509 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 W__5suj_#8!@_lOcUG~g2;r|5&3~Vz1 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/rdatatype.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/rdatatype.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74230b201719de179dd90d747417daf52cfe5cd1 GIT binary patch literal 10289 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 IxjB;m3o6`kivR!s literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/reversename.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/reversename.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0579d5fb5da69cfb20dd6b15084064d64a5976d GIT binary patch literal 4761 zcmcgvU2GHC6~1GS$A57g=Z_@(WJt^=24XM78~#=Wny_poh*;Q45D6s{&rLjDXU5D- z5<6>)?yAzpEmC$@tk$S20V)zf>;sSc)-K(Ls(p!TilZs2kl=;4K=z^V)N{u(wqrsG zt+qGu+%sqHJ?GqW&YbhjUp*cdg7KHT|BTwa2t6V<=5QLs>YpiuzD7J6K|IA<;?xMG z&z2FZKGVi*GiGMQimkDFQ%q%!`6{$Y@z&cQos1FZ8|JTpQo{6I$;EtK48!eaaH3n`<2!KEs~IGgwwIdy-4A z!`u{Bf=`HhhUYwj*vIj_9OUE#7o2kniGdQS(JD@=nsX*RD#^m6AObmHl8lViX<3X| zt9)z+n%u9RvXK07BDMT52AtXvv(<7CN+UDQ8S86y6KypkUCu3eTUyW64m5-o=!j(! zMJ(q%2xVy1%yN`2=hPRLOI{jfY#BODWf<{LWsT}KRmAE{sxnvf#o%449k&gJc_OgH zZdgKtCHAx(^fJ0$d)ks=s?OV7D+{66rk8oVHP-$zSKVy#(l%4KIj5O#>Dxvu&VWxS z_+{u>ld{jEZlpGt`~65hLRCkx(R@;w|8o>K?tRf#vBr$VIdti_vnXjjhi0jOjk^Vg z4@x5BvC48H`-@XEVOEmKJiw9+#)`rUB1^J%nv1g&k~{_Xs>E`7#$P3~mJkynt=VcGAIZw6O#tJx!sHm9Z&7<>pf}4)3C9~I5 zi?Xq1UHE?)TMqbZj5TYSVd!a2P_P0qBi_Wx2u`SiBnHoJNVf{4{oPL-gp z3d$&?T>|r(6|zZlj0+;CL-Db;&>=3P2o^P zijSv4ktnCCsn84-XF_pdJd{YOQAz9%?%5ks1QqusxX4G`BvwLjypW+;_edh8)#;`g zxg~D{9i8w~p2Fo+zvvI={oy;O3;z8@ z|KYsq{F zZldqD&_MsG(M7IXb`I^Z{=hU2wKG4oxd`p2;3~I(fHh5*v@;Um)yI0%O{16ruYgw` zY3jDYl`p^wvc6_iR%8J5tyk35a>E4)BvopLsll9~f$LC{EL*R6uGcbOvtu1NS!uzh z-ZSVGKCqZ8@D5-5G}7PZ{EE06l|q4C_^Umd=$F0lSaI4-JNAfuqHkohOt%CYvQxsjt$1AD#}}` z6dGiVl!uzZGv?Vor2=+tQ#BAU8#i0qxb@a=f&sJp5l~?Ea8jYxZ&=XSUyU!SfdUcX zBK5Lb?(15&j1$nJv8d`D8d|d|CHmq3KjJ3v0BO2Z9pV_KW93!7j_v)gn(|Lz#{bZ6 zi6`Os(49p|Wux2-AQs&R)$XqHa&-gN5m>41u97+zlnKOrLR#J`$t0b5aIR}w{795$ zMKfb`s8>!5Zc2!!0u5RnG-G9YyaWL?myF|_D$EFTSSGKrOg=3&rUbTR@^v5+ux@m7 z0Gpf)pBYG_26E8)3RY1C}5Az&PXpV9<=fcCl@IqHh`w!FP9*M865z3gpTxR|||OBK9< zd79j%vZ-&{uK927y1uK>9xQl6^YmkH<3ciW(4lO%9MQ2Ce*^xVN&$(l**ayCr%h8;A_2Vla7g~1}e7onF<%Y(2+XG+g=NG=| z&Q1Jo*R=};U*H*{oCEVmm+O2BC$cAUO;_8lv|W2^(YN@)9p9~AE_L-6+QWaZduPQ8 z%N~|Y&9xM~og1_8alLP0DmzuI@5$HqEcKo!)Sp~(oRqu3IRUR`A06eT$mpm{zI8Ou z8aSv&RdWy?0JU(L8YodDQA<&3C3~C5no%Q3K`8lf(3t}@OqqNlYVIs4i9PtLD8^;M@RQ``<}n21~G&Gs(zW&2jFgt0~}VbqA#Sw!rQ JPD1J)_&2H1M;8D9 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/rrset.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/rrset.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c016eb1d744a21f41de211a3294f8d6ecee9ee31 GIT binary patch literal 12455 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/serial.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/serial.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e01d91492c45e2b1f7bb987be70d4ee915c1ce4a GIT binary patch literal 5173 zcmeHLT})F~96#r_x0HTRu|=(-s{;+?tHJ3uox%Bm3%WINOil2iE4^1KlorpuHES)4 z*?gec7VwFZxq!(MX?zf$bWbyT((Pe@JTwhiOtu%_ls+u+Y5)JZZJ|({X3MguC+Y8e z_MHFuf1mq{)oLP8e*WpV-VIfR{EnSM(dQevT4;C*+unC~1qyp(E#^D9PoXhJWYSIH z3_)5aQCTMmvhJ*2(p?t2^^*QF=@ums&>$HAE2IiQqhtiElqvyDk_pf(nE@@51<)#4 z0js1cKwHe_uFe`X8nfaBe<&(v^*upV3Fz`fV1;e`<(dJ;NPy$X$)`X)@AjI96Gx+qTt>xm+R>vPj6&E_5>oKo|q@l>sQs7 z=Ykx*;0XnLJcBW{HxhoUwQZM23953-pg+*>KPM|5DXe%D&gIs@Sk~zC1;atr=Zo94 zYqb^zD&Q1l6Tl^sso9)5F{vLveZ@Dd|JsysY#P>oQ<;M~5-5@n9i*3Ed_Ocm(lkN( zptZ7m3v!2T1XYXc3QEj2ss&{PslydP-YZLC?GO!nN_NAOcqy!4&IB#DL-~FC-vNa& zsK^>#xNs-Guq!kNEb;{Z`zPG1(je}QT^e7oYe)xD#QD8UCdT_BWmZs zU`QU2!>TNCodjh?L1e{<+AA{!nc~U|#NhME=Rq9SqH19m`~%5(0WOgRbIquHwQJtI zeZjFgwP&*ay8D{@fulLuu~5Aw)$#q?^VN-0^3A?<-<=~rcK*;g*V?gQ-#%m6p0U|S z;>mdGIuX{^`g@@7FR|8x zva*))7E#`M;$S=6t(CECNx)h3^ZmFl1hn#dls~Wk2&d%*Z1y>6!F_?2m_99f89Mbx z=)*oay0)%_FdQt|EOs$;=WYW06Pq>LM)oE5rNjyIxOvW8HznRQr_IyS?Y>)mvkmXx zZM*0A)p_4JbMn;vt+R(u&o!LR*f(aZ)g#@>?$nNnrtzjZ>yD}Vn~mwl>FDi?w=T{$ z9=dxXV_7%Sne0r_!O+22gk^R99);&rX+2M$*hXAIFJwhY4yk?x*$O#+ zK9Q>M`TUXuUb(86N%4L=tjc`}0B#5T;IZVfVXEa+r{>M|8HaQ1_?6?3Oj$Rk#H-4@ zwGM`o9e=E^ow3v|n5%}PU+qg(r__nqcx}*}$KGHv6#o84`b~9SE4}zwbbvtqb4)lxR@cTp9 zKL8;%Yx4O9B2pAH!>q;UJ0JCjv>AqhfMHByHU#{>!nPpb*9lgKuoD4NQo`C0b|b(O zkFdQ6Jk;aC{F{dsT`(SFG*!?sTqaKhL3E}9j|o&$_8eA=Mk*dyG>D>mv5JEQ#%ih9 zvS?V|s$ZLXrRg literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/set.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/set.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aabfc91af481376bafc6c47c517c859c08a83717 GIT binary patch literal 12261 zcmd5?Yit`=cAnuw8j+MpNz}u#9%DIG5+{weYdc9-X+VuQBWq-e zRAxq2$dwUqU@RoWg(U=qWNa4pj|Db@e`MP~%>pg@_*3)&tZ2!wN*FF2oK;C;^vy|(iAe@#>eQG+X$);w>iKJqhgMomIa$!O@d51i!m~z_CFX|~Jnas#D zrl!hPT2?hBDbMQGggl+GCekT6mA2#w<+7f-C@Y2a24p3rvL%bwuqHHnakg4gH!V}v zQ?gEfgwK?gvJ^{Crv?cX4ewVq1MkIJ7^iCQC}T;Dt(sD-83O~x<#Xq-36l*urz)1h zTG+)*YCLUNGZ?4={aIb5KlWa{QB9c~eYTh~J)P7O?8WAQY^G(*l*g3BrCG&LL67v* zG#-xWN!`lO7O5*`>8TlwC^DvDB0K4S*)IY~e9rqskzXl5+Q=>IvGRVq^x{ zn7m)Nv?+5q;m9gUE=j%@zwJK)SQaesjF&t1|C|ta`{u;&9+(r)2yZnC!WGY)FzI(j z7yjowckJ<^aJ|VVWaz|rvdY<%Zs|%=&tfYeI5uZX0!hPBhS^c#C3vJ6M1AZ-9vvM{ zr?k=00X9eHOEBJpA8Zca8m%!L^X7fDXM?cHdlgls9#cz>^By0YK%2$rho^=n(o@<{ zBApz|3?(KM%gPL0)>4;;lKR*XXW4HJe*GImCT2Z34HhXEHFF5;93s@GGkGZOYU^5t53$|~zc5DgW+O9?4=T$;|)3UTAtwjH{ z?u%;leeM-P9ov{EBwTuwt&loDuOk#mU7WK0NPBB9dc%`GVoR~;TX|KEq6 z`+@ho5c3XaL)hdXZ4%q+e<;RczP!)Srj5Lh&XG;aOIpTU6^$CKZxX(;5dP8Fgr)anNDuXSS4bX;p{b?H04?uhN)^uz} zqRWA$z{;^)ME=hQ;g&5ytm$~r(7s0dM)jddBzvzmA}mH^?+&KA1R%zMsTEl|NhB&M zbD}CbCRqe=u?zTs+BHxp3C*&yxMZk)pF&1L|` z=9=0YfrY@0x`nz$YvsZ{$>wbFsU;E?OY4{3m~%e=7%N;6p<_jyNiln*usOvT(=7u^ zor~@c?mAnm#@0yLf!TCG^Wvqv7&i$aXg+pOjSe)p7-Q@iG)(HbDtrdD)V9=izrH(H z-@RVnyHGV>KYw~N9GRE602go5Dd@;^K^zB1LtsY&jutWmjXTmd;>-9OC({!Abx2p4P(=X(TXLTGh@sf z(%|~g0**5jpxIQ+iS$fTB{zaRl(am>Urw(akjHhyggXT^LADgkzqiW+iEeNlNQN-S2k(p;spF;_mQ4#JF-k+9d4G1is2TxH+UW7MiO-vDP zisd50k(P^bHRXP7@Zuo6F!(w#xIhVw@As0XO=EF#7lF_LA3RQ7v{a!Yc{3ejhcm;`%-I8YF&xmYP;FCA@yvA8|DM$2iDa~hw)0LaEp&K z`lvVu>b$TjW)JNv^)Q%EV3q-&u+$YGX)U+{ycNf=?FazVrIF~3?<{=h{VQuy`y*p( zJV|Jft5JrFkKUb?NTpLvr7>S4Q08a&(91OUJSsa>npm*Ej%qW5maB50@N8PX2mfPE zoDmE$1N}W&g@sB=kq7OnvBHot584y+W|h~NggGY!BCe_|5=v!cjmI;l%#1-A(r(DJ z6MAAoR-lCtHo1bYBn?ebVLI$13VOKD9=7wDQwT74r~z`hDq%S0&j)mzCaxh2qLa}^ zfGEJcqE*}4_}Mz#b1Jqu`4Bs!aeh{^5#mO1kB)`R-vhu|2ndn3mEIpVZiIWumFPIJ z-f(g)baFEoUVLidnaxncX7s@FD@(7CvvF|#%w{NZEP&IUELI{(s4(ubXo}YxDz7o;c}BP{BZ4CSzg5bA=8S!H2>|EE zz#kS|7I<$^zycNhOs()>hNiy`@MU=W ziz=b%09R$}(Vi{AQ_~9;hZ|R(x^?2_iM!z=o6*+gmzQ2%v2xL#FZ_7C2fzOkFMULQ z9|V6YoZ`s}XV=l4a7y<-`$3>qQYxPPHy(laMOKd)<>C`>bG@pR>%{Q9KatcF!+`V( zMmh8ARegLMJ`Sv{!}=y-eOdQ4%s2lK;7c$*LIKnLP6w;LRXTcy0Hh zkD`pwQV9NbqSZTB85{}iwLKVrEsS&6BxX<$i{N?xct0v@L@8O8=mO0i%tg>bT5 zC@cjD3Aj7y6cSLbQGkkl+s+@k2jAwrREhHj1!MV8@gX*T2VKT90Jeu?P!O1}Mwn?N zmFb;&;h>qBg5PQAiFlbbWV=X6_872)bPmU+?Q+a`^dlEJUIU% zOv3$8M=sQ{qHTnFSnF^ubolnu_d-vU%MRHoX7*n~Wk1IVT=Z0qN*%Rt#|>h3_8fDw zWrg{yCJZV!%<%W=I5P7`5y0pu<*7zT5w)vmFbg_5TI4$Ob@(h(nbKqsQnoVF@W81P z>tXSNLPsRXoHAmZZI>C(jHBq-$uB=cB3nb8Vbved5ONUVp@oESBYB6HI1LVj;=LKaJFDLq{e=P}atIQDBi53qxl1|J;+wAdUlV#FB zzX?hH_gx6z?^3de@-cZ%#siy%F5PJaDU296 zqa80WhA6HfZbks$uEx^n_5xh{xXak#0lQ>??Qmmv-M~Tx=kC$f(CNyoi&nA(*;Zm= zr6+$?#Lh_RIqJ(vQ2iUy;C|<*2dnI+;R_ii3VtaUd%zAkX`9r56f$kyob^GvM3p^VBwg^)Qs^e>N|3^$VX3SqbM|I_}mZ6xv+e@X+7MxCiOiojEq0P`jzofi}9hxPig2k36zE2uIc!yE+^A3|1n?0;?kMJpCPk%1l{}I?T zv?dMhL7{$3+h2ysmpm^pp@AebAFh(n=*3vE#wM2EZUTVNNTK`Eft++;rFTP;A=2}~ z$B7q&nVHfuyy$j~OS5yk###@EUPYM6ISJytA@x0W%#3g2Rpmm>@M2Q!;zg~`Xz-f^ zb{Fff%rp>(*}RIx;j--@!CF7V`-_RwnUgwKW;UeW$4(qz##DMm%(#?0n6cKsm0;GN zlOX0B($FrL`7oZ2-!|nXAn3(+&J{A2#laE3bH;^Gt~5Y;Y#B4xeja6J71!3}86ml! zbvajPBS>BlFg1AK1rlzfo zd-cX5%+ee!>*P@g%RKxN>zNGzxRa7XbmPW@k9$&E`5AMuw&kVbeH$`xr5i!}5=dt& zNrSHV%8xv(y!Lj%%2WNnFwi+^wv~VF)|)rqg!W#O`pfZiP00~V++T5r9E`y5i@V{J z(h4|H{a1uj6aY%Qv1R$h(g~Qh#@@9e>sHKY7o`D)QsJ`#;&|9~ogEi1g1THCeNSUG(IA{^WSo!rZqTBZ@=HDSI(7(+*x+xw0IW=Md<0<^* z{U$zl(hMSOzJ};-rPH_?tI~NtPc-62!Lab5j+LG!r~#(5cPykZD8ZT+^&MVrFdo0C zSy*cdJPcE)P}G61A_Q{;v#tscf=!FchfN#7t_O`RAJ*J&?a#ILe-!<=ZME&k-};L; z{_KrUl)oPT$@t$We?R`Wv z1?)sP8N3X7^Ik1!?Z{x{L0){nwv)VWM=g=}IZWI^-j;(aXKvTueDQ-Tl&kGo^g8Kz zbLpFus_o$)JF#y>Zi_dc&PDc0-p*n9{fM~xy;~*1fUK}1l(fSEsX#b>2SXWq5Z4>? zWnVa3NU6yR-PMx~gHgBb{i(u5zmt|z5O0q`wkc;v*Y{LdlJ$iPt2P@*Op0#I%5&)5Li^B{A+>$)=#u)_t>ZV3-wpTvKPENq z<*zps1bo<*%snWC51%H}$N@PNa@vDLA1B4M1mfz4i_p%Os_$P69!-vZ0!_yfa z`pAsFMPc`ju_x#o!W1&x_|hT}k58r5nIzRi@%Y;_O45D;7eh@a(2egh%w*a!@i_+B zNrN09?j#?i8oR#gpc+eu4^izK1Xwi8LQfWA(fMMz9-doa83h)`57UEh5jaoaH3DxC zAS+|MMS#9&Fcbm_0_2Kw50|-aIyI97(gd;ut`N9N;5`D@09JYA$Un|vs)7JTy5>&- zt_fdws{PXE4MM|##hR;Uw|zl>*H)Y8Kebim^Y?6ps{9>W5wE{{E8z9lZwI3O#;pM_ z`p^Uj_z!Ni*7|$5I)fO}7Vr;jwMrNgtpR9eAb?t{=s&boCHngbu-Xx-eQm4CP{0L5o&5zTE=U zX$8jP+7&igOU~@lU}q5%PgxAEjb}4EA-nTPrS>s3`%gC_t4>F|Lrtb?%KxN9 zPxkkn`|Q@01UR1B+S*I$+|%ctbMAM}`M&RbuXFhy#l?jjuD|{1|3356uW{Tj>4$RJ zPl!5lITn%LbuXvW-SBAMS|^&Y0xE@28#vzV2R)ubPLWwk5DjJDijWu2}OhDX6|K9aJ|h5#R2DNn*xq@ zAKjwmmE0YRlpCyMtxM3_ea9@9Rk1P;%1YI;>It*2EWU9dayAeS#shqJ|4BX`2?zL* zU9P`IMqxStiOV-jAp2%8X2}WaC+rap_P~d}2PPd+nILa+af;QGu;3C-tcb|l02Q!lDX*Rlu6H7>wmb~&2aK?@+XWZkcYPX}TL z$45p2V()k;6z}c|^Zr;YFm^7+U+_oyF~1PtV-bFw>hXSLF(UrL86>eY0lD}TYtQPB zpp%Y%pEc_o8VdWz0z*UD!l9wDh%g?aylZIa&2fK7e&QG!5+cJxL$|qQ0`gH$=b6Y@ zpmR79IyKQbd5|$QQ|Z7xFGSUyOXQoG(GXM9#aB z2hy@WJ;-~6(ziLFZyC}up&V(sP$hWr#EW|c?iE4+P5fVX86umsa2n7_Mj*N{3!m(k67hnN6LTke6<^4#maS2m`8$wRH5@!5TaGFb4 z@Jpsbj!Rhai(0a4!sIi@>-xgMSkNCL3RF-x7>>sL;o$%wskjMs#d=D3N*a*(Y*}kG z5E?Oh#&C%ykQXX58GF zs!dbY@3#6LWlcJnDG&*1ywaEYzxTnE`J zq7MZt;R@xz73x7HaVsT6y1!#E3SU3mH_&tPD_w_rRzOpXdAV=~TqX)G6Ns0qlcg0N z21I#tHkfv|rkt%`3b1|MJ^cfHy?s5e8Uh;>Ku5umU_jIXs}&xlIo$arO=R(j*NzYL zbPt^zc(t$pj%k;nVR-bvkp(`vG4x7YqMI+Px*^-1242sN>M_ zW5+P3E8r~zyfR~odsIl}k`pUod_C=MPC1*u3>XW#dtT`~1}t5<=q^Ah1VX_vAgLHH zQ{hz023A7)dujLPlymbMkiLpWkH>2+nxf^V)wmJ~BIY{5qFW-E;4r?f&@4ts{Tdly2Uga_*Lfs4_=1AduL$auu0t zTuf!P36sV|74a=J8ALP!S7yU@y@RGJ*0xxe?eH4Cg$ zb|!Hnnrqw7Z8KxySz81Gml&l%Df^FoXejU|b|0CUOTPwDAxaz2Wp2@3lWh5@;=a2z zQ(i4|_T+0fzjpm=X?NQ_ckkT54+d`!-rfDz`+mGHy{$Lp?EUASxMykNqE9+;Xmt|X z35b_=F0q?NoHs9FNSoTc&@YlA4CbY3<-GLFb^qYV?IU+v{(9?=x2D^BQqG>0G@~Na zUi7;f&3piT1&E|aMAW7WPt4!5VicDCM`pf}$@TIEDG4O`8zf|9uulxCSfX|Lr+~hq zgfO6pvdU{)X0}Y%C10Jjeq4B~FqxR!k#cWKIk&9={!5^W)xeKJ+&YONOqyauJM1#1 zi#swsO_~Msqy>VR`78!KZ4$q3Tt8u{L2r}RghjB#i;=S=AYl_lpv@lE{_9xT+L%f= zqfQwB-ECBn-F0Petwy;9Zyd#&^RGK{fw=r7LSfU5o961v8LM6Q7TcO*^(5?TJ&&%p z4kh`s$Z>C(VzigDD`8deL$Jjm?CYM`)F*a&5=VXAQ@iq%$|oHOhu}C1YF`&GjNvJl zpnepixYHe<`Qps!ggh|;gK*Y}eEGH6`i-b{+Oke+u z=<+i{&(D5~#OGoPM%EGvgc*5fEznxC1%Zphfpf87B%CdNEgZObE-(x+sV6LC9l@v& zJROW>i%w2JtGd`DiV-nu35H`?%kUXd+ahi|rQM-mJS^#5Le><@nu1x=aMnD$6Mwt1 z1;Y^`a4v$zQ6eJJU+9u`7^R`2!zjN`qiXaN=vvxqFTHaqQ(iT7G~=zDDp+*6r>)lt zXA0jvK4r?dijkDKryJf4WISFfPVSf~p6bGb>C!8`$hbYz-S19h%DqX``+F8^H{RTK zecM9q?sV<$ly`T=TYc@w%#q}qGsmV)8Bgi-i5WXG<=$)0&piK!`!ZG4$*%X$FK%kM zId*+)Vbk;JP0y#QpU>26yjgp_cDC#K#_59@Z$+|nrgysQ$tu@vx_RXKkq=*)dzJRV zPrk0c@~dvMc>leM_AjW})F~;PdUAr($~G+ThIKY|XrTYs$HGC3_~e z$HbNMj6E|Jtcfg!JBo_Z`7AhLyaTPtf)DGMpB1pM%SebZV#|yL8|-;TZyYUBde`nM z3x4<|LSf8;f7fHpWx;EWm00i^&%uJ%hRvnTpOOO$b}RW6ELeMD#DcY_tFz#zfG1%R zoC!x9*e$W#a4WIjQFWtGpyfCP2jqI?k5?~;g(qSf!{O1?@7X)fX$NV4`onE0{A!n3wh z6Ri^HE*Jach@5b4RIbXWr znY8zrnZBhGu42<{>BrT#s=r^G_HLc&d*G=_p1AqO^*25kOnX|V3zl9~`1ngEm63l` zF*`Beuw&l6Gv(aLxVg_F9>Z$%l0B}4)}CZ^U!_P=g-GIrNXREvQ$lDV61UGN={KSLJ3LP3?8=GOE^c6wBs-O zmq^~`9-3`7=aQGJtj=uUGn*P87P)No58P!-Hr$u&oTq%rK{+Q^QL|J)xk9e2VmdZc zyG%8g>P)tVq;;9YZONXSZ%f9gyp78B`K`0fRKAtUTaw+BZ=rl^QlNY*<@ZQ!_x##! zvAt~ib)m)9i_~Q6ShjDnwSWGSX}@ikG%^~`F#gm_VWZ2mh2~oH16CuQLBN(d0!;+g zeXC;Khppc^TBi1?W{en#><@r%0RDg?!4Yu4AK-*#-i3T|z!4}JG1E%OUIMpJh_}Ft zFk%(RJK#Yr)PPgU3Om0Kj;S)C1b>b|`G{R`qff8k36#q%N>QtVc>^i~m4j8xBTy}D z5xfDoyU>pnJ^|ZcjofD?YS-pGt-{j{IVII7smm#;LCHp8t5AzRnXd!AIneV4ly72r zvrvbg>rrQ;t_~k4XXCUAn^3bsXcy|)IH)a;l1AC1vW<qgQ(-XIH zJbsIjLZKOOHmh*{-w2~qXaS5ZSch_8&1S5_W_e9ok#9wwu&E9CHh6|QvIT6rAn%y} zSD=Ws97N28COL_^WZzKa)F{*vJ{aXs`J=&Me<(D;`}sC<8MX7wspI2g&V?%Cd&1C~^JMRw*ylM%RU0CEtl{{F? zqY4jYO!l=((8FIDfg3YC1UI56RUC^%W4ss`hNT=sP%7|L!4JJlZ092)-w!9P{0gN8 z_FXvE*6f4T$ZtP39*PA+!LSVH@VF@A)x28FhXUsVAvSB6T*-%qOfWnw2K>>0G>4}q zcyb$~rZ~aJf@9>!YHe%fRXZ;&4C)KgGzkH@F~6cAn^QFQ88nthOy+nP=xL}7A~EPc z1$Q;_F>xIC5zXgEh(ZtKQH3U0_XG$!0F07lpO-BDa`*5%J$Bb{6Y?Y|jfb%^0f8_` z?nNDWR9X^cUe$752o94@?H4EHCbS4c;qkFk0T~sPHuQj(9!O|I$VATRy?hsQ)l(jW z_QM$-42Aep0n9bI>HrZYm^|Qcg^PhUA3qj=ds>iR7!<;OI1#WK)Dcwu4(h0*ue-nN z5S6y`QRw>QuaVrS^ngvEEE{&B4#|i>j=m9wlkMzfLSV!XtODiqckw&7Z{KCqh?Zp> z_|w52_Ge8aS*J2C*qK$(hE)L`%$D-t4cu}T;^TVuaoAsAtC21vrBD;6Crt_S+ ztU^Yn+aZd#U>EzP+8YHMo+)w$C)n{zeGmJkS{7qA&Qb&yTV*ayWK`~;G=f-3Fh!`1T)hX-%d^O5PB=(fUa;UX}`+He+M$vLr zYozpqR8EbbiqZ`l=2AOV=#M-1OOtRQ?m!M}av*NsPlG+c zjFi31N(p7;5Ku`xgCuJXMzYS6%stz8Tntc=UE)E|Mkbdr`eXSPKB z$+p34fx5$G9rBKnwSf0$i-y!){A9LJ<=4?{k&Z!&Ap(k2=&Y62DqBFzZitwjIEGS0 zZ8TAhvy`y9R2n7zMT3|09HJW8Das@oB`)J1sp|hka+&+QjdN8_`#&tdU$k-2Q+BOr zrYPC<-4ZfZR;AqanGG#7&c))Yr0K)m_lq0U8aEGLKm5Uwc~5Ij#RpQGa_@(hn+4Yk zW)5WP+hz_wsBfJ<4AUgyJqj|uZBy3!&e}}-cDirK)KD}?Nv5g}Hy7M2LX0NLKUpTz<{iOek8xO-%{YNpkkmEazRLj=Tc=6f8P^jG#X9>xcyc1?b3p z8a8<9(InW|vx)@VfJUZ7$vZS`9#Y>dL$dOv+#~`A@dot?)%4lpb+1Aw;r*JNC$SF3 zMLPPKmd@y5K%|lQY~na>;shlzBv~s7HDUr;*%K>%1Nj_(W5E!D4qZ1 zeiFop#}jjvrd-=wq$bc1_%?}O(QXjS)h0%QBBXW%IgBwe6uqcSJ2Im3XSH`pOo~E! zg3ds4D;gLGd)gia^9*Amw7x4M5^PMki@OJ8)k5rG1wL9&(sQw%C79%E#WTgp*g}10 zy1p~z-kx%9mk5#YWSFK_xzuUigOb&z`SHRKo}gSo1%SY-1%hfV zje|k)mT)Ad_Cx`tia1Vu8iFxf8%6n2yH;C>{$Ld1F&n8Cpvsy-qeS=q5K$&%ig2zn zr}jX$H%824`xSp5Ne_`CSM~ky))|OTXIfv-^AT@K7Wi9fx`&$x~3Yf+3J%I1=8bMY-t^1T(Hv$nx-Q ztyWlPZG|K?m6l#oyCAix>t*y>XqXb1jGPtaOZRr@N7Ne=Jrt!B{6>{Qc*GvVP{Du* z2QHALA=xCfx9NIQSW*sv+9W{CJsUiCPSLHHz(GVUlF}gBDWOe7lCLb{Ta>>*$-hJb zlW-GmS%=(*w2u&$pdZN@>!wHg$io^9E%P27B-A7^iJ@k>cgm3|DI@o5$xO+-hnJkL zhu*oE?3(r7=pzTI_x-+E;p6Zhg{2)M?QNbtao^jT+0Z!MpK+B>zj@`Ash5|EId?h3 z=dA03KO7qhNh-bkONrPesFjPbkA5%zEhb=9uJ!`(RB55RPg?ReZOf>>Z@H#IBO=`O zVs*j-(ZOPb^}2dy)MfRU;9?-8D0L|WK&(LciJ65Kcw(x`7t_=|i=w6?9L73n)0E0d zyI`AiFxpm?J8gLCJDdrp6y6ebC!ClGRrS;Ao~O{4-zfFO&8THyeunC;da{hd5=P0R zkauAhv8ll|KTOzTwMqrzEO_f}yw$-3F}xLmn5wcW^&rwYiF-g0QH(|i_?tUDs&zj+sVm{*0f>IvceJHT1U;5=eH=h0fG(rb9ZE8sxfRLuq2 ztWF|);c6i>5om21joPd?hv=sR&=oQ0^qASK^d=N(n2DM$R`c#-;UnucYC|g)cqN*` z_5^;hUBY&?Az>C90AH)}CheYZYI}>0S|H?0SHhM9lhpfuV;m6c*TH9s_RPD4V&(nt zaBDM6&N6l-Y@d|qATY!|(YxN<6P;uJf2DhaZr0g5WKIPR;{;D=SKdEq50|b#bKyTg z9U}^FR#*IK=Ekkf4f^pge0W(~SHX@jDsJ@gcvYzi$tv6kQ3+4QjT9`KHedao!kfgs z`J74I0Z0w_#uvsJ6;med+Uh0<`yI+jFDNyo{=dW?`!eKcCJh8GB14LRPF&FMkbB8g zI{-U*yKE!o{W@=l)KoU1s}hf--DsjT3A7~I>SPnisiHHq^}>dxxF|>fVKB)1vJnij z8)Yp3*sz!vfyvbnx!Ad!l}5h&cfTsh=w#qlL)kiB(?_!HmBq_ISb)v^oEX5tw{bB5 ze}Os)hMnlb-jpurINB+{0L_KY&%r;U^}uvbQY4wUq-l5kQcxa~&W?dRdj~|?)0uoG z7EzAaha|JPkc(H2hfn!KbUp~c!gCeI(nAaJnb_H&c(p7+Voa}DY0t=}ubuZ8ONbangv>JAv+V6RIK-#~~& z{mlc{56qpox1nRPvE}1!x3(=b?n^iBOV#artoHRy1W#sbXR`alR~9?Ea67)(yd&B3 z^Sb5-TenN4&r@lpj$f$Tp03+|ziuZU5&*)Dn#KC&k50}t&%@GC-KhYzsXoIue_V2_ zWS-xi?8?;hv)wl)=DI#D`;(V5d`qUTVfMuJBgvkn0oL zHnn|H^=Zp|({ri1=b+QhHr{w0#-^L!zVYqF&AyMvZjCK$elflI#Z2@5WsAABafvfG zG=6U9nth*?e;T;ov_Dn1KeMsn<~MG9Ll1POc{3Z4{icJdx`PNGX=qH^ z7J1)w+XH61QZ*4zMF!TCy7%j58)jpFMGBMIyti33m?ht&dHB@(qq_f4w*muPh%>!%WUA-kaWgcY zrLUMdlpK6#Uvl?H$L?*|L8iHHzw_-p5Dw2*_N6?1i{7fKo<+BJ`n6=w?B02IN6OhD znS9;@A$_j{bb}}T$cn9#X?KVkYnqyF;kD;lE(`ED{(a0zEiE|Xp544XHyf|cQ&lF?~u|@ z(%kO*cP!$^}PG`fO3U?Z_tm@D}v0SX! z17UU`BNU4z%NC!EMb7mp&BgceAZMPe^eQNcvQfWT&tqE+vpdbXHVhuiT3U>#fdOD?>K9wd@Sh`gIPdkc^Rc z32Lk<*4jKuVn^P!mURA9Z&Yo8IfpU~-<@-;!4m(7IZtxsUlxx8Lw!JmI7fKNLoj1T z*l6%HMMluG(+FNzLFc)0qE5skxfs^s^?FgnJn|^SG%4>AY&m&*6Q5R&EgX@sz= zpqivloWLB$8%S5mM_Q?48MfP2@k7+qiTH$G*sq9}GAzsCy#<6e<7oSw-ixRf{)VvW z09pc@$_SfWGcNe6i3?Bfc<1?~ZPs$5C^=iORQRPlbdYrv%tdI7pQ^nE1AIq7@? zbmMpI`JJO%exFZ(Zuo!$V>`55!QbK}*=MB6W=)ZJn-2RT!!dtIr4Z$HG7`ocbu|?# z%Bv&(JM?;6a!*JvB&%CAhtCx%h`3`<+?&hl2xoWfu@XL9Ksz0N&NffGC{{v;q_!Ky zkgccRd}ks@wniXC^2Bs7RsV9zdl*_qRT;F5(z4H;Tve^61fbUR>D0y-Q{I=fIuxT` zp-3Kvi2W9zdTPXe8op-+B3f4h4-og|z<`*KY`0b_NUb1k; zs3pHdMn8sH;zww+3OeoDX@pL@cGeI&9Ywpf(W&rW^_Co_hftd7)>O^Dl!uhn@)Dp? zamoLiDDq71Huk&N8u`3FpAJQ=GK&26l4BJlkzez;Nb<~cMo99^bH2wz68F98&L=~X zY~h&yB7C3W)3Gx-ss&*`{VkIReKnq@qsjVsxH6NA)m? z&+3i`nvGnNcQ8h}5+=6AKg}EUvY+8Qut8hmj~-hd%#A1~d*yoOV^z0Mn^G&f9;c;b zxsZbMC3H~9j4a*(fj*I@U<%&`Jw}P_7JPaINRcO301+?K=_vBkxLb;cg3Ug{ha%zA zD^-@~uq=6krFH^QQCa?kDy>vm*j6sRA)lortdJcukKHRisjWv*hu z_hQ=j;(cFN4vnSSUz#uLO1ZjzUR3d6Q!;S#?DezrwOi%}@6|q=SMi1UvI8mC0a@8e ztr<}BX8`L9(H$9M3<72pntdt-TNRDTd}E~21Ix|vYX(@}E=NB{&qT&UI8=bpXuSrj zMOz0p`@_cpR;Gg~6zV>AIT6U7af$YLFZuoa&Zv>WeAE3VujD znh|K!m`DFP!xiiQk2(V&xy|sUIh?GKhKtxe=9HvprgljFEOaYOC#>iU6VA$z%pVQp_$rA_ zNv)ZekTh31?1ZBzFbVSh5!iFkC+W28pbQJACyPeZ5QjqeTBW{<#8G)3@w3dh@)D+A z{4SEL-H(8&u)w&J61@Eb_CSJ0Vo)S9P#=;}rI@fv9khJjS#d*Y4_+sn`X*X}PkFd9 z@06X~LU05eo;mzo*!K1I73IqmyYG7%GM)+wlC7L7UUG27|5l*5DR5AMmR|*K(ybw2)7op(Fb(aVo_ol{Ne_pfgZ`i zd(j8E_3|#2&DCnrO-2mM=&%ggE77YbuS@)7F(c-y@q{UgxDl{W^0Knauy42WRJ#i* zDbQO;DDYo`^r3Q06&VaE0RD2EAklcJ#&&YB_c1sqOr3}>r8A4+2t4AsWG9_VBs3il zhXT_!3J0KV zJZp*?5r2aQaX_Q>A#Lc<_|^f{*1B$G`ubM%DqaUBQOy;*>031#lUVS!roFB6Uf-JB z0!rmV`Q~)_=KJMsIcAHLr<1;NQ*mv>%!ah9ZmMg^4R-Mm8(iJ>H}j;NBlBfPQ?8?0 z2!7Tc5%H0qaJCQu5m=t{;BeVuEf02`^J!Ey(DFAJ4P+qGTH(YXLJVBG9@k#0*AH~9 zRXs!LkK;y*w7iltmVD?;n-Mcn0&OO337gReKoVAb7|x8~Ww0DAmmHl%*(IwmN$cWWN`6eqf1-r?iIVq>(WK&cz~^`gSiRptF8Tw) z_-~*-FuoSHvDLf>vX23~-g_6XTwHKBq}>g(Egx^awRP_Nr?DSh`qNAE?w6;mFxM@( zThi{9*|B?@U-7sPjb**czM6K?sd$Mi2EUAB6XeB&hfhOM5?%c+$88(O|*G+@i8 zbGITSA#o*vkb^yQdq16=clQ{6n2-bmwg`kf>n;N6D^xr0QpJS5nrqtVq_2^Pfq-t4 zkwt<&d1%Zdn3u?QN47SQ4uwUx#>49F&m7<~iQ{08NE<3CN@~yLcxRu~?|%pkF{^)qECK2}+a1NZZth zWa{6f?3{Vdddb+Zjtf3(0RZA(U>R4TCx%a`laEg*`g0xiFh41yo+t9U(~fxI%7q0t zkFTK5R(!nS)`ofQJE}f&Z!ZEuze3QpBJr7e@fBBm>B(pKbtjSPMJXYiWyh^0#vzFZ zbh?>+6GHmp(_2)Bb_?mNP;XO?MR{JK+-XWSQo?o}(yzsTi-i4e3lK4ehQuV5F}L$g z%Kb4VA5(Ihl20i4_mo&E`5`4gLW2Ju0kVWh+nD$hN*F2s7s~y2O6asBBSf};iKFzB zlK+Ok=r=Lr^c|^9_>NS!2_J{LRx?wB0K&}nXC4-G+I$a+N|tQ6!wXYhnY7MqS#r=X zd{ByM>I>e@Y47GaZ`!+UnW`_HGP!M~$z97FZcB|0+m7k+VMxXlhM(`|__v(?O&FLSs(Y#TG#I%lm*9Nq7BOZUHdZJGUE`i7;%)|}i;t(&FR z&A;XgY!x`*=kZ=X{A;Vp=6zULY}=eXw8Y^yhhA}8+T^lT5Ky>1tSd)8y2Rl&cW9Yz z54V&^MYvHBZV!2bBDZbB?8&)jZVfJBbQ|unL2g)TEJCqmnZxa2ZK4Dq3WTOlwyVOgO zT68*f)|&P-FF9Db^c^pjE##^d4ZAvBv}{!HAtM>i9LjeruAZ^Zx*7meP~} literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/transaction.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/transaction.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..283a306771a3465ec5bfd67e2885ce91dc15c5a1 GIT binary patch literal 29286 zcmeHwYj7Lam1g6~20;=e!I$^~_z+1@d`KcKQSS#STCy!Fmguab#6v;srU;q@lm;k? z3^=rG0 zD=Gil@7#W%@gOC8JjrZTy2#tz_tp2FbMCq4oO|xYFA59uIb8qk=YKoi^CHLnieA*q zqCBjB+sJV@IDr!kliXRl8z&7@#_{dGgNkEU!(u=PP%+ zaxYNsh0481xjU45v2rgt%Nx0;Il=TACz#(v8|XS)%Hk}Dvx@vgiyW&DC))IH^7ES} z`F^&XmCi$H{xg)WV5My+o&On1R|@vmxU*G40m3b!Nhm}ez1eM@*p(fJoPF7`6MM7c zE|>(vm~jgd&enhGZye07Z{l!v99r{)IWL&C)--&&)?6@ZB^w#kivaaPjS8A(3?9cn zP}7DymZa&3e%0=H@Jy+ue?0dQIJg@{1Y`k$Zg zR<3s47uq`juyg0m2LhAl&ULxnC&t|8hNWroT!%X}F1kkp0F*!E4vf*OR)emu$U+be zi2%1!Z_w@a&$us2zJR1wAlE$ToeYW{sao&>K^*f=Plnjn{5d|U?}B^!qTmgQ!L+7` zS1|H?{tMcdwCbfo0L^2Rh)AV!1VMtP+bxygmb4O(Ho%XZR7pi>*1F1lT+BHdfg!03 zuR~8xlisg_Tu(Eq22k7~Vh<@i5IWkt=YxUC>5w=QkbEHD4ghh|8}eQ9&59jvacoS) z5V@qLdQyciAPag;NK&hC`@K`5f+`iTZn;|b7;pisq<|xT1qcXCc`<6JXi&y^qxMy$ za3g5MFSsv{i};$PRl$n5gX4keNx^+y)HPfHx`)xQ?3&!ZbnIzOSDNV?(^W&6Ws+gv zr>=@q7eh00KYe3bOHv?X^@DuTfHrDDW>+Zvq7=B~6GXu+Vqj}^U}R5U7YWaXRHgS)z$Xv|Oa|Ck8Vv}k!6bkx ze51g5z~7}qFh(NvSmudnh74}6B)ZQH1^mhY1>yoKihX_{d5YD8ZfY&_Uh?|bbk&t2 zp5bx8Aw9nvO$8cf&~HI>Uk*qw6M0?sg~s(m5ZDW(YMz`7T&C`Y0w@)b1fL(nGlf98 zOEM{>a|~VYeIeq$?vVFo4D8EbxK!ZiE8dwR2Y{^YaFrLhTE66=(m^03q z!bZs$Hk{!g0~u$VPB8{W$YxaSX9Hv8vQHA*JQy^SlfkYbk3k~A!eeG!H|bEvw=!gs z`6|X{#iU^*XhvS*f-;7RX{50VmF%-kIr^KK=`iXDHX*pmeXy-NzU^pq+tJvzfhgZL z?^a=%KPLW9XN}6e&lxSj*Aa-#{6DIzonQ|n}LkL;t(Me2GUWTz`{%LydC(KJVRL+gy zu8bnqYsp5j6wnufVFa&n>qeuc@gttMG%hu-ak#AS$UTUha9T?{42)2ksuY*Bjk-DB?I2wH;o!AotoXRJGW1>)_3U@tWRfP47B~ zh&A47sak5f({{V)pQNrHA^(= zq(%w|xyihd5g{--G9tB86wSjj_&Vr`f)n@;k}CNc@N`_xLGhoeAN*-ZZKohQ&bALr z)HqN(lLqL)3)YrPvK*2+@tw?uZKpU2PU3%EZCJ0|FpB?F4J$Y@Ngtm%=EuyXYa9(l zK+h|uc8Zj&XV_i28`ndPqu_aZ*g*jUDMh7m4jQ2*{I4${AkOh>b4VoyyoEFYb%Jtc zTobDHql;HHD{vO>%;U<_Aj`PI+?&ex&v17QLmqQ7-xu`xgCVbfRFt+Ncamr057Th6 z)NbU_PYl%~qCYq-i6cHW^N3o3v{r7Jp?GCmw6g6%WqZQmoVOEX`mXiG?F~_T!%}n1-WoBt$`nUjIBg^lE2L|k14X4+ zjg7N$!X9XyCcy}e*(93Btb&;|X3;u9`e4SDOM@OW2$VaA9z8+SpK*zH(ye6;Tgcns zD>*b|r5yjMOW07NI-?PIov6)@8{>t1^xg4k`Xf;N?(DiIh{7KGhGE5hwU8^Rre2jNbk9bu2K4Pmd)f$%Az6X7nQ3*l~| z8)2WY9pN5f2g1F=PK5h}9)$aaUW5mPrw|?#b|E|@>_&K4=tFn}mZE)3OiSh|A~MtC zlQxi*T1NwyL@CGw=`Ey(qOL12=0&u>j|=yWn2?9d1PE+gJ4k0&lz;(3oOLw-Y(R*4HlMkF0*M=(NjNY)_fwU?q~R3vO6qNMaugO#20$ zZVd?^W<^rd(~J|Uz(n>HO^oWwC*6*&A&(vElsp*N(2!{DA!!PRBqoDML|sW6Q|&_H zl@LZa^@Nd{jfrlRmnI;sT zb?8S5B3?zF;2$BtAgSPNg>imMl;84zuUWO1F9_f7e_(g7IxBAUUGIxKw?>^?mxSek zm~+Scz-md^jk@b~@shS^Nn5<6Gg{KQJQgc?YTmkWO2S#G)iSs;7<2BM=T{vhFgki7 zydp4aya|oc#uNb!X73ai<0iE6gCyk&8&vvG@1PUI?3Zq8$%7{yQ|n;2_tECSDuhZInvpc8v9+!PeK)puK8SX z%aU-%f7`z@9BbJdbL@-o`?BR}0taI`ZFpcT2lvcNvZhp(uN!7KSspere$BMBEPGM0 zLa*9flV*MC%1r$pz8Iu&3=!Dwc;n#f2Ukn0=ZDrzTtPK!bJFaUE(Fsw0b+Qh;;gHh zz-DF{1DS1dp!-c!Ko?8bCMr3OLQHNNLo{%ttHVr$(*-;(r z$d|ojS%u|AUO~yKNQcGWGHGZ{NliCs^-N$IZN_R8Y%$7GlMFF`2u4W8o;p}2WlciR znW`1bl4^ZrupA4J0b8~}!J=niXRzo;Lc6r?|iwwbMJ~s;7gkh18(c%8GEDF7}Nk(eVMx2d}y-k=}-iR#j zXOoFlPMFm%hr*QL%@>cOu|Sff&h5MVKiWRE9Q zBVZ(rJKRx+d#NnuXo>JG3?=_SlI?KPa>+Y64QC@|=}gS6Z{G+09*-%~j$cMj$)3`@ z1x>sCx*?o&iCTLwY@Mn_4b= z?wf>GTw%j3!a0lI^%?5dbi6rB*fLSiDohI~Ug?ifFyl>aQA1ib_7b-I*rK*-&Z=uo zV|Lz&t=Vy5b1whLuvNo*9`K&)yQXlSng^P|XHFeS$=sT*ouh^?((q2zp>&Pa zB3>X@ASRj0wd?zra`6ZT+>=n;6vGVsCoVIe9NGPZIHF&?jD9gtqK9y9nH1rP05c8~ zy|g~bE?W!|DFIiaiD9Y!WxmQgLurc4@oa0m&cH_%JH5u#%z=Msm?KzPcFtQ zokmgV6oRA)K*LJH6@+x$t03}<`us%p+)0DgvkcB6i&+JA_1WTHrV|sYm z8pN_qQ8s25qP%iF7CCPLz@piag2aJTp6{dAH&6BSyE$P=N8gzhip?{`J^Y-F} z9k1_MEv=2z9f*}4j1(MvoJC1z9r|(qTPN?HjCUM}b{zPR!Jo~&GZ#PfeDu)sG3V)d zK4Eu8>^0vHF7toyl;1|mRH?4d;M&@yd_%RvUFv|aQkcV);-bIJ+bn= z_g;vWAH6pCVOo~ySb69CV4|e*M(y?5d27O6vhe)t`x509h%ByJEV*uv7dJ(Vo0fM) ziko7^JLfH{mDM-G*TeD3u4rY~@|Cy3cf9w#q`Z!~~0@ zczA;8YEz5H8QF>hTLDe%$vwRAG4;szy#*e8iWxLPts zIeFPT1CE%*)u@VG-lJJH$jp>@9#|XG?n1t)6jK$ZCDl7|(l@55e!7mN`21F`#z3WRsfrZM^merl!an4h&b%%vc~SH$J)uy?PDr|4G)p`H!ap>Q5F`s22qbU`daP1|FQ-I{O%b;aOOR3Q?I3)cM@R~&oygJ^}*DTc7 z-9!+iwtyhYz-VMM^Fv;Crb}Abx>uSjzeX5)QHt>x+%vCS#{QJf3%&!dOz)40?_ls_ zEsvI(+cD1I1uVL;xSDfnAO;{pY6k-nSmGy#)<6ro6g8& zj!nw0yn0Baxj~JrEvb5m9|gT*v_*hc@L=`xcamQgYoU%+x~Je_9!NP=GefM*)y|9K z-b=Kh0ByvIq&VjDqnTg~o7BTttHufBo++N`84Xkn#*Duapwd_m7+B!^1pM$Z5vMH5 z)O!F~21SaX)S8&je~y*1Axvt=fa#>wd+{O|kGjw?kGM2*Ln&)wn(M%9{jkpXl%3vB zbSST#F*=O%?kMk$^Q}?7b=mTe@5(mxe13%Z*BcNbvrUmK@VU~TMtjWQr{S1pOG;wK zT+jz2Ps)-(@fqmEf#ElRIb@QqBWESITHF*vHDf3_i;Ff+28($S{8!rm0+ zo8o+Xly6^t>LI`5pOQ9zumNrU6EI08un`FopZq=5DbwteaK)!Lrn-_esw2qG)@?{} zl>8MPwb6_m=Uq|W73W)`e9O}GL%uUxR{NYaQ>bu<(TEeWMhY2pk})QA#L(`WOqrFo ze$oC%FiUMXwlV3P(b5q51v3|mP^;-Wtt`2p^T?aa^vn6FwNr@SG;IdS&c$YSc_Sl4 zb9wtJv^%U96O$&fT%5xGC_E!|DrIAWtc?wYatTUhI)Tp*>yHm@kOgqHJ~jx5smc@T zxzp2Fp`B7}Tw34oEfVF)<|Y?}nh2*d1}k_V!2LehC54G9NZ8i9B9|!nQ;L2%J!B=1 z6Fs<43naAC3{^1h_^oR8v%fx(K@)`CT=byh9{*TWvQKD$|J|Tc)#i&dGYxB17IF(! z?6Kr@t6N$U`9Y~jEyzM@W7N?Yb2LZzW_hfWq@!MDJkb04egtU_RoYSrOy{r{N=5o* zlCa@>+z+%I8qrDt|IGQ3KVAiMlk4xaY&p>OP2G?4#MO{9M3 z%C42RNbSB@>HbK;{**UT?nR_O0T;XhNBgj8GfyNqBWlnLn^SI^xwd=ca@$l*{!%Fx z4sy6~svb?Wgn=z0xxZC_?q*!M+&Q&xnZvXQg8HzagQE-Pto|>j)vPT$X(c*Q!Wy>f z`j^Y~JjX)Q%LAaCTHOK@=teN=DJMj0L+m1&5={EE&j|Zm3sYqL%DA-NHR}463zoz! zGp?`|OJy`q;!<#v-Vd|BA5C1y&9WkB#>PbWr(rwcm$(@&qxTklsmt6I)0eo*29H%4 zSzSA||zv|es*!2D2t(luMw_dsV$^%DtqNe`V zp__+dH6085S~FKxJAdlqCa$!0vHwTTJGHlK;|;r_4Z9wc>_$f9aI+jA)!~+?uDRa- zVP(To(~p7=Dz_!-8x~E-gkqJqEAO~&yWXqWzS`KaVqLxx+5JqU^VwMAbCKHTKCEwD z-nHBo*?Bb5)*q`Mh*S+Es_Soc-0b*X*FyiQvu<(pM?H7;+}?BN(CtGHoIR_q##`Z= z;kav8)U|6Rc&|U^I=pav%_=w5#MLw}U0NECbRCH_AB|P_N6Pwt^PywQBhG@JzwfAg zzq00ID_7}W+O^acY44A?2Vxb+BE`qn?3CdX#zen<#4^y#{T1Iz;r63jaeKF-h{9(3 zG0ybv4hOB2Yj!g;y+4PKQup>Zl*dzDADq9{b0bJ-O(fg`7J zvat24@fi-T0M=Yvv9PqAWBGleL9H)!SMjJw7ENjj`(hdw#Fv`Y)YM&vU0R#SxN;pw z#M~KL(ZX#RnenQ&hHdxe=lD5W*b=toY@1cjp;23Bc5Pw8szTl1^oHOH@^$CPXUW|+ z#BR^dt=kJBOytGi!l%*yb7hq#MfNSEu1?3(KnPjtm`+O_tbj!qnL@ zpA^LI6UB-A6E|D>QLU#qMCP&-fbPhqMz$w@*2!{^L0CR_f!roO*+NEo(a#R#$PlUB zNjuKw!68euFcnN%1T;vL$oYO_|AJOZ{c{SwPr(l;_*WFrY@N)L1h!RQI!sZ-2IQ%n zZ6i5FuU|!gy%O4mdjfPL?O+nEBz2z zJq5B<;H9#p6i6f=NdKAw8tQV*to0<9C9QH>lDz6Pn>2^u%O51B&jbb~U~+@>5@KhI z({uw$$*z z(X?8z<;M8+@pwgBw4!apZ6P48q=0uGvUegn;=~)@P zckF&|tmgQ2ezl~ANfujfZy^!kLCKCpRn3AWQCWLq_WJCC8S{E=L%gOVTGJ7$>AKE; zSm8=k)-8-j>Q6?>hSp8S%F;E?SW?R7|A@0SQqYPJo3Y~#SJdHJ>bDj z(T-;xR6eVTH@#6;Z=$w2UfU6^?N}avD{wb(@5;|!dFK^$+FiX!kl9jq%YM_obo@b8 zXQHWfkx#fRt1K1#+B=^wh2wDNoC$##!SsG8~01<`>nPl z&tLtR@8!^QAq#R7xpdfcIfa{ONI^z@qB#_fiK>jshxw}|km-v;=@MI-j5tHPvq*`| zxYFyQ85#L#Ldv+pCc&bUi*5cRI+`%+`rnpO5U&&M*>NguNe*8;8beckbXWx-xNAKl+ks( zl8mp?2FqoTj%-HPfR76;JCds=6+Xetkzq@N3OCWeS!rqolT*PlNw2Ti)cM*^!DLt8D$nwm#CcW$r zT=q-<1{pm~5|OF&V+x)?+$6)Vv_t_DA-_OR&r={#kdhla@S3dBB*TnV(@d$A1Xk(a zQgE1p3}KaIP#o|A4>Ii0WCC(BU-01wK}n>uao(mPKFU*;`912eEQs1=1GR-gGp8t; z+Ft$dsX`|6{sd1!I|7~XDa)P$BGbL|l$}r!5==qQo{H3-g2-210}K9^x_H%7(W<9b zgnNVc2Om^Dn<%UPfa=;EbwRAVZ@w?YT+d!Vo!J6YhctQkRAlQHV%5(_%AS9`@cVvcZKAb1-nu{9 zy8qsbvDU#woJ!HU-1lBhkJj~PBRigpHJ*&ro`h%{ckPV2cCPfrT>BS}6B7EqGq-Gb zuhNsKs+Hl9sC6&3-RZvFz0&uSLvJ7Yzb50B>NTE$YiG1-=SuIrs)toa-!HFTt!`T$ zT<(kX9E*4cW7SVb%AWpvXVp?!ykTdw0r?&@^l4f8Bc6d+^|46Vu|#oMyx0>h_AEC) zEbd;ja}_ne`M7{9u70dkO&Z*_V~3t=u^9t0D%4 zcF{A*AnP>cZ{#Gl(Y|(KrtE;P%=I zrU)4Vy43mIKr2f*J-YR7ZR{`>#gfFanISz!vr!m_hOwUZ z96pdNrpAw81x`IuSfc)`zEyvWXT5Kg^co^E6k?Pwqpp+4lJ5MyW;EGcOq*(m7Bnoi zJSgydP+dPi@U3Tmv)D|xPuSLhZum}U6jTok1iK*+Hfgi&1xUSmsR4+}a8VqFYkbY8)tur#6PsfTJVi;h4;$C%EGztt{$%`uL_ZPGKy}aZHpP zyMwyD4HdI)zwx!#zXrQl#NooMcf)?&{@p@IAI0UE1xm_q6kade+;Djx^NU8mn&-zUpO2j3CX+!BgVlrB$=fwkPneM<}qD%GKyglOs?3h*N zX+?P000}w=q!Vo05Siu4J6b)}LG^P?9n#F~(J6;|hJ>uG)Y7|XW`+o&<1fe2k#z;& z`Fenx)!yO0c?Vy6vJP%^9@Z0eT*c&LI{!b=*bIXEIdxxulyjHLUe!8J5oROECJ(VE z$LvnF37+Y%3>CWFVcL;SZaT#Hu{a_{F#|_bt+GA-i|Gv{Y^c}fm}z;kO>HvaWH4{( zD!!4v1p_h)+Zpx}wa1C%PYbZ*F~wzECp_xVj}y{p8xUe4c?f_mLwVo zx(aXt4`SF3lYEUIVbuzLj+xt?PK>FWqR2v#@?U2=$=FUOw(SCYH>kY4=L;=XcPePr zYdaP)5&NG3lr-V@KM@eqsV_r#_5zqH$1Bwjz$d4}cg|oJH922!t+SQU|{xJ^Y@0nCw=jO7u_mdBj31J9TfyCr;v*1lnjUrcT__ynl0#n>j*x z+U60>2UF!Z!Rho7^yE!#c}!E~mEzoI+e_c2mQ|+-Vrf2}GwH2*asvE!>EBB zJD;WK{{>Pq+{3b$u&<#hK{{Fz2bN%4Z1`F@?&yj-x?+y)5q>-4rXJ(qAU;Gh@{s4K z)Ic9>q@alc8wJf29H(F_1-Q86hq8&nVTYOC7>6W91g8atj5d8%W=#;9pX}Ou2tfPqfS~2^5e`f*BcPn=5IH z5~)Sf-%#)i3QkjSpMrNO_&EhM<+3?WA|5UMPYP)0vQZ)trAdTSS+OA-jjV9l>4}1u z@gKYiguceX?p4>AsCIp1E3h0kd{EM~W}ycN@fFo;dF+wr%Bt2`^4d;rNAFJx-!6=A zKN{VB^dnn|rT2rPk~hY$jlVH|Z={>&2TdlDTz_!|QVJtt)r0yft@s z?p`R`-M>z8>qSOO%^F{5aV}8?TN+-c+gf`We(9ipjl*qe zV4ZGj9px5yB(8C|EjO>zZLP~MvLbIN9mdccXZ!bxt2#y&<09XAgxgz>S)or9Z!#r8;q30hIXUD zxUE;%EmiB)`IaqfRr!|gNZbB34)^;=#ci$1ZrK^xc65!y{eEbjZfnTg6=~f|`MT~Q z4mT?6`M_3!Qw&YF_?!H#!kdLlLD?cprqoE){%GlesO{jo1sT_>@+?~yuPmG1;_vcr z72Yj`&*VCXxJT7S%YjFHt!2>gX#Y0LQNyFNhJ$oFj31tGJ>o40icFSmkMd2HUJBfg zN)48>M^>w){86>p()g$VKO#Z-8Xpy<0v2aOTtyy&QueCmTdy)&ivrJ+X-_S)@J6Kfh6+y^}{T ztB_@68naxE&a=ci3968fE=lImGma5qtSxEN8jvg)7&>!I-IUILb3#5jl+0o57+Q7+ zs;q1>o|ocu|BJFPw#ZoE-_q;fQNRXoJ3VOw`V)S?{2L?(rT>itnm2-V2w@E|7}l+v z!TERG?uXp&UvXu>&I`Lynb@2M|QL6i#GK|%zN(jM$CskG8wCk5v~lwrKTw}_kr67 z;!S&_O?&V4|Lo*DC*%9iME9SGn1>T)9-Xx0e`GS%pl~HdSfy*JFvS-lt^@Z%5m$e- UVj#jfe{DU-8+JV6C}iOKf20Sz8~^|S literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/tsig.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/tsig.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43993999b5b5cc23e8f1b3f6250596cfae8547ab GIT binary patch literal 17008 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/ttl.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/ttl.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d419bbf5f708cd38557945dd3fa67d3a8ac9bfd GIT binary patch literal 2334 zcmaJDT~FIq^g7P>7Xd;CRJ5k+3gIIuFhMm{o!Y_}QpdEywhFCt-1tIV5}VpLK(es1 z3TbIqsiBods&-OeCtms+ChcW=NkpYZRy1vDU;37fKCy@HTswhKO}mPH&pqGg_?&a| ztIy{~uzvaJ_juZa&~tjIFIx@RT?JqZiD(XqjA%(Pa~6@EV=ZVBiPjAy+OXxivo3V7 zUpF)#oGG-;n&&yYXvYrGft~YK(YaxrbBQkO#Ey&up&Hf=Sa;eI@?=`ar)Rj?*-KnZ zk(aTmNs1hPN%c%8sJhoTU*BX=%v5Z?nc87D0KgW)XpRBzEh00=f<{)iUl2r4nCY%- zUKSE5%t;z2D}-CYiNxT%q9(Bzo(@@cC(p}567#(7=J})|rV1{JTEFS zp8pZ;Qrmm)N8(BnM`B9iS~?Po3xuR2%UE8HB&2JRr8J2v@<@1SIHE}e4=xF@MPUJJ z5mDA6ge1aCY1Il^jMp&$H2NtvkZ$c)X;KZmZY6k?=yny8lq$#SAp;rN;h~w?-2?0v z%A)IyRLTKs+uV@qCAE)&fOEUXxyHyplX?L(;6-M!7aXt?YQtE!7?D|{k43bsu~{^Z zu6~cunl)?vU#my5P(+y);Iw2d4IWtQ9%Ou!CR*3nyP+(5*dpk5)$Jeb^@yl#(PsGR z$}(Wbo`FaVeT+N~G)Du?*+3fwnyZ24ZlLu7&C@{hHqbhO<}+w^YOiLgi9j)%OY~>0 zkC52(16z;I$eNL{y9Fx(AC0_fo(7q~0dE%ayJ&$rfOyD$TP=>mmuQq|Zj_iop_a@) z7mb!79EoF&)(c1Lnp5UE!RRQWaDvQ9GQkU24ZGbOtXW{aFf~2So#9flL^MuI%S2eE zl2k_HdU1Fm%=HYdjEe8ZN0LLQwVn{|9WAxAq^JbC##Gct4fYLysc2LqYBV~q?*=pu z)m3A>EJzyGfD7tBOzYol&uEmB<_!_r{QuFMrkTq|186jmXcR(;M#DxNDkKcezNG2; zKxbB@_4iyzXGCfJ69)FU6!0V1lP}K#gU)Cfe@b4Il@-~Dn(NtPdNKi`3Q|In7wRd> z3{h9KxROeU+%*h8vVyV9od)Y7H{91ZGRzHfLtL*U#}X+`TE?M}U3W;DC@n}tXTd$4 zO<>vVU|T|2!K&`~==>EP^oDGzm-=EO3K0^zBbHKC3<7Q$W05yCFfn5m(C@^lFtCyC znpc%1PgAXOa6SQ#HUeF|j;ig*>HRkQO?Kn!N}x4A{=jwHR&jZAv)^^(g*(S8{$S4g zN1!=pE4Q@f9Obr-oV(oI_BT4>I*~hHZVKE>-%j8CsvLN$Fudu@I{MGv=r!u zQ`d=2_n)m@l};{ysT^$En%taxFqLP@t-%6&|4cd9QTY7Ha;a;u6dc@~D7PIcynX+Q ziA_D^OTmFRkmTW)rC|RXNcG_trC{hVvJ^bIIq{;Cdvfk+_tVy==ZoE=JDp?2;8?ly zD0QoQ(_I~e5UODWo_F8LR$AJN9sN5k14ZvZ+2PIIxRuUNJf6J&F(jba_Ti3ys_2|5 zdjjj1jNz)=jLYEAK&8Xj7^o@$TZrtB(<~yi%8a#LgH%fv%^c>T@`rgE+V)`Rg4F(4 z6Pbf%4q2u%M`MCaFDLU3B%FC;LH`FmGYUd?e?~922~}0p z5TkYjQKSCX%Ey`%7O{E?5VMpr46k)maWv=OdEENU(N%7OBB{7qA!8%YT*oU-?S;0T zrsGBD@fUtb>*$Vutmqsw5*PBQ^k0DPqg{+wV|aj3GQF=mS4lzG*i z1P;SV&}jx+}CQ+QQbhGG6XiI~pkC|LC)PjKDw%FZwY zxtV)cZ(se*cdFz&^$G!^>WnbW`HNKqTjAm^-KrvEXU^o=d~DNIMF19Jujpo&{TGlK BJ&FJT literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/update.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/update.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81aa31285215f3e073feb89713e2b55b8cb060dc GIT binary patch literal 16266 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

@pGIX7^}@!`tMbHuDL7LJbqp0Ef5X z!kiedY}#1N%xPxsj0ydwQmYAE&<|H|t9;IU0(Z*C)4Z;6gJt>$Clpt{rub)B*7ZZd#AOj>#35P8u}nwcB}<TUY8n{r2w7`X<{>-MA{OlyGx?b5YM3W<&(TPZ|hzxLIgt!Ls5x6WL2jX4353OHdP?eM}@7|4#;bOm?1`RQUnk9+|WyR6kQ&Rzoc_j+j8h zC)6+jj{TMk6n`vZ1#c2l+7x%E7C`xGGHC+_5CA9pVO_ zarE2371G*shQ&tjgB z402JHo+~c<`B1iUn^d{&v*K-EYUaG{l7IW+ z>r4KQTzTk&lk=zNPG{>{q`H>R%3GlB%w=Y}MEY&oBBz*k+8{f~i9*|lO+^O!8O1g428>d`w-8&zi3omreHD#+?r0SL_ z=lwt}AT{4LUzTZ?;4jerS>V*Ifq$;ccAk>p@5NK#_F7sA-dCl;*~*3B;;uU-ZD772 zVgmd^!8f`myoJZT#Ft`#Xn@S4?2V{C5D_jlGGv%h%MtiN3VLeGr7YJU9Z8B2-Md=7 z!S4Y>jpAL& z{BRKj^-}jYX9lSDlb?C~_Y)tYsEObl0ZOeB21gr;#=lbdTY-yuZ+PLf_nJ&nj5~r(| z-Q=|h%LJ!`Gj-E-3jwKg%W^Tv_fS>U=pcY=Xdwn(*UMg#@1wl_sm)iu^+(eC^0#Tq z;aj#??93E9yK{c`-0u1PbNi*r?Qn;0XI9|%ipp0Ql%wcJ@4WjC{(7+v^Gla81vIK9 z0K@B^2U7h`llo;@dr-0m^%g>fE!bvHq9t}dG8d78%_c2)esucX(|Y}KQh&waw%2@B z`GUQZUVfAI+IP-c=UsCyGy*SLrQr5u3i57sk?3~!3cfAZzhVc=@6P#`=U$$Fb?(*0 zol;H9GKHD91SP&Lu8{ZTOSIqKKHD{ac<%80vAJW5T~cl9G6i|Jtt9%?wt{cVn|IoM zYl=Z07J~?jL9{$fQ!np*;AHH+!iitDLniLFfa0RQRq}6L1QgMH%O?3dmaz&!1Ia`R4BVz`Dchs7v6K;Z`;9%b*~J^>d`_|=NO7iBMb)#Ya~zN$Yd zQHr8HK`z2<^(PH(OcV85_UTFGhYBA39sPYs)1)9;9b}~*NKsK~nCyKUQX)H#q{M?L z9`G!bom&4+)wi9D2!5DLeiqD=lL4hU`AG$ju89Rd$NjTwukxGUef-biD*_4OXAmq~ zX_|hpnW7v1ldAnaRq_QD_<}0?0u!ZQQ2sBdiZ7^gk_qXOhmIn;bgFCS@buwqV6zn1 z{E&i-ht-GebUS2&`%X5nRSIlo5*3`x$=ge~W;%)09&cin`; SnDka>y;~&jmUoMY0R12Enn1At literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/version.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/version.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3096ed2b2b42a0f08ca91b72fcb9ee72b4ca7741 GIT binary patch literal 754 zcmaJ-KWq|F9DeWq0_6^y2HQGSV~`HzVzG757_lLw23w`3!#R+H_Y_j#=p7PZg3*ml z3^8H6Zj6q`$>;)|$PI?16I)F;CVxkP0i!Q@-|zdq?|Z+z@BNe{4}$gM+po-pfY620 z%$7JMS2mE3h#(aatYX5b3}IE4a4H9trySuO9a;)aEbqLx#lk4;sm(mZgVr8V&bW>2vLSCIkg4}C`*_*=ZJUCc}G>ns?ICuZz39xMvefEqj6g}iYE3W zaa)|e!S-ZmWy;Uc6--3qJtd>(sgl-nsj8CBXr)qBDbqq($z@Z@b(C=E{AgIIolUIr_v{hnX0*IRLn1F~+~|BD{4eAh)N^4_)pV_*UxP zA)Ew`tLV_LgbuqTKA=P|5+ zwbqlj8?nEpTi))g-o1xkgl?rLKdr~$h4M->_2Erxr}OO7qrMcd{N3AUxB61JoBOKu TcVm4i_C3&-UN$_&Dunw3NM7C; literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/versioned.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/versioned.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25003819e98ca964efd3c41150da98aff6f5a465 GIT binary patch literal 14796 zcmdrzX>1(Vc{95^dtY3V;vpWxLnN0bm!fWq5-C}SB}=9gOL1c+_G+~=lt?XinVF>| zZkG=2s0|s&AlbMfg{B}~6g3s6i2S3lQUu6Rv<1>1-4SW*W-PP<{HOU*U?s{`>K16f z@6E9Xm%ak@M~CE_H}Cx3_rCi*{*%jPr{MYZum0uo(Evq#gdf_&sF{t&G({~^95qC7 zG-rs=Lo|6Ch79mFMvP;oA=4N$#EhAT%wv`z3$3)X4%tXMyZUyhZ>Rcpsc*OX_NZ^~ zkj+3nOL3+f6vyzk(Pj;x+rSV+r z0D&YJjE2VeU{JOPgJUsnB7%8mF!-&BP(-b<1cO{G91Py3HV~RmPV`)kjqyFrhRvMo1~XL2 z`8f}agC!=;3nkmQvK!P;73YJrnk$F2hO5Z+gq51O${VJkT5j8?!NEC6%PMHOovVgX z>bM$6>$zG;8@TO|Hga{4vRpl+O#rK(Yxu$#Y$ULM{v9?G?682H<}WduvZf|jQwu

8X_Z9Aa-2vWcx-+QbjkY^kym9+lmfkqP-^tB29^voU3yigQgj{3I9ecL1?oM%o zaqHS+ydBRyPwI@lccu)v#`Zh*eB-{g8kRB{JMY*`#(KITm2b4K)$A~~-3jr#jn=hU zzT3E8If#(j;mBj`Ry>2WI6^lF#W=u*ZX_PCeH@W=KJgZAiQEJ5vKwLU>Z~8@I(W~t zM?O|@pZGqqlW>Z25;zWY&+~u775ojC{|{XG-*Wc949Ycz-7 U&o8vaid&@OmVe>cBlCXwKkDXc*#H0l literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/__pycache__/async_socket.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/__pycache__/async_socket.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c703227c6be99f420e6df441bdba7790eff5cc2 GIT binary patch literal 16167 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 aGOjOm(=73l%kZU0UEE&x6+=&HkN+PcxO|@g literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/__pycache__/base_client.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/__pycache__/base_client.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea32b956286baa80f42e3241beff5b64226af28b GIT binary patch literal 7400 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/__pycache__/base_server.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/__pycache__/base_server.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f270e0e94d28ad5b3246a52fef1de97b9bfadc6 GIT binary patch literal 16908 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 E00rkH^Z)<= literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/__pycache__/base_socket.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/__pycache__/base_socket.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63a32bb89048a1e8e0788f2591b87232acb51630 GIT binary patch literal 890 zcmY*XJCD;q5MDo$%M%14xpR0Z#WgMx3uqAq1vDrPqRAC2$6gZOIbP#+f{ap7q__fv zsKQ@B_a_i76d(#L7c~3ei!ydontQgk(`Jun4( z!mSe)Ce;^gEYi?YaybGXf_(E4+?p(ywv>Sthmj>YE*5t1xU@s^@GM7AZ_lzp zPm5-uJz!g9%F0EZlU zIZH|zR9#bq)OWl|Qnk3}F)L(tQl0ygz_c-2$wEl(SuPitDr0~N!Gv(Qrz?pnnENci ziwAgLfz9@Mz4OP$l+T!9ad#XU*3=Bc$XGCMVYtrN@S||by}N_kcMRc#tnZuFi8*1y zVBW;>n8S@Ryc*5kYT!png;K|JLP}q@{=es-Na9GvQ*f_H+_`b8zOKiu4nQStYwM$T zM?c#AZ|(l~wgI_Xtlb2-6l=G3;Q*7(cpue`xN{w#9`9?X>YK&}3X`E;!b^DOj8U4@ zD4lV8?qb}aFjv#fSFYqkqkh0c4&95O1@aFhxCRhhMTa9w9eyiu8QhA*+Tr`=SGB!S rYo%_s7q<;{Dci20W?SOCaxXjoe$a)5ZJpnWBuRherqumIz@>tJ7evnV literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/__pycache__/client.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/__pycache__/client.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ff7329db24ddd9345f5ff2367007a973b9c4324 GIT binary patch literal 33996 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 F{Xd7WDINd- literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/__pycache__/payload.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/__pycache__/payload.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b353cc7e789065e76e75244eb76295454ab86fd GIT binary patch literal 2114 zcmZuyL2MI86rI^!uN|))r-2gUBx~aul7(W62B;`dR2tM)NCBy+2e)*)c$UP@#%pFb zfLD$R6$c|xQF;lS$gv0xoT{F=wugkYjoC&OQriQ!1Xrz^Q~&IG6F2I&_RpW0KmX7C z|KIob@pu#g{qW@1NnS+gH#P}KXbY{^L6}1X5p19WA{^ljJk1q2j5zcjBEk(sLJv7# zyTI45BEDdg@fR4lf?cty6{mE&=xg}At&UoIVLpd+RKNrkID!j2;R*r?gIQb%kq8OF zC=&4oDuipi(&dKFYBj^uh|_zjGEu7Nxev~ytNv2%MGsgTFY+Cpn9RP`NnmrRj;~@1 zZa#tPNZnAYbJu(8sN8w3y;tWRvR43MRD<6GJg#tV*ng4bibOA(L|1rMuyo@hWzlpK zdW9_#wS6s%*-fX@>+k0$&1pSfG>r?jd~s5 z+S82n+&+Et^xecyv5{3C#fDp;hl)Wm*dzp>7q4$YfUM(L4kE*Lw$K{4gN;OUTi@KVJI7ukzUyG)-De58*^`<- zyx60Bm1xEW?ppVqd(JoHuCtUHU5t(1JhK`G#nlLk^({!5raZhN@0#DYB3N&c2)Ho9<(;kksJL;GiLNtl$w;XVt2)} z-cB1AJ`_3{qIZf8f#w!$utaZS!}kLH1z8V(0XF*jSsVLZ>TZ0#B&FsLJe9J|ST7WI zY+za1y(sOTCqGL2R^;q`D+Fq~b^99hg9c`6-Q=^b1cdTXio=tAu1^W5>jc)asOb8zpUJnB!~kKcP{!D2C4t>Q&xFBd|GkZU0*w M!6#lKVBWER0ad)(`Tzg` literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/__pycache__/server.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/__pycache__/server.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8575a3d5fd4b38758dc3bc6eb1137d1dc1a09713 GIT binary patch literal 24473 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/__pycache__/static_files.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/__pycache__/static_files.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..268a9851609c985b904ea40cdff15ae7693517ce GIT binary patch literal 2166 zcma)6Pi)gx7`NjjP2yxt()3SSTD_)FDYT6#3~f{}QMO52MUe*Eg`rihpWE0Zc4ohp zCUT`7c1Xt#L7ha9kd|qeOk6nZz;W7jJItd}EelPRcH#!=trzw^C!vA$uqWB?d+&SS z@Av)QXMexPVi5+$AHV&bkB1oMAG+v_aN9UQVBBXggIVmu9QNY?4&ojhdKkLN;P5xh zO|~4#@|6&gH;5paW+ki?#Z_5YRaPotZ!=Gdg-WEgtCr-|%79oZ6%HwkYaOhTwpxjJSfzrfXxWg<0h7xChwG>CE~l1OU7u>Yeih6OKejJ1>)roCmrfS56z>?di8uJ#=ek+7bH|-%>Fz#}E|WDwLnJSwf-Z>#w4xMbO)Sbt)G(5CjmR27q+F8Gif(x44EgId z*(5SXCV_uViXa#?41IC^YA)g}=Srd>7Ewtg`8h9$TBxKO^xs7K%3DBQAcbkiwSbo> zs5ytkHc^WNa#~SV*9`ABM_7?4q-d1e%%A5K6RoUi-ZnBkc_u&{9Yl8&$c5a|(QUbG zPNPgma)xU5($BPoFHzx{9QyQ_gb~(d6KOg@MUhB(h%I;LIZY*cV67a`x>#6~5hQm_ z*W{e%ahYp}tni#x*$s8FaHHMi(BL#IZ{%n77nylzTH(?HP?uB%gx;Bk*pn|5rm_NbQZ80wfm}F{e!Rg_v zmfbVrM2G4rJ36+_?nmQQ)_EscTmDvc(xX3&JQ!)*XpTQkPj4?&KdPN{(kJTV!Ei&h z(`Tv+PH$h$vU?|-6l$dG)MSly;{8sdzs}tou3L6u%5yZiABXMKwC8-0U?67suKF^` zgu>7G{+jr(Z zd+PG9eY;a1JL!qWeB(-e4DtwttEcbA>ce(u;%@A}aZYcdcFXP^f7UlzFEr2EW4T|r z-M)97RDVs~S!~>ToUu=xw^Qf8U)+CV>#=H|nzvK)JB!ZXspg5t%XapHJ$S*He*a0* zp1%0xD|`BCovr8o8p=Z6wbMJX#tAz;Rl@Ppu-P3k&F`^qf379Wg?vLZN(0T1UBYD F{R_JpP>lcp literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/async_client.py b/venv/lib/python3.12/site-packages/engineio/async_client.py new file mode 100644 index 0000000..43f3a56 --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/async_client.py @@ -0,0 +1,689 @@ +import asyncio +import signal +import ssl +import threading + +try: + import aiohttp +except ImportError: # pragma: no cover + aiohttp = None + +from . import base_client +from . import exceptions +from . import packet +from . import payload + +async_signal_handler_set = False + +# this set is used to keep references to background tasks to prevent them from +# being garbage collected mid-execution. Solution taken from +# https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task +task_reference_holder = set() + + +def async_signal_handler(): + """SIGINT handler. + + Disconnect all active async clients. + """ + async def _handler(): # pragma: no cover + for c in base_client.connected_clients[:]: + if c.is_asyncio_based(): + await c.disconnect() + + # cancel all running tasks + tasks = [task for task in asyncio.all_tasks() if task is not + asyncio.current_task()] + for task in tasks: + task.cancel() + await asyncio.gather(*tasks, return_exceptions=True) + asyncio.get_running_loop().stop() + + asyncio.ensure_future(_handler()) + + +class AsyncClient(base_client.BaseClient): + """An Engine.IO client for asyncio. + + This class implements a fully compliant Engine.IO web client with support + for websocket and long-polling transports, compatible with the asyncio + framework on Python 3.5 or newer. + + :param logger: To enable logging set to ``True`` or pass a logger object to + use. To disable logging set to ``False``. The default is + ``False``. Note that fatal errors are logged even when + ``logger`` is ``False``. + :param json: An alternative json module to use for encoding and decoding + packets. Custom json modules must have ``dumps`` and ``loads`` + functions that are compatible with the standard library + versions. + :param request_timeout: A timeout in seconds for requests. The default is + 5 seconds. + :param http_session: an initialized ``aiohttp.ClientSession`` object to be + used when sending requests to the server. Use it if + you need to add special client options such as proxy + servers, SSL certificates, custom CA bundle, etc. + :param ssl_verify: ``True`` to verify SSL certificates, or ``False`` to + skip SSL certificate verification, allowing + connections to servers with self signed certificates. + The default is ``True``. + :param handle_sigint: Set to ``True`` to automatically handle disconnection + when the process is interrupted, or to ``False`` to + leave interrupt handling to the calling application. + Interrupt handling can only be enabled when the + client instance is created in the main thread. + :param websocket_extra_options: Dictionary containing additional keyword + arguments passed to + ``aiohttp.ws_connect()``. + :param timestamp_requests: If ``True`` a timestamp is added to the query + string of Socket.IO requests as a cache-busting + measure. Set to ``False`` to disable. + """ + def is_asyncio_based(self): + return True + + async def connect(self, url, headers=None, transports=None, + engineio_path='engine.io'): + """Connect to an Engine.IO server. + + :param url: The URL of the Engine.IO server. It can include custom + query string parameters if required by the server. + :param headers: A dictionary with custom headers to send with the + connection request. + :param transports: The list of allowed transports. Valid transports + are ``'polling'`` and ``'websocket'``. If not + given, the polling transport is connected first, + then an upgrade to websocket is attempted. + :param engineio_path: The endpoint where the Engine.IO server is + installed. The default value is appropriate for + most cases. + + Note: this method is a coroutine. + + Example usage:: + + eio = engineio.Client() + await eio.connect('http://localhost:5000') + """ + global async_signal_handler_set + if self.handle_sigint and not async_signal_handler_set and \ + threading.current_thread() == threading.main_thread(): + try: + asyncio.get_running_loop().add_signal_handler( + signal.SIGINT, async_signal_handler) + except NotImplementedError: # pragma: no cover + self.logger.warning('Signal handler is unsupported') + async_signal_handler_set = True + + if self.state != 'disconnected': + raise ValueError('Client is not in a disconnected state') + valid_transports = ['polling', 'websocket'] + if transports is not None: + if isinstance(transports, str): + transports = [transports] + transports = [transport for transport in transports + if transport in valid_transports] + if not transports: + raise ValueError('No valid transports provided') + self.transports = transports or valid_transports + return await getattr(self, '_connect_' + self.transports[0])( + url, headers or {}, engineio_path) + + async def wait(self): + """Wait until the connection with the server ends. + + Client applications can use this function to block the main thread + during the life of the connection. + + Note: this method is a coroutine. + """ + if self.read_loop_task: + await self.read_loop_task + + async def send(self, data): + """Send a message to the server. + + :param data: The data to send to the server. Data can be of type + ``str``, ``bytes``, ``list`` or ``dict``. If a ``list`` + or ``dict``, the data will be serialized as JSON. + + Note: this method is a coroutine. + """ + await self._send_packet(packet.Packet(packet.MESSAGE, data=data)) + + async def disconnect(self, abort=False, reason=None): + """Disconnect from the server. + + :param abort: If set to ``True``, do not wait for background tasks + associated with the connection to end. + + Note: this method is a coroutine. + """ + if self.state == 'connected': + await self._send_packet(packet.Packet(packet.CLOSE)) + await self.queue.put(None) + self.state = 'disconnecting' + await self._trigger_event('disconnect', + reason or self.reason.CLIENT_DISCONNECT, + run_async=False) + if self.current_transport == 'websocket': + await self.ws.close() + if not abort: + await self.read_loop_task + self.state = 'disconnected' + try: + base_client.connected_clients.remove(self) + except ValueError: # pragma: no cover + pass + await self._reset() + + def start_background_task(self, target, *args, **kwargs): + """Start a background task. + + This is a utility function that applications can use to start a + background task. + + :param target: the target function to execute. + :param args: arguments to pass to the function. + :param kwargs: keyword arguments to pass to the function. + + The return value is a ``asyncio.Task`` object. + """ + return asyncio.ensure_future(target(*args, **kwargs)) + + async def sleep(self, seconds=0): + """Sleep for the requested amount of time. + + Note: this method is a coroutine. + """ + return await asyncio.sleep(seconds) + + def create_queue(self, *args, **kwargs): + """Create a queue object.""" + return asyncio.Queue(*args, **kwargs) + + def get_queue_empty_exception(self): + """Return the queue empty exception raised by queues created by the + ``create_queue()`` method. + """ + return asyncio.QueueEmpty + + def create_event(self): + """Create an event object.""" + return asyncio.Event() + + async def _reset(self): + super()._reset() + while True: # pragma: no cover + try: + self.queue.get_nowait() + self.queue.task_done() + except self.queue_empty: + break + if not self.external_http: # pragma: no cover + if self.http and not self.http.closed: + await self.http.close() + + def __del__(self): # pragma: no cover + # try to close the aiohttp session if it is still open + if self.http and not self.http.closed: + try: + loop = asyncio.get_event_loop() + if loop.is_running(): + loop.ensure_future(self.http.close()) + else: + loop.run_until_complete(self.http.close()) + except: + pass + + async def _connect_polling(self, url, headers, engineio_path): + """Establish a long-polling connection to the Engine.IO server.""" + if aiohttp is None: # pragma: no cover + self.logger.error('aiohttp not installed -- cannot make HTTP ' + 'requests!') + return + self.base_url = self._get_engineio_url(url, engineio_path, 'polling') + self.logger.info('Attempting polling connection to ' + self.base_url) + r = await self._send_request( + 'GET', self.base_url + self._get_url_timestamp(), headers=headers, + timeout=self.request_timeout) + if r is None or isinstance(r, str): + await self._reset() + raise exceptions.ConnectionError( + r or 'Connection refused by the server') + if r.status < 200 or r.status >= 300: + await self._reset() + try: + arg = await r.json() + except aiohttp.ClientError: + arg = None + raise exceptions.ConnectionError( + 'Unexpected status code {} in server response'.format( + r.status), arg) + try: + p = payload.Payload(encoded_payload=(await r.read()).decode( + 'utf-8')) + except ValueError: + raise exceptions.ConnectionError( + 'Unexpected response from server') from None + open_packet = p.packets[0] + if open_packet.packet_type != packet.OPEN: + raise exceptions.ConnectionError( + 'OPEN packet not returned by server') + self.logger.info( + 'Polling connection accepted with ' + str(open_packet.data)) + self.sid = open_packet.data['sid'] + self.upgrades = open_packet.data['upgrades'] + self.ping_interval = int(open_packet.data['pingInterval']) / 1000.0 + self.ping_timeout = int(open_packet.data['pingTimeout']) / 1000.0 + self.current_transport = 'polling' + self.base_url += '&sid=' + self.sid + + self.state = 'connected' + base_client.connected_clients.append(self) + await self._trigger_event('connect', run_async=False) + + for pkt in p.packets[1:]: + await self._receive_packet(pkt) + + if 'websocket' in self.upgrades and 'websocket' in self.transports: + # attempt to upgrade to websocket + if await self._connect_websocket(url, headers, engineio_path): + # upgrade to websocket succeeded, we're done here + return + + self.write_loop_task = self.start_background_task(self._write_loop) + self.read_loop_task = self.start_background_task( + self._read_loop_polling) + + async def _connect_websocket(self, url, headers, engineio_path): + """Establish or upgrade to a WebSocket connection with the server.""" + if aiohttp is None: # pragma: no cover + self.logger.error('aiohttp package not installed') + return False + websocket_url = self._get_engineio_url(url, engineio_path, + 'websocket') + if self.sid: + self.logger.info( + 'Attempting WebSocket upgrade to ' + websocket_url) + upgrade = True + websocket_url += '&sid=' + self.sid + else: + upgrade = False + self.base_url = websocket_url + self.logger.info( + 'Attempting WebSocket connection to ' + websocket_url) + + if self.http is None or self.http.closed: # pragma: no cover + self.http = aiohttp.ClientSession() + + # extract any new cookies passed in a header so that they can also be + # sent the the WebSocket route + cookies = {} + for header, value in headers.items(): + if header.lower() == 'cookie': + cookies = dict( + [cookie.split('=', 1) for cookie in value.split('; ')]) + del headers[header] + break + self.http.cookie_jar.update_cookies(cookies) + + extra_options = {'timeout': self.request_timeout} + if not self.ssl_verify: + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + extra_options['ssl'] = ssl_context + + # combine internally generated options with the ones supplied by the + # caller. The caller's options take precedence. + headers.update(self.websocket_extra_options.pop('headers', {})) + extra_options['headers'] = headers + extra_options.update(self.websocket_extra_options) + + try: + ws = await self.http.ws_connect( + websocket_url + self._get_url_timestamp(), **extra_options) + except (aiohttp.client_exceptions.WSServerHandshakeError, + aiohttp.client_exceptions.ServerConnectionError, + aiohttp.client_exceptions.ClientConnectionError): + if upgrade: + self.logger.warning( + 'WebSocket upgrade failed: connection error') + return False + else: + raise exceptions.ConnectionError('Connection error') + if upgrade: + p = packet.Packet(packet.PING, data='probe').encode() + try: + await ws.send_str(p) + except Exception as e: # pragma: no cover + self.logger.warning( + 'WebSocket upgrade failed: unexpected send exception: %s', + str(e)) + return False + try: + p = (await ws.receive()).data + except Exception as e: # pragma: no cover + self.logger.warning( + 'WebSocket upgrade failed: unexpected recv exception: %s', + str(e)) + return False + pkt = packet.Packet(encoded_packet=p) + if pkt.packet_type != packet.PONG or pkt.data != 'probe': + self.logger.warning( + 'WebSocket upgrade failed: no PONG packet') + return False + p = packet.Packet(packet.UPGRADE).encode() + try: + await ws.send_str(p) + except Exception as e: # pragma: no cover + self.logger.warning( + 'WebSocket upgrade failed: unexpected send exception: %s', + str(e)) + return False + self.current_transport = 'websocket' + self.logger.info('WebSocket upgrade was successful') + else: + try: + p = (await ws.receive()).data + except Exception as e: # pragma: no cover + raise exceptions.ConnectionError( + 'Unexpected recv exception: ' + str(e)) + open_packet = packet.Packet(encoded_packet=p) + if open_packet.packet_type != packet.OPEN: + raise exceptions.ConnectionError('no OPEN packet') + self.logger.info( + 'WebSocket connection accepted with ' + str(open_packet.data)) + self.sid = open_packet.data['sid'] + self.upgrades = open_packet.data['upgrades'] + self.ping_interval = int(open_packet.data['pingInterval']) / 1000.0 + self.ping_timeout = int(open_packet.data['pingTimeout']) / 1000.0 + self.current_transport = 'websocket' + + self.state = 'connected' + base_client.connected_clients.append(self) + await self._trigger_event('connect', run_async=False) + + self.ws = ws + self.write_loop_task = self.start_background_task(self._write_loop) + self.read_loop_task = self.start_background_task( + self._read_loop_websocket) + return True + + async def _receive_packet(self, pkt): + """Handle incoming packets from the server.""" + packet_name = packet.packet_names[pkt.packet_type] \ + if pkt.packet_type < len(packet.packet_names) else 'UNKNOWN' + self.logger.info( + 'Received packet %s data %s', packet_name, + pkt.data if not isinstance(pkt.data, bytes) else '') + if pkt.packet_type == packet.MESSAGE: + await self._trigger_event('message', pkt.data, run_async=True) + elif pkt.packet_type == packet.PING: + await self._send_packet(packet.Packet(packet.PONG, pkt.data)) + elif pkt.packet_type == packet.CLOSE: + await self.disconnect(abort=True, + reason=self.reason.SERVER_DISCONNECT) + elif pkt.packet_type == packet.NOOP: + pass + else: + self.logger.error('Received unexpected packet of type %s', + pkt.packet_type) + + async def _send_packet(self, pkt): + """Queue a packet to be sent to the server.""" + if self.state != 'connected': + return + await self.queue.put(pkt) + self.logger.info( + 'Sending packet %s data %s', + packet.packet_names[pkt.packet_type], + pkt.data if not isinstance(pkt.data, bytes) else '') + + async def _send_request( + self, method, url, headers=None, body=None, + timeout=None): # pragma: no cover + if self.http is None or self.http.closed: + self.http = aiohttp.ClientSession() + http_method = getattr(self.http, method.lower()) + + try: + if not self.ssl_verify: + return await http_method( + url, headers=headers, data=body, + timeout=aiohttp.ClientTimeout(total=timeout), ssl=False) + else: + return await http_method( + url, headers=headers, data=body, + timeout=aiohttp.ClientTimeout(total=timeout)) + + except (aiohttp.ClientError, asyncio.TimeoutError) as exc: + self.logger.info('HTTP %s request to %s failed with error %s.', + method, url, exc) + return str(exc) + + async def _trigger_event(self, event, *args, **kwargs): + """Invoke an event handler.""" + run_async = kwargs.pop('run_async', False) + ret = None + if event in self.handlers: + if asyncio.iscoroutinefunction(self.handlers[event]) is True: + if run_async: + task = self.start_background_task(self.handlers[event], + *args) + task_reference_holder.add(task) + task.add_done_callback(task_reference_holder.discard) + return task + else: + try: + try: + ret = await self.handlers[event](*args) + except TypeError: + if event == 'disconnect' and \ + len(args) == 1: # pragma: no branch + # legacy disconnect events do not have a reason + # argument + return await self.handlers[event]() + else: # pragma: no cover + raise + except asyncio.CancelledError: # pragma: no cover + pass + except: + self.logger.exception(event + ' async handler error') + if event == 'connect': + # if connect handler raised error we reject the + # connection + return False + else: + if run_async: + async def async_handler(): + return self.handlers[event](*args) + + task = self.start_background_task(async_handler) + task_reference_holder.add(task) + task.add_done_callback(task_reference_holder.discard) + return task + else: + try: + try: + ret = self.handlers[event](*args) + except TypeError: + if event == 'disconnect' and \ + len(args) == 1: # pragma: no branch + # legacy disconnect events do not have a reason + # argument + ret = self.handlers[event]() + else: # pragma: no cover + raise + except: + self.logger.exception(event + ' handler error') + if event == 'connect': + # if connect handler raised error we reject the + # connection + return False + return ret + + async def _read_loop_polling(self): + """Read packets by polling the Engine.IO server.""" + while self.state == 'connected' and self.write_loop_task: + self.logger.info( + 'Sending polling GET request to ' + self.base_url) + r = await self._send_request( + 'GET', self.base_url + self._get_url_timestamp(), + timeout=max(self.ping_interval, self.ping_timeout) + 5) + if r is None or isinstance(r, str): + self.logger.warning( + r or 'Connection refused by the server, aborting') + await self.queue.put(None) + break + if r.status < 200 or r.status >= 300: + self.logger.warning('Unexpected status code %s in server ' + 'response, aborting', r.status) + await self.queue.put(None) + break + try: + p = payload.Payload(encoded_payload=(await r.read()).decode( + 'utf-8')) + except ValueError: + self.logger.warning( + 'Unexpected packet from server, aborting') + await self.queue.put(None) + break + for pkt in p.packets: + await self._receive_packet(pkt) + + if self.write_loop_task: # pragma: no branch + self.logger.info('Waiting for write loop task to end') + await self.write_loop_task + if self.state == 'connected': + await self._trigger_event( + 'disconnect', self.reason.TRANSPORT_ERROR, run_async=False) + try: + base_client.connected_clients.remove(self) + except ValueError: # pragma: no cover + pass + await self._reset() + self.logger.info('Exiting read loop task') + + async def _read_loop_websocket(self): + """Read packets from the Engine.IO WebSocket connection.""" + while self.state == 'connected': + p = None + try: + p = await asyncio.wait_for( + self.ws.receive(), + timeout=self.ping_interval + self.ping_timeout) + if not isinstance(p.data, (str, bytes)): # pragma: no cover + self.logger.warning( + 'Server sent %s packet data %s, aborting', + 'close' if p.type in [aiohttp.WSMsgType.CLOSE, + aiohttp.WSMsgType.CLOSING] + else str(p.type), str(p.data)) + await self.queue.put(None) + break # the connection is broken + p = p.data + except asyncio.TimeoutError: + self.logger.warning( + 'Server has stopped communicating, aborting') + await self.queue.put(None) + break + except aiohttp.client_exceptions.ServerDisconnectedError: + self.logger.info( + 'Read loop: WebSocket connection was closed, aborting') + await self.queue.put(None) + break + except Exception as e: + self.logger.info( + 'Unexpected error receiving packet: "%s", aborting', + str(e)) + await self.queue.put(None) + break + try: + pkt = packet.Packet(encoded_packet=p) + except Exception as e: # pragma: no cover + self.logger.info( + 'Unexpected error decoding packet: "%s", aborting', str(e)) + await self.queue.put(None) + break + await self._receive_packet(pkt) + + if self.write_loop_task: # pragma: no branch + self.logger.info('Waiting for write loop task to end') + await self.write_loop_task + if self.state == 'connected': + await self._trigger_event( + 'disconnect', self.reason.TRANSPORT_ERROR, run_async=False) + try: + base_client.connected_clients.remove(self) + except ValueError: # pragma: no cover + pass + await self._reset() + self.logger.info('Exiting read loop task') + + async def _write_loop(self): + """This background task sends packages to the server as they are + pushed to the send queue. + """ + while self.state == 'connected': + # to simplify the timeout handling, use the maximum of the + # ping interval and ping timeout as timeout, with an extra 5 + # seconds grace period + timeout = max(self.ping_interval, self.ping_timeout) + 5 + packets = None + try: + packets = [await asyncio.wait_for(self.queue.get(), timeout)] + except (self.queue_empty, asyncio.TimeoutError): + self.logger.error('packet queue is empty, aborting') + break + except asyncio.CancelledError: # pragma: no cover + break + if packets == [None]: + self.queue.task_done() + packets = [] + else: + while True: + try: + packets.append(self.queue.get_nowait()) + except self.queue_empty: + break + if packets[-1] is None: + packets = packets[:-1] + self.queue.task_done() + break + if not packets: + # empty packet list returned -> connection closed + break + if self.current_transport == 'polling': + p = payload.Payload(packets=packets) + r = await self._send_request( + 'POST', self.base_url, body=p.encode(), + headers={'Content-Type': 'text/plain'}, + timeout=self.request_timeout) + for pkt in packets: + self.queue.task_done() + if r is None or isinstance(r, str): + self.logger.warning( + r or 'Connection refused by the server, aborting') + break + if r.status < 200 or r.status >= 300: + self.logger.warning('Unexpected status code %s in server ' + 'response, aborting', r.status) + self.write_loop_task = None + break + else: + # websocket + try: + for pkt in packets: + if pkt.binary: + await self.ws.send_bytes(pkt.encode()) + else: + await self.ws.send_str(pkt.encode()) + self.queue.task_done() + except (aiohttp.client_exceptions.ServerDisconnectedError, + BrokenPipeError, OSError): + self.logger.info( + 'Write loop: WebSocket connection was closed, ' + 'aborting') + break + self.logger.info('Exiting write loop task') diff --git a/venv/lib/python3.12/site-packages/engineio/async_drivers/__init__.py b/venv/lib/python3.12/site-packages/engineio/async_drivers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..709393cf1d3863fc560741131ad7d74dfd792576 GIT binary patch literal 184 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 F{1+Z5O~e2I literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/asgi.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/asgi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d098b21c72065e73996bb4e182d86eb651d4fca2 GIT binary patch literal 14490 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 yh49&7=6f_<`fN(&q?^b63 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/eventlet.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/eventlet.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c6428ca038ea9d8ada1a5f3b7bf03bc0025cee2 GIT binary patch literal 2907 zcmaJ@O>7)R7Ov{Q`SFbH@n3$3v(3gEJb}z;ccpBi1j8asln7Z1jud%vX!UfLXWHrM z9;$odjPW76YiSb%Bw<13u!n$Mr zCH1S26nV*&YH~@g zDJ7+*mQ*2=U9FZVB`J}}abhbg#8z)gv6WIv$TW~8Sn{UamL^z=$qOUxFZE0y9qV~* zvy8F?Q|#nFv{I&-%BR0ad7eh<^f~5*c{x&FYp@23)WBt|4pZG+^gy3I&C0XBb(V#v zXOEw-dJVVFM>Zxvx=I)+QJa(`o0eo-v*i_9Qt&1v)mDLOO(~y<`o-s57M`kdX4*^N z#t&`}@Rg9+y6M@v%gnRPent-*&thPNHFc9S z-Gh^Hy<+mR3HB}DbzwiV3&PrrrSt%V_@K8oUKaQ)(`90G)yvX(8c5)Kl+uoPS&pR+ z0ci(ch#7cm`<;*`$xAagNM4Dw(Bu^sMhZ+pq@7(9Gp&G1379+2Ghmlzk@N!z@NkIV z!_&p8Ut>i$eYsh*0Q9g~1i;S~U8h{EH^Zv${l4(q-xUKVWC!b}br$X#6q#3XJm&aC zGiZ92VRHwd0CC$@g?cke8iwOJpu{Z?-3+NqD{KA8J=ZtC-ie?9%rr`K{*2ml|3g(efY8cjya$|m; zaU*b+Sd==|tg~s(eIBVs01YFrh@^-TRj8&e}d_|_+;Pp`8$OZYlRcH51fPu%Z_gnnthqB zr^(oE$f5Lu%>)_T^#hSo`~TnG_WtSrWPZ~c-G%D)1SlQ`n8|;gD3Z^1{xK(iF-U=K z@hg@C;a3g}`V~qyJx6)pqY`9G9jyNXI-Bl-mA{m6y+uCk79tTNP(`3&NFCo}sLd?( zcWbTV)f?f`!kpiLjszVG`WG}fY*UX~XN-42pJlP)MOJtHW(^vv9zfG_d{6i1#TH1f z2Fgt#ocFn2^P$%{9Ejom$(=Y3Mw{(JbWq;hy~0Kro*z({&A_>wah} z4)?vNU(`N>v!L#KaI`!u-xdG|LG<$gi0xs$3{C=p0BH3+aqSOleFyFi?Oi95K20}> zH2e|;J_}SMW8=5R53Y?C?u;H;8$EJ+^e8lx^xc7xON(m*zq*#VzW4UP;akbWaUIe) z#und$WE_d8GI*3I+m2L=i5t=Q%$S$Y#}SLUFA4zGqX0KPSRtEILf!r2kft74CqUwI zgDWAKwD?DcV=P5|;*ZNkvwaChtJpd}^`Aj|=?d&>T{0kLg6OLEm7>!Y~ zqZUTRF43r>p^l>P$R-{m>NmFJn5dbN`bzx!H!V)18KTE;E3prg$Knz_#gBm@1VXrE zU8a=YBL}`ClWS!19+~=4%h4zA6Cj(DgY?LinN0%I`Xm_`Z)JxrpSg6VHM|p>0=%_y Pdx751CeqTM<8Jp8c&VxB literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/gevent.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/gevent.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ccb4d43c62522d09619c3c512c259ea6f8bb2671 GIT binary patch literal 4205 zcmd5<-)|Jh9iO?q-TUD?E^rMt#x;wJUbA<5 zncc&jb&*I!RjejOq-qUPC8MejLA0v+C#0|KgReaJv>|=TL)ABfp+Tq*{eEX}_ppIh zd8pKR_|A7`zBBWk?|eVs`Ocry=_G;jmp}b&{*x|3zQjgQVr^#gU%;#ogBa8zMXVzh zDbpgQ$V*nFEEOe6623&V7&T~7j*zp&kgpO$VPxTzV98E>SMvE{g2^AGgmes~<4g`m zl9iGq$pn_muXR%Cb_7m zoS)}RHRVe4@O-EhidbP^9TC#MOTk8Z*!DTP*m20AR>{pJ9W z71AIJ&yGSLmb-pMrojP$1%Hxwt#XI*6to42@gS%S_X}q^6Z^qh=wW(zN$#y449AIIsuEldI%GM3UnhaWZ)LQ~sDx`Pe@dD)$OigzOeTw2|x^ zW;-C-P>2_!0t7OEK=RUC(6#hO;gIMC{|OIzfQWGN^NOeykun<@R6af?jyvqPB zfR0@mR%?z~ne%ZYhDH|Ij3b~QuGV%pA2_IIDF^~!E`k;mj`1sC5IhEi1B<*biJHDI zxgpd9n845tfRY=>$ygfFf{5CFE1(PLGTRDBqK9lNFN(*%i`+$QK|z@2q$!Tq1h!AP zA{6)t^tyy$+KnB6Sypmw?zeLZFVzN;kn&z>w*oPTquRc+p%FY;$Z4#i;{aO)}l2ELDmp~3f40p#7`-D6k|@vOpz&S@Zk=gl}t ztDmP1+)wRXKHE(7-Mnyn_Wse=TDig>`tJ0t=f;}3vDWah_2HAv;gjpb=bFRkHew|G z&BsJaPf;Ij3h#!+I6gld5y3H5_~+bZ42s?X+6H&%>mCF|d?P|01lwgn3Aj!TYv8`3yku4wE&@5R za~N7)GV29_1@>x(23+BV8ons>5G~XjW@S2B1YCmzw!t7jwX|c=nKScYG+p}eJmeMpbDd3Bm$;=|N&R<-Op#&4Kf!Vs?7vtk6E zzFHX9)mfXXWt%g2B!Df8V4u&$z0?nD71u1YGo0JplQj(WUJMcqliL+9hUo*W=|v&4 zn)ei&n~-35aZtszAvfV8aN58L(WAT_kZo~Se{KJ0bI-AB;~!17_6|UGuC;gH z>WNz?*7uGy_l}5;p1#%Ot>kLvRtB0At=|6CqqmN(_YO6ChoIx_R-d{$d24dLuh8r( zK*#r6+3uC&H;&&N|HT_?sr^16LT3iGLTG*m8Y1a|dV5(2UAHr`JrRoV*I>|-OO_2c zE^-~*r;Xe10=tym8r+5C_h7(5X|NBddg8{3_3VLW_Q1XD;96?X*Y{b7g#bPdbH0wg z4cc*DKXq$s42f*T$VJ*OR+%{tCZ4!w5Ol_XU)r@*d7N|x`nT%7a_YvZ+ppit4y~nz z{`ae_ttY7uV)9F*};3+!)vL-LgT4i;^mLy>oA|=U2?Pu zKLz9t5!3we9KMr7Fyg2{Fx7--@?nxD_B~WmiGhcGG;!#mLKDNAK#qvXIR!ovB5UDz zzh0O4v}vqdb?bZ(dlF%kfxn2wST&d7M`1w7^8?6Xp!70+_z9&s212iEqFlAP>qmq` z!IvVxMX1w5}syC0FCTe8fNYFYl6ke2-nE`qn`yO`?O7SSF$V3{&i+<9 zd;Q(zcORr<$;b!FlZ}Z8iDw^@2yFQG=;Vfs%u@%yET1T+N62R*U8l#S&rVRF{{}Si Bz108! literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/gevent_uwsgi.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/gevent_uwsgi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..425263d2e1a92483e09697444a661cf2c3369af6 GIT binary patch literal 7459 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 Uf}!j0EWh(8SmAfQ=i%7=57639DgXcg literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/sanic.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/sanic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c92b6fdb0f0516b8a7b2361107f14d1b6d2ba20 GIT binary patch literal 6655 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/tornado.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/async_drivers/__pycache__/tornado.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1383cf62babd864292a737388d1c437ba6468fbc GIT binary patch literal 9920 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 2.1.x with support for api access across-greenlets + self._req_ctx = uwsgi.request_context() + else: + # use event and queue for sending messages + self._event = Event() + self._send_queue = queue.Queue() + + # spawn a select greenlet + def select_greenlet_runner(fd, event): + """Sets event when data becomes available to read on fd.""" + sel = selectors.DefaultSelector() + sel.register(fd, selectors.EVENT_READ) + try: + while True: + sel.select() + event.set() + except gevent.GreenletExit: + sel.unregister(fd) + self._select_greenlet = gevent.spawn( + select_greenlet_runner, + self._sock, + self._event) + + self.app(self) + uwsgi.disconnect() + return '' # send nothing as response + + def close(self): + """Disconnects uWSGI from the client.""" + if self._req_ctx is None: + # better kill it here in case wait() is not called again + self._select_greenlet.kill() + self._event.set() + + def _send(self, msg): + """Transmits message either in binary or UTF-8 text mode, + depending on its type.""" + if isinstance(msg, bytes): + method = uwsgi.websocket_send_binary + else: + method = uwsgi.websocket_send + if self._req_ctx is not None: + method(msg, request_context=self._req_ctx) + else: + method(msg) + + def _decode_received(self, msg): + """Returns either bytes or str, depending on message type.""" + if not isinstance(msg, bytes): + # already decoded - do nothing + return msg + # only decode from utf-8 if message is not binary data + type = ord(msg[0:1]) + if type >= 48: # no binary + return msg.decode('utf-8') + # binary message, don't try to decode + return msg + + def send(self, msg): + """Queues a message for sending. Real transmission is done in + wait method. + Sends directly if uWSGI version is new enough.""" + if self._req_ctx is not None: + self._send(msg) + else: + self._send_queue.put(msg) + self._event.set() + + def wait(self): + """Waits and returns received messages. + If running in compatibility mode for older uWSGI versions, + it also sends messages that have been queued by send(). + A return value of None means that connection was closed. + This must be called repeatedly. For uWSGI < 2.1.x it must + be called from the main greenlet.""" + while True: + if self._req_ctx is not None: + try: + msg = uwsgi.websocket_recv(request_context=self._req_ctx) + except OSError: # connection closed + self.close() + return None + return self._decode_received(msg) + else: + if self.received_messages: + return self.received_messages.pop(0) + + # we wake up at least every 3 seconds to let uWSGI + # do its ping/ponging + event_set = self._event.wait(timeout=3) + if event_set: + self._event.clear() + # maybe there is something to send + msgs = [] + while True: + try: + msgs.append(self._send_queue.get(block=False)) + except gevent.queue.Empty: + break + for msg in msgs: + try: + self._send(msg) + except OSError: + self.close() + return None + # maybe there is something to receive, if not, at least + # ensure uWSGI does its ping/ponging + while True: + try: + msg = uwsgi.websocket_recv_nb() + except OSError: # connection closed + self.close() + return None + if msg: # message available + self.received_messages.append( + self._decode_received(msg)) + else: + break + if self.received_messages: + return self.received_messages.pop(0) + + +_async = { + 'thread': Thread, + 'queue': queue.JoinableQueue, + 'queue_empty': queue.Empty, + 'event': Event, + 'websocket': uWSGIWebSocket if _websocket_available else None, + 'sleep': gevent.sleep, +} diff --git a/venv/lib/python3.12/site-packages/engineio/async_drivers/sanic.py b/venv/lib/python3.12/site-packages/engineio/async_drivers/sanic.py new file mode 100644 index 0000000..4d6a5b8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/async_drivers/sanic.py @@ -0,0 +1,148 @@ +import sys +from urllib.parse import urlsplit + +try: # pragma: no cover + from sanic.response import HTTPResponse + try: + from sanic.server.protocols.websocket_protocol import WebSocketProtocol + except ImportError: + from sanic.websocket import WebSocketProtocol +except ImportError: + HTTPResponse = None + WebSocketProtocol = None + + +def create_route(app, engineio_server, engineio_endpoint): # pragma: no cover + """This function sets up the engine.io endpoint as a route for the + application. + + Note that both GET and POST requests must be hooked up on the engine.io + endpoint. + """ + app.add_route(engineio_server.handle_request, engineio_endpoint, + methods=['GET', 'POST', 'OPTIONS']) + try: + app.enable_websocket() + except AttributeError: + # ignore, this version does not support websocket + pass + + +def translate_request(request): # pragma: no cover + """This function takes the arguments passed to the request handler and + uses them to generate a WSGI compatible environ dictionary. + """ + class AwaitablePayload: + def __init__(self, payload): + self.payload = payload or b'' + + async def read(self, length=None): + if length is None: + r = self.payload + self.payload = b'' + else: + r = self.payload[:length] + self.payload = self.payload[length:] + return r + + uri_parts = urlsplit(request.url) + environ = { + 'wsgi.input': AwaitablePayload(request.body), + 'wsgi.errors': sys.stderr, + 'wsgi.version': (1, 0), + 'wsgi.async': True, + 'wsgi.multithread': False, + 'wsgi.multiprocess': False, + 'wsgi.run_once': False, + 'SERVER_SOFTWARE': 'sanic', + 'REQUEST_METHOD': request.method, + 'QUERY_STRING': uri_parts.query or '', + 'RAW_URI': request.url, + 'SERVER_PROTOCOL': 'HTTP/' + request.version, + 'REMOTE_ADDR': '127.0.0.1', + 'REMOTE_PORT': '0', + 'SERVER_NAME': 'sanic', + 'SERVER_PORT': '0', + 'sanic.request': request + } + + for hdr_name, hdr_value in request.headers.items(): + hdr_name = hdr_name.upper() + if hdr_name == 'CONTENT-TYPE': + environ['CONTENT_TYPE'] = hdr_value + continue + elif hdr_name == 'CONTENT-LENGTH': + environ['CONTENT_LENGTH'] = hdr_value + continue + + key = 'HTTP_%s' % hdr_name.replace('-', '_') + if key in environ: + hdr_value = f'{environ[key]},{hdr_value}' + + environ[key] = hdr_value + + environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') + + path_info = uri_parts.path + + environ['PATH_INFO'] = path_info + environ['SCRIPT_NAME'] = '' + + return environ + + +def make_response(status, headers, payload, environ): # pragma: no cover + """This function generates an appropriate response object for this async + mode. + """ + headers_dict = {} + content_type = None + for h in headers: + if h[0].lower() == 'content-type': + content_type = h[1] + else: + headers_dict[h[0]] = h[1] + return HTTPResponse(body=payload, content_type=content_type, + status=int(status.split()[0]), headers=headers_dict) + + +class WebSocket: # pragma: no cover + """ + This wrapper class provides a sanic WebSocket interface that is + somewhat compatible with eventlet's implementation. + """ + def __init__(self, handler, server): + self.handler = handler + self.server = server + self._sock = None + + async def __call__(self, environ): + request = environ['sanic.request'] + protocol = request.transport.get_protocol() + self._sock = await protocol.websocket_handshake(request) + + self.environ = environ + await self.handler(self) + return self.server._ok() + + async def close(self): + await self._sock.close() + + async def send(self, message): + await self._sock.send(message) + + async def wait(self): + data = await self._sock.recv() + if not isinstance(data, bytes) and \ + not isinstance(data, str): + raise OSError() + return data + + +_async = { + 'asyncio': True, + 'create_route': create_route, + 'translate_request': translate_request, + 'make_response': make_response, + 'websocket': WebSocket if WebSocketProtocol else None, +} diff --git a/venv/lib/python3.12/site-packages/engineio/async_drivers/threading.py b/venv/lib/python3.12/site-packages/engineio/async_drivers/threading.py new file mode 100644 index 0000000..1615579 --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/async_drivers/threading.py @@ -0,0 +1,19 @@ +import queue +import threading +import time +from engineio.async_drivers._websocket_wsgi import SimpleWebSocketWSGI + + +class DaemonThread(threading.Thread): # pragma: no cover + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs, daemon=True) + + +_async = { + 'thread': DaemonThread, + 'queue': queue.Queue, + 'queue_empty': queue.Empty, + 'event': threading.Event, + 'websocket': SimpleWebSocketWSGI, + 'sleep': time.sleep, +} diff --git a/venv/lib/python3.12/site-packages/engineio/async_drivers/tornado.py b/venv/lib/python3.12/site-packages/engineio/async_drivers/tornado.py new file mode 100644 index 0000000..abb1e2b --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/async_drivers/tornado.py @@ -0,0 +1,182 @@ +import asyncio +import sys +from urllib.parse import urlsplit +from .. import exceptions + +import tornado.web +import tornado.websocket + + +def get_tornado_handler(engineio_server): + class Handler(tornado.websocket.WebSocketHandler): # pragma: no cover + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if isinstance(engineio_server.cors_allowed_origins, str): + if engineio_server.cors_allowed_origins == '*': + self.allowed_origins = None + else: + self.allowed_origins = [ + engineio_server.cors_allowed_origins] + else: + self.allowed_origins = engineio_server.cors_allowed_origins + self.receive_queue = asyncio.Queue() + + async def get(self, *args, **kwargs): + if self.request.headers.get('Upgrade', '').lower() == 'websocket': + ret = super().get(*args, **kwargs) + if asyncio.iscoroutine(ret): + await ret + else: + await engineio_server.handle_request(self) + + async def open(self, *args, **kwargs): + # this is the handler for the websocket request + asyncio.ensure_future(engineio_server.handle_request(self)) + + async def post(self, *args, **kwargs): + await engineio_server.handle_request(self) + + async def options(self, *args, **kwargs): + await engineio_server.handle_request(self) + + async def on_message(self, message): + await self.receive_queue.put(message) + + async def get_next_message(self): + return await self.receive_queue.get() + + def on_close(self): + self.receive_queue.put_nowait(None) + + def check_origin(self, origin): + if self.allowed_origins is None or origin in self.allowed_origins: + return True + return super().check_origin(origin) + + def get_compression_options(self): + # enable compression + return {} + + return Handler + + +def translate_request(handler): + """This function takes the arguments passed to the request handler and + uses them to generate a WSGI compatible environ dictionary. + """ + class AwaitablePayload: + def __init__(self, payload): + self.payload = payload or b'' + + async def read(self, length=None): + if length is None: + r = self.payload + self.payload = b'' + else: + r = self.payload[:length] + self.payload = self.payload[length:] + return r + + payload = handler.request.body + + uri_parts = urlsplit(handler.request.path) + full_uri = handler.request.path + if handler.request.query: # pragma: no cover + full_uri += '?' + handler.request.query + environ = { + 'wsgi.input': AwaitablePayload(payload), + 'wsgi.errors': sys.stderr, + 'wsgi.version': (1, 0), + 'wsgi.async': True, + 'wsgi.multithread': False, + 'wsgi.multiprocess': False, + 'wsgi.run_once': False, + 'SERVER_SOFTWARE': 'aiohttp', + 'REQUEST_METHOD': handler.request.method, + 'QUERY_STRING': handler.request.query or '', + 'RAW_URI': full_uri, + 'SERVER_PROTOCOL': 'HTTP/%s' % handler.request.version, + 'REMOTE_ADDR': '127.0.0.1', + 'REMOTE_PORT': '0', + 'SERVER_NAME': 'aiohttp', + 'SERVER_PORT': '0', + 'tornado.handler': handler + } + + for hdr_name, hdr_value in handler.request.headers.items(): + hdr_name = hdr_name.upper() + if hdr_name == 'CONTENT-TYPE': + environ['CONTENT_TYPE'] = hdr_value + continue + elif hdr_name == 'CONTENT-LENGTH': + environ['CONTENT_LENGTH'] = hdr_value + continue + + key = 'HTTP_%s' % hdr_name.replace('-', '_') + environ[key] = hdr_value + + environ['wsgi.url_scheme'] = environ.get('HTTP_X_FORWARDED_PROTO', 'http') + + path_info = uri_parts.path + + environ['PATH_INFO'] = path_info + environ['SCRIPT_NAME'] = '' + + return environ + + +def make_response(status, headers, payload, environ): + """This function generates an appropriate response object for this async + mode. + """ + tornado_handler = environ['tornado.handler'] + try: + tornado_handler.set_status(int(status.split()[0])) + except RuntimeError: # pragma: no cover + # for websocket connections Tornado does not accept a response, since + # it already emitted the 101 status code + return + for header, value in headers: + tornado_handler.set_header(header, value) + tornado_handler.write(payload) + tornado_handler.finish() + + +class WebSocket: # pragma: no cover + """ + This wrapper class provides a tornado WebSocket interface that is + somewhat compatible with eventlet's implementation. + """ + def __init__(self, handler, server): + self.handler = handler + self.tornado_handler = None + + async def __call__(self, environ): + self.tornado_handler = environ['tornado.handler'] + self.environ = environ + await self.handler(self) + + async def close(self): + self.tornado_handler.close() + + async def send(self, message): + try: + self.tornado_handler.write_message( + message, binary=isinstance(message, bytes)) + except tornado.websocket.WebSocketClosedError: + raise exceptions.EngineIOError() + + async def wait(self): + msg = await self.tornado_handler.get_next_message() + if not isinstance(msg, bytes) and \ + not isinstance(msg, str): + raise OSError() + return msg + + +_async = { + 'asyncio': True, + 'translate_request': translate_request, + 'make_response': make_response, + 'websocket': WebSocket, +} diff --git a/venv/lib/python3.12/site-packages/engineio/async_server.py b/venv/lib/python3.12/site-packages/engineio/async_server.py new file mode 100644 index 0000000..c417067 --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/async_server.py @@ -0,0 +1,611 @@ +import asyncio +import urllib + +from . import base_server +from . import exceptions +from . import packet +from . import async_socket + +# this set is used to keep references to background tasks to prevent them from +# being garbage collected mid-execution. Solution taken from +# https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task +task_reference_holder = set() + + +class AsyncServer(base_server.BaseServer): + """An Engine.IO server for asyncio. + + This class implements a fully compliant Engine.IO web server with support + for websocket and long-polling transports, compatible with the asyncio + framework on Python 3.5 or newer. + + :param async_mode: The asynchronous model to use. See the Deployment + section in the documentation for a description of the + available options. Valid async modes are "aiohttp", + "sanic", "tornado" and "asgi". If this argument is not + given, "aiohttp" is tried first, followed by "sanic", + "tornado", and finally "asgi". The first async mode that + has all its dependencies installed is the one that is + chosen. + :param ping_interval: The interval in seconds at which the server pings + the client. The default is 25 seconds. For advanced + control, a two element tuple can be given, where + the first number is the ping interval and the second + is a grace period added by the server. + :param ping_timeout: The time in seconds that the client waits for the + server to respond before disconnecting. The default + is 20 seconds. + :param max_http_buffer_size: The maximum size that is accepted for incoming + messages. The default is 1,000,000 bytes. In + spite of its name, the value set in this + argument is enforced for HTTP long-polling and + WebSocket connections. + :param allow_upgrades: Whether to allow transport upgrades or not. + :param http_compression: Whether to compress packages when using the + polling transport. + :param compression_threshold: Only compress messages when their byte size + is greater than this value. + :param cookie: If set to a string, it is the name of the HTTP cookie the + server sends back tot he client containing the client + session id. If set to a dictionary, the ``'name'`` key + contains the cookie name and other keys define cookie + attributes, where the value of each attribute can be a + string, a callable with no arguments, or a boolean. If set + to ``None`` (the default), a cookie is not sent to the + client. + :param cors_allowed_origins: Origin or list of origins that are allowed to + connect to this server. Only the same origin + is allowed by default. Set this argument to + ``'*'`` or ``['*']`` to allow all origins, or + to ``[]`` to disable CORS handling. + :param cors_credentials: Whether credentials (cookies, authentication) are + allowed in requests to this server. + :param logger: To enable logging set to ``True`` or pass a logger object to + use. To disable logging set to ``False``. Note that fatal + errors are logged even when ``logger`` is ``False``. + :param json: An alternative json module to use for encoding and decoding + packets. Custom json modules must have ``dumps`` and ``loads`` + functions that are compatible with the standard library + versions. + :param async_handlers: If set to ``True``, run message event handlers in + non-blocking threads. To run handlers synchronously, + set to ``False``. The default is ``True``. + :param monitor_clients: If set to ``True``, a background task will ensure + inactive clients are closed. Set to ``False`` to + disable the monitoring task (not recommended). The + default is ``True``. + :param transports: The list of allowed transports. Valid transports + are ``'polling'`` and ``'websocket'``. Defaults to + ``['polling', 'websocket']``. + :param kwargs: Reserved for future extensions, any additional parameters + given as keyword arguments will be silently ignored. + """ + def is_asyncio_based(self): + return True + + def async_modes(self): + return ['aiohttp', 'sanic', 'tornado', 'asgi'] + + def attach(self, app, engineio_path='engine.io'): + """Attach the Engine.IO server to an application.""" + engineio_path = engineio_path.strip('/') + self._async['create_route'](app, self, f'/{engineio_path}/') + + async def send(self, sid, data): + """Send a message to a client. + + :param sid: The session id of the recipient client. + :param data: The data to send to the client. Data can be of type + ``str``, ``bytes``, ``list`` or ``dict``. If a ``list`` + or ``dict``, the data will be serialized as JSON. + + Note: this method is a coroutine. + """ + await self.send_packet(sid, packet.Packet(packet.MESSAGE, data=data)) + + async def send_packet(self, sid, pkt): + """Send a raw packet to a client. + + :param sid: The session id of the recipient client. + :param pkt: The packet to send to the client. + + Note: this method is a coroutine. + """ + try: + socket = self._get_socket(sid) + except KeyError: + # the socket is not available + self.logger.warning('Cannot send to sid %s', sid) + return + await socket.send(pkt) + + async def get_session(self, sid): + """Return the user session for a client. + + :param sid: The session id of the client. + + The return value is a dictionary. Modifications made to this + dictionary are not guaranteed to be preserved. If you want to modify + the user session, use the ``session`` context manager instead. + """ + socket = self._get_socket(sid) + return socket.session + + async def save_session(self, sid, session): + """Store the user session for a client. + + :param sid: The session id of the client. + :param session: The session dictionary. + """ + socket = self._get_socket(sid) + socket.session = session + + def session(self, sid): + """Return the user session for a client with context manager syntax. + + :param sid: The session id of the client. + + This is a context manager that returns the user session dictionary for + the client. Any changes that are made to this dictionary inside the + context manager block are saved back to the session. Example usage:: + + @eio.on('connect') + def on_connect(sid, environ): + username = authenticate_user(environ) + if not username: + return False + with eio.session(sid) as session: + session['username'] = username + + @eio.on('message') + def on_message(sid, msg): + async with eio.session(sid) as session: + print('received message from ', session['username']) + """ + class _session_context_manager: + def __init__(self, server, sid): + self.server = server + self.sid = sid + self.session = None + + async def __aenter__(self): + self.session = await self.server.get_session(sid) + return self.session + + async def __aexit__(self, *args): + await self.server.save_session(sid, self.session) + + return _session_context_manager(self, sid) + + async def disconnect(self, sid=None): + """Disconnect a client. + + :param sid: The session id of the client to close. If this parameter + is not given, then all clients are closed. + + Note: this method is a coroutine. + """ + if sid is not None: + try: + socket = self._get_socket(sid) + except KeyError: # pragma: no cover + # the socket was already closed or gone + pass + else: + await socket.close(reason=self.reason.SERVER_DISCONNECT) + if sid in self.sockets: # pragma: no cover + del self.sockets[sid] + else: + await asyncio.wait([ + asyncio.create_task(client.close( + reason=self.reason.SERVER_DISCONNECT)) + for client in self.sockets.values() + ]) + self.sockets = {} + + async def handle_request(self, *args, **kwargs): + """Handle an HTTP request from the client. + + This is the entry point of the Engine.IO application. This function + returns the HTTP response to deliver to the client. + + Note: this method is a coroutine. + """ + translate_request = self._async['translate_request'] + if asyncio.iscoroutinefunction(translate_request): + environ = await translate_request(*args, **kwargs) + else: + environ = translate_request(*args, **kwargs) + + if self.cors_allowed_origins != []: + # Validate the origin header if present + # This is important for WebSocket more than for HTTP, since + # browsers only apply CORS controls to HTTP. + origin = environ.get('HTTP_ORIGIN') + if origin: + allowed_origins = self._cors_allowed_origins(environ) + if allowed_origins is not None and origin not in \ + allowed_origins: + self._log_error_once( + origin + ' is not an accepted origin.', 'bad-origin') + return await self._make_response( + self._bad_request( + origin + ' is not an accepted origin.'), + environ) + + method = environ['REQUEST_METHOD'] + query = urllib.parse.parse_qs(environ.get('QUERY_STRING', '')) + + sid = query['sid'][0] if 'sid' in query else None + jsonp = False + jsonp_index = None + + # make sure the client uses an allowed transport + transport = query.get('transport', ['polling'])[0] + if transport not in self.transports: + self._log_error_once('Invalid transport', 'bad-transport') + return await self._make_response( + self._bad_request('Invalid transport'), environ) + + # make sure the client speaks a compatible Engine.IO version + sid = query['sid'][0] if 'sid' in query else None + if sid is None and query.get('EIO') != ['4']: + self._log_error_once( + 'The client is using an unsupported version of the Socket.IO ' + 'or Engine.IO protocols', 'bad-version' + ) + return await self._make_response(self._bad_request( + 'The client is using an unsupported version of the Socket.IO ' + 'or Engine.IO protocols' + ), environ) + + if 'j' in query: + jsonp = True + try: + jsonp_index = int(query['j'][0]) + except (ValueError, KeyError, IndexError): + # Invalid JSONP index number + pass + + if jsonp and jsonp_index is None: + self._log_error_once('Invalid JSONP index number', + 'bad-jsonp-index') + r = self._bad_request('Invalid JSONP index number') + elif method == 'GET': + upgrade_header = environ.get('HTTP_UPGRADE').lower() \ + if 'HTTP_UPGRADE' in environ else None + if sid is None: + # transport must be one of 'polling' or 'websocket'. + # if 'websocket', the HTTP_UPGRADE header must match. + if transport == 'polling' \ + or transport == upgrade_header == 'websocket': + r = await self._handle_connect(environ, transport, + jsonp_index) + else: + self._log_error_once('Invalid websocket upgrade', + 'bad-upgrade') + r = self._bad_request('Invalid websocket upgrade') + else: + if sid not in self.sockets: + self._log_error_once(f'Invalid session {sid}', 'bad-sid') + r = self._bad_request(f'Invalid session {sid}') + else: + try: + socket = self._get_socket(sid) + except KeyError as e: # pragma: no cover + self._log_error_once(f'{e} {sid}', 'bad-sid') + r = self._bad_request(f'{e} {sid}') + else: + if self.transport(sid) != transport and \ + transport != upgrade_header: + self._log_error_once( + f'Invalid transport for session {sid}', + 'bad-transport') + r = self._bad_request('Invalid transport') + else: + try: + packets = await socket.handle_get_request( + environ) + if isinstance(packets, list): + r = self._ok(packets, + jsonp_index=jsonp_index) + else: + r = packets + except exceptions.EngineIOError: + if sid in self.sockets: # pragma: no cover + await self.disconnect(sid) + r = self._bad_request() + if sid in self.sockets and \ + self.sockets[sid].closed: + del self.sockets[sid] + elif method == 'POST': + if sid is None or sid not in self.sockets: + self._log_error_once(f'Invalid session {sid}', 'bad-sid') + r = self._bad_request(f'Invalid session {sid}') + else: + socket = self._get_socket(sid) + try: + await socket.handle_post_request(environ) + r = self._ok(jsonp_index=jsonp_index) + except exceptions.EngineIOError: + if sid in self.sockets: # pragma: no cover + await self.disconnect(sid) + r = self._bad_request() + except: # pragma: no cover + # for any other unexpected errors, we log the error + # and keep going + self.logger.exception('post request handler error') + r = self._ok(jsonp_index=jsonp_index) + elif method == 'OPTIONS': + r = self._ok() + else: + self.logger.warning('Method %s not supported', method) + r = self._method_not_found() + if not isinstance(r, dict): + return r + if self.http_compression and \ + len(r['response']) >= self.compression_threshold: + encodings = [e.split(';')[0].strip() for e in + environ.get('HTTP_ACCEPT_ENCODING', '').split(',')] + for encoding in encodings: + if encoding in self.compression_methods: + r['response'] = \ + getattr(self, '_' + encoding)(r['response']) + r['headers'] += [('Content-Encoding', encoding)] + break + return await self._make_response(r, environ) + + async def shutdown(self): + """Stop Socket.IO background tasks. + + This method stops background activity initiated by the Socket.IO + server. It must be called before shutting down the web server. + """ + self.logger.info('Socket.IO is shutting down') + if self.service_task_event: # pragma: no cover + self.service_task_event.set() + await self.service_task_handle + self.service_task_handle = None + + def start_background_task(self, target, *args, **kwargs): + """Start a background task using the appropriate async model. + + This is a utility function that applications can use to start a + background task using the method that is compatible with the + selected async mode. + + :param target: the target function to execute. + :param args: arguments to pass to the function. + :param kwargs: keyword arguments to pass to the function. + + The return value is a ``asyncio.Task`` object. + """ + return asyncio.ensure_future(target(*args, **kwargs)) + + async def sleep(self, seconds=0): + """Sleep for the requested amount of time using the appropriate async + model. + + This is a utility function that applications can use to put a task to + sleep without having to worry about using the correct call for the + selected async mode. + + Note: this method is a coroutine. + """ + return await asyncio.sleep(seconds) + + def create_queue(self, *args, **kwargs): + """Create a queue object using the appropriate async model. + + This is a utility function that applications can use to create a queue + without having to worry about using the correct call for the selected + async mode. For asyncio based async modes, this returns an instance of + ``asyncio.Queue``. + """ + return asyncio.Queue(*args, **kwargs) + + def get_queue_empty_exception(self): + """Return the queue empty exception for the appropriate async model. + + This is a utility function that applications can use to work with a + queue without having to worry about using the correct call for the + selected async mode. For asyncio based async modes, this returns an + instance of ``asyncio.QueueEmpty``. + """ + return asyncio.QueueEmpty + + def create_event(self, *args, **kwargs): + """Create an event object using the appropriate async model. + + This is a utility function that applications can use to create an + event without having to worry about using the correct call for the + selected async mode. For asyncio based async modes, this returns + an instance of ``asyncio.Event``. + """ + return asyncio.Event(*args, **kwargs) + + async def _make_response(self, response_dict, environ): + cors_headers = self._cors_headers(environ) + make_response = self._async['make_response'] + if asyncio.iscoroutinefunction(make_response): + response = await make_response( + response_dict['status'], + response_dict['headers'] + cors_headers, + response_dict['response'], environ) + else: + response = make_response( + response_dict['status'], + response_dict['headers'] + cors_headers, + response_dict['response'], environ) + return response + + async def _handle_connect(self, environ, transport, jsonp_index=None): + """Handle a client connection request.""" + if self.start_service_task: + # start the service task to monitor connected clients + self.start_service_task = False + self.service_task_handle = self.start_background_task( + self._service_task) + + sid = self.generate_id() + s = async_socket.AsyncSocket(self, sid) + self.sockets[sid] = s + + pkt = packet.Packet(packet.OPEN, { + 'sid': sid, + 'upgrades': self._upgrades(sid, transport), + 'pingTimeout': int(self.ping_timeout * 1000), + 'pingInterval': int( + self.ping_interval + self.ping_interval_grace_period) * 1000, + 'maxPayload': self.max_http_buffer_size, + }) + await s.send(pkt) + s.schedule_ping() + + ret = await self._trigger_event('connect', sid, environ, + run_async=False) + if ret is not None and ret is not True: + del self.sockets[sid] + self.logger.warning('Application rejected connection') + return self._unauthorized(ret or None) + + if transport == 'websocket': + ret = await s.handle_get_request(environ) + if s.closed and sid in self.sockets: + # websocket connection ended, so we are done + del self.sockets[sid] + return ret + else: + s.connected = True + headers = None + if self.cookie: + if isinstance(self.cookie, dict): + headers = [( + 'Set-Cookie', + self._generate_sid_cookie(sid, self.cookie) + )] + else: + headers = [( + 'Set-Cookie', + self._generate_sid_cookie(sid, { + 'name': self.cookie, 'path': '/', 'SameSite': 'Lax' + }) + )] + try: + return self._ok(await s.poll(), headers=headers, + jsonp_index=jsonp_index) + except exceptions.QueueEmpty: + return self._bad_request() + + async def _trigger_event(self, event, *args, **kwargs): + """Invoke an event handler.""" + run_async = kwargs.pop('run_async', False) + ret = None + if event in self.handlers: + if asyncio.iscoroutinefunction(self.handlers[event]): + async def run_async_handler(): + try: + try: + return await self.handlers[event](*args) + except TypeError: + if event == 'disconnect' and \ + len(args) == 2: # pragma: no branch + # legacy disconnect events do not have a reason + # argument + return await self.handlers[event](args[0]) + else: # pragma: no cover + raise + except asyncio.CancelledError: # pragma: no cover + pass + except: + self.logger.exception(event + ' async handler error') + if event == 'connect': + # if connect handler raised error we reject the + # connection + return False + + if run_async: + ret = self.start_background_task(run_async_handler) + task_reference_holder.add(ret) + ret.add_done_callback(task_reference_holder.discard) + else: + ret = await run_async_handler() + else: + async def run_sync_handler(): + try: + try: + return self.handlers[event](*args) + except TypeError: + if event == 'disconnect' and \ + len(args) == 2: # pragma: no branch + # legacy disconnect events do not have a reason + # argument + return self.handlers[event](args[0]) + else: # pragma: no cover + raise + except: + self.logger.exception(event + ' handler error') + if event == 'connect': + # if connect handler raised error we reject the + # connection + return False + + if run_async: + ret = self.start_background_task(run_sync_handler) + task_reference_holder.add(ret) + ret.add_done_callback(task_reference_holder.discard) + else: + ret = await run_sync_handler() + return ret + + async def _service_task(self): # pragma: no cover + """Monitor connected clients and clean up those that time out.""" + loop = asyncio.get_running_loop() + self.service_task_event = self.create_event() + while not self.service_task_event.is_set(): + if len(self.sockets) == 0: + # nothing to do + try: + await asyncio.wait_for(self.service_task_event.wait(), + timeout=self.ping_timeout) + break + except asyncio.TimeoutError: + continue + + # go through the entire client list in a ping interval cycle + sleep_interval = self.ping_timeout / len(self.sockets) + + try: + # iterate over the current clients + for s in self.sockets.copy().values(): + if s.closed: + try: + del self.sockets[s.sid] + except KeyError: + # the socket could have also been removed by + # the _get_socket() method from another thread + pass + elif not s.closing: + await s.check_ping_timeout() + try: + await asyncio.wait_for(self.service_task_event.wait(), + timeout=sleep_interval) + raise KeyboardInterrupt() + except asyncio.TimeoutError: + continue + except ( + SystemExit, + KeyboardInterrupt, + asyncio.CancelledError, + GeneratorExit, + ): + self.logger.info('service task canceled') + break + except: + if loop.is_closed(): + self.logger.info('event loop is closed, exiting service ' + 'task') + break + + # an unexpected exception has occurred, log it and continue + self.logger.exception('service task exception') diff --git a/venv/lib/python3.12/site-packages/engineio/async_socket.py b/venv/lib/python3.12/site-packages/engineio/async_socket.py new file mode 100644 index 0000000..cfdbe1a --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/async_socket.py @@ -0,0 +1,261 @@ +import asyncio +import sys +import time + +from . import base_socket +from . import exceptions +from . import packet +from . import payload + + +class AsyncSocket(base_socket.BaseSocket): + async def poll(self): + """Wait for packets to send to the client.""" + try: + packets = [await asyncio.wait_for( + self.queue.get(), + self.server.ping_interval + self.server.ping_timeout)] + self.queue.task_done() + except (asyncio.TimeoutError, asyncio.CancelledError): + raise exceptions.QueueEmpty() + if packets == [None]: + return [] + while True: + try: + pkt = self.queue.get_nowait() + self.queue.task_done() + if pkt is None: + self.queue.put_nowait(None) + break + packets.append(pkt) + except asyncio.QueueEmpty: + break + return packets + + async def receive(self, pkt): + """Receive packet from the client.""" + self.server.logger.info('%s: Received packet %s data %s', + self.sid, packet.packet_names[pkt.packet_type], + pkt.data if not isinstance(pkt.data, bytes) + else '') + if pkt.packet_type == packet.PONG: + self.schedule_ping() + elif pkt.packet_type == packet.MESSAGE: + await self.server._trigger_event( + 'message', self.sid, pkt.data, + run_async=self.server.async_handlers) + elif pkt.packet_type == packet.UPGRADE: + await self.send(packet.Packet(packet.NOOP)) + elif pkt.packet_type == packet.CLOSE: + await self.close(wait=False, abort=True, + reason=self.server.reason.CLIENT_DISCONNECT) + else: + raise exceptions.UnknownPacketError() + + async def check_ping_timeout(self): + """Make sure the client is still sending pings.""" + if self.closed: + raise exceptions.SocketIsClosedError() + if self.last_ping and \ + time.time() - self.last_ping > self.server.ping_timeout: + self.server.logger.info('%s: Client is gone, closing socket', + self.sid) + # Passing abort=False here will cause close() to write a + # CLOSE packet. This has the effect of updating half-open sockets + # to their correct state of disconnected + await self.close(wait=False, abort=False, + reason=self.server.reason.PING_TIMEOUT) + return False + return True + + async def send(self, pkt): + """Send a packet to the client.""" + if not await self.check_ping_timeout(): + return + else: + await self.queue.put(pkt) + self.server.logger.info('%s: Sending packet %s data %s', + self.sid, packet.packet_names[pkt.packet_type], + pkt.data if not isinstance(pkt.data, bytes) + else '') + + async def handle_get_request(self, environ): + """Handle a long-polling GET request from the client.""" + connections = [ + s.strip() + for s in environ.get('HTTP_CONNECTION', '').lower().split(',')] + transport = environ.get('HTTP_UPGRADE', '').lower() + if 'upgrade' in connections and transport in self.upgrade_protocols: + self.server.logger.info('%s: Received request to upgrade to %s', + self.sid, transport) + return await getattr(self, '_upgrade_' + transport)(environ) + if self.upgrading or self.upgraded: + # we are upgrading to WebSocket, do not return any more packets + # through the polling endpoint + return [packet.Packet(packet.NOOP)] + try: + packets = await self.poll() + except exceptions.QueueEmpty: + exc = sys.exc_info() + await self.close(wait=False, + reason=self.server.reason.TRANSPORT_ERROR) + raise exc[1].with_traceback(exc[2]) + return packets + + async def handle_post_request(self, environ): + """Handle a long-polling POST request from the client.""" + length = int(environ.get('CONTENT_LENGTH', '0')) + if length > self.server.max_http_buffer_size: + raise exceptions.ContentTooLongError() + else: + body = (await environ['wsgi.input'].read(length)).decode('utf-8') + p = payload.Payload(encoded_payload=body) + for pkt in p.packets: + await self.receive(pkt) + + async def close(self, wait=True, abort=False, reason=None): + """Close the socket connection.""" + if not self.closed and not self.closing: + self.closing = True + await self.server._trigger_event( + 'disconnect', self.sid, + reason or self.server.reason.SERVER_DISCONNECT, + run_async=False) + if not abort: + await self.send(packet.Packet(packet.CLOSE)) + self.closed = True + if wait: + await self.queue.join() + + def schedule_ping(self): + self.server.start_background_task(self._send_ping) + + async def _send_ping(self): + self.last_ping = None + await asyncio.sleep(self.server.ping_interval) + if not self.closing and not self.closed: + self.last_ping = time.time() + await self.send(packet.Packet(packet.PING)) + + async def _upgrade_websocket(self, environ): + """Upgrade the connection from polling to websocket.""" + if self.upgraded: + raise OSError('Socket has been upgraded already') + if self.server._async['websocket'] is None: + # the selected async mode does not support websocket + return self.server._bad_request() + ws = self.server._async['websocket']( + self._websocket_handler, self.server) + return await ws(environ) + + async def _websocket_handler(self, ws): + """Engine.IO handler for websocket transport.""" + async def websocket_wait(): + data = await ws.wait() + if data and len(data) > self.server.max_http_buffer_size: + raise ValueError('packet is too large') + return data + + if self.connected: + # the socket was already connected, so this is an upgrade + self.upgrading = True # hold packet sends during the upgrade + + try: + pkt = await websocket_wait() + except OSError: # pragma: no cover + return + decoded_pkt = packet.Packet(encoded_packet=pkt) + if decoded_pkt.packet_type != packet.PING or \ + decoded_pkt.data != 'probe': + self.server.logger.info( + '%s: Failed websocket upgrade, no PING packet', self.sid) + self.upgrading = False + return + await ws.send(packet.Packet(packet.PONG, data='probe').encode()) + await self.queue.put(packet.Packet(packet.NOOP)) # end poll + + try: + pkt = await websocket_wait() + except OSError: # pragma: no cover + self.upgrading = False + return + decoded_pkt = packet.Packet(encoded_packet=pkt) + if decoded_pkt.packet_type != packet.UPGRADE: + self.upgraded = False + self.server.logger.info( + ('%s: Failed websocket upgrade, expected UPGRADE packet, ' + 'received %s instead.'), + self.sid, pkt) + self.upgrading = False + return + self.upgraded = True + self.upgrading = False + else: + self.connected = True + self.upgraded = True + + # start separate writer thread + async def writer(): + while True: + packets = None + try: + packets = await self.poll() + except exceptions.QueueEmpty: + break + if not packets: + # empty packet list returned -> connection closed + break + try: + for pkt in packets: + await ws.send(pkt.encode()) + except: + break + await ws.close() + + writer_task = asyncio.ensure_future(writer()) + + self.server.logger.info( + '%s: Upgrade to websocket successful', self.sid) + + while True: + p = None + wait_task = asyncio.ensure_future(websocket_wait()) + try: + p = await asyncio.wait_for( + wait_task, + self.server.ping_interval + self.server.ping_timeout) + except asyncio.CancelledError: # pragma: no cover + # there is a bug (https://bugs.python.org/issue30508) in + # asyncio that causes a "Task exception never retrieved" error + # to appear when wait_task raises an exception before it gets + # cancelled. Calling wait_task.exception() prevents the error + # from being issued in Python 3.6, but causes other errors in + # other versions, so we run it with all errors suppressed and + # hope for the best. + try: + wait_task.exception() + except: + pass + break + except: + break + if p is None: + # connection closed by client + break + pkt = packet.Packet(encoded_packet=p) + try: + await self.receive(pkt) + except exceptions.UnknownPacketError: # pragma: no cover + pass + except exceptions.SocketIsClosedError: # pragma: no cover + self.server.logger.info('Receive error -- socket is closed') + break + except: # pragma: no cover + # if we get an unexpected exception we log the error and exit + # the connection properly + self.server.logger.exception('Unknown receive error') + + await self.queue.put(None) # unlock the writer task so it can exit + await asyncio.wait_for(writer_task, timeout=None) + await self.close(wait=False, abort=True, + reason=self.server.reason.TRANSPORT_CLOSE) diff --git a/venv/lib/python3.12/site-packages/engineio/base_client.py b/venv/lib/python3.12/site-packages/engineio/base_client.py new file mode 100644 index 0000000..01a42c5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/base_client.py @@ -0,0 +1,169 @@ +import logging +import signal +import threading +import time +import urllib +from . import packet + +default_logger = logging.getLogger('engineio.client') +connected_clients = [] + + +def signal_handler(sig, frame): + """SIGINT handler. + + Disconnect all active clients and then invoke the original signal handler. + """ + for client in connected_clients[:]: + if not client.is_asyncio_based(): + client.disconnect() + if callable(original_signal_handler): + return original_signal_handler(sig, frame) + else: # pragma: no cover + # Handle case where no original SIGINT handler was present. + return signal.default_int_handler(sig, frame) + + +original_signal_handler = None + + +class BaseClient: + event_names = ['connect', 'disconnect', 'message'] + + class reason: + """Disconnection reasons.""" + #: Client-initiated disconnection. + CLIENT_DISCONNECT = 'client disconnect' + #: Server-initiated disconnection. + SERVER_DISCONNECT = 'server disconnect' + #: Transport error. + TRANSPORT_ERROR = 'transport error' + + def __init__(self, logger=False, json=None, request_timeout=5, + http_session=None, ssl_verify=True, handle_sigint=True, + websocket_extra_options=None, timestamp_requests=True): + global original_signal_handler + if handle_sigint and original_signal_handler is None and \ + threading.current_thread() == threading.main_thread(): + original_signal_handler = signal.signal(signal.SIGINT, + signal_handler) + self.handlers = {} + self.base_url = None + self.transports = None + self.current_transport = None + self.sid = None + self.upgrades = None + self.ping_interval = None + self.ping_timeout = None + self.http = http_session + self.external_http = http_session is not None + self.handle_sigint = handle_sigint + self.ws = None + self.read_loop_task = None + self.write_loop_task = None + self.queue = self.create_queue() + self.queue_empty = self.get_queue_empty_exception() + self.state = 'disconnected' + self.ssl_verify = ssl_verify + self.websocket_extra_options = websocket_extra_options or {} + self.timestamp_requests = timestamp_requests + + if json is not None: + packet.Packet.json = json + if not isinstance(logger, bool): + self.logger = logger + else: + self.logger = default_logger + if self.logger.level == logging.NOTSET: + if logger: + self.logger.setLevel(logging.INFO) + else: + self.logger.setLevel(logging.ERROR) + self.logger.addHandler(logging.StreamHandler()) + + self.request_timeout = request_timeout + + def is_asyncio_based(self): + return False + + def on(self, event, handler=None): + """Register an event handler. + + :param event: The event name. Can be ``'connect'``, ``'message'`` or + ``'disconnect'``. + :param handler: The function that should be invoked to handle the + event. When this parameter is not given, the method + acts as a decorator for the handler function. + + Example usage:: + + # as a decorator: + @eio.on('connect') + def connect_handler(): + print('Connection request') + + # as a method: + def message_handler(msg): + print('Received message: ', msg) + eio.send('response') + eio.on('message', message_handler) + """ + if event not in self.event_names: + raise ValueError('Invalid event') + + def set_handler(handler): + self.handlers[event] = handler + return handler + + if handler is None: + return set_handler + set_handler(handler) + + def transport(self): + """Return the name of the transport currently in use. + + The possible values returned by this function are ``'polling'`` and + ``'websocket'``. + """ + return self.current_transport + + def _reset(self): + self.state = 'disconnected' + self.sid = None + + def _get_engineio_url(self, url, engineio_path, transport): + """Generate the Engine.IO connection URL.""" + engineio_path = engineio_path.strip('/') + parsed_url = urllib.parse.urlparse(url) + + if transport == 'polling': + scheme = 'http' + elif transport == 'websocket': + scheme = 'ws' + else: # pragma: no cover + raise ValueError('invalid transport') + if parsed_url.scheme in ['https', 'wss']: + scheme += 's' + + return ('{scheme}://{netloc}/{path}/?{query}' + '{sep}transport={transport}&EIO=4').format( + scheme=scheme, netloc=parsed_url.netloc, + path=engineio_path, query=parsed_url.query, + sep='&' if parsed_url.query else '', + transport=transport) + + def _get_url_timestamp(self): + """Generate the Engine.IO query string timestamp.""" + if not self.timestamp_requests: + return '' + return '&t=' + str(time.time()) + + def create_queue(self, *args, **kwargs): # pragma: no cover + """Create a queue object.""" + raise NotImplementedError('must be implemented in a subclass') + + def get_queue_empty_exception(self): # pragma: no cover + """Return the queue empty exception raised by queues created by the + ``create_queue()`` method. + """ + raise NotImplementedError('must be implemented in a subclass') diff --git a/venv/lib/python3.12/site-packages/engineio/base_server.py b/venv/lib/python3.12/site-packages/engineio/base_server.py new file mode 100644 index 0000000..7d717fb --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/base_server.py @@ -0,0 +1,358 @@ +import base64 +import gzip +import importlib +import io +import logging +import secrets +import zlib + +from . import packet +from . import payload + +default_logger = logging.getLogger('engineio.server') + + +class BaseServer: + compression_methods = ['gzip', 'deflate'] + event_names = ['connect', 'disconnect', 'message'] + valid_transports = ['polling', 'websocket'] + _default_monitor_clients = True + sequence_number = 0 + + class reason: + """Disconnection reasons.""" + #: Server-initiated disconnection. + SERVER_DISCONNECT = 'server disconnect' + #: Client-initiated disconnection. + CLIENT_DISCONNECT = 'client disconnect' + #: Ping timeout. + PING_TIMEOUT = 'ping timeout' + #: Transport close. + TRANSPORT_CLOSE = 'transport close' + #: Transport error. + TRANSPORT_ERROR = 'transport error' + + def __init__(self, async_mode=None, ping_interval=25, ping_timeout=20, + max_http_buffer_size=1000000, allow_upgrades=True, + http_compression=True, compression_threshold=1024, + cookie=None, cors_allowed_origins=None, + cors_credentials=True, logger=False, json=None, + async_handlers=True, monitor_clients=None, transports=None, + **kwargs): + self.ping_timeout = ping_timeout + if isinstance(ping_interval, tuple): + self.ping_interval = ping_interval[0] + self.ping_interval_grace_period = ping_interval[1] + else: + self.ping_interval = ping_interval + self.ping_interval_grace_period = 0 + self.max_http_buffer_size = max_http_buffer_size + self.allow_upgrades = allow_upgrades + self.http_compression = http_compression + self.compression_threshold = compression_threshold + self.cookie = cookie + self.cors_allowed_origins = cors_allowed_origins + self.cors_credentials = cors_credentials + self.async_handlers = async_handlers + self.sockets = {} + self.handlers = {} + self.log_message_keys = set() + self.start_service_task = monitor_clients \ + if monitor_clients is not None else self._default_monitor_clients + self.service_task_handle = None + self.service_task_event = None + if json is not None: + packet.Packet.json = json + if not isinstance(logger, bool): + self.logger = logger + else: + self.logger = default_logger + if self.logger.level == logging.NOTSET: + if logger: + self.logger.setLevel(logging.INFO) + else: + self.logger.setLevel(logging.ERROR) + self.logger.addHandler(logging.StreamHandler()) + modes = self.async_modes() + if async_mode is not None: + modes = [async_mode] if async_mode in modes else [] + self._async = None + self.async_mode = None + for mode in modes: + try: + self._async = importlib.import_module( + 'engineio.async_drivers.' + mode)._async + asyncio_based = self._async['asyncio'] \ + if 'asyncio' in self._async else False + if asyncio_based != self.is_asyncio_based(): + continue # pragma: no cover + self.async_mode = mode + break + except ImportError: + pass + if self.async_mode is None: + raise ValueError('Invalid async_mode specified') + if self.is_asyncio_based() and \ + ('asyncio' not in self._async or not + self._async['asyncio']): # pragma: no cover + raise ValueError('The selected async_mode is not asyncio ' + 'compatible') + if not self.is_asyncio_based() and 'asyncio' in self._async and \ + self._async['asyncio']: # pragma: no cover + raise ValueError('The selected async_mode requires asyncio and ' + 'must use the AsyncServer class') + if transports is not None: + if isinstance(transports, str): + transports = [transports] + transports = [transport for transport in transports + if transport in self.valid_transports] + if not transports: + raise ValueError('No valid transports provided') + self.transports = transports or self.valid_transports + self.logger.info('Server initialized for %s.', self.async_mode) + + def is_asyncio_based(self): + return False + + def async_modes(self): + return ['eventlet', 'gevent_uwsgi', 'gevent', 'threading'] + + def on(self, event, handler=None): + """Register an event handler. + + :param event: The event name. Can be ``'connect'``, ``'message'`` or + ``'disconnect'``. + :param handler: The function that should be invoked to handle the + event. When this parameter is not given, the method + acts as a decorator for the handler function. + + Example usage:: + + # as a decorator: + @eio.on('connect') + def connect_handler(sid, environ): + print('Connection request') + if environ['REMOTE_ADDR'] in blacklisted: + return False # reject + + # as a method: + def message_handler(sid, msg): + print('Received message: ', msg) + eio.send(sid, 'response') + eio.on('message', message_handler) + + The handler function receives the ``sid`` (session ID) for the + client as first argument. The ``'connect'`` event handler receives the + WSGI environment as a second argument, and can return ``False`` to + reject the connection. The ``'message'`` handler receives the message + payload as a second argument. The ``'disconnect'`` handler does not + take a second argument. + """ + if event not in self.event_names: + raise ValueError('Invalid event') + + def set_handler(handler): + self.handlers[event] = handler + return handler + + if handler is None: + return set_handler + set_handler(handler) + + def transport(self, sid): + """Return the name of the transport used by the client. + + The two possible values returned by this function are ``'polling'`` + and ``'websocket'``. + + :param sid: The session of the client. + """ + return 'websocket' if self._get_socket(sid).upgraded else 'polling' + + def create_queue(self, *args, **kwargs): + """Create a queue object using the appropriate async model. + + This is a utility function that applications can use to create a queue + without having to worry about using the correct call for the selected + async mode. + """ + return self._async['queue'](*args, **kwargs) + + def get_queue_empty_exception(self): + """Return the queue empty exception for the appropriate async model. + + This is a utility function that applications can use to work with a + queue without having to worry about using the correct call for the + selected async mode. + """ + return self._async['queue_empty'] + + def create_event(self, *args, **kwargs): + """Create an event object using the appropriate async model. + + This is a utility function that applications can use to create an + event without having to worry about using the correct call for the + selected async mode. + """ + return self._async['event'](*args, **kwargs) + + def generate_id(self): + """Generate a unique session id.""" + id = base64.b64encode( + secrets.token_bytes(12) + self.sequence_number.to_bytes(3, 'big')) + self.sequence_number = (self.sequence_number + 1) & 0xffffff + return id.decode('utf-8').replace('/', '_').replace('+', '-') + + def _generate_sid_cookie(self, sid, attributes): + """Generate the sid cookie.""" + cookie = attributes.get('name', 'io') + '=' + sid + for attribute, value in attributes.items(): + if attribute == 'name': + continue + if callable(value): + value = value() + if value is True: + cookie += '; ' + attribute + else: + cookie += '; ' + attribute + '=' + value + return cookie + + def _upgrades(self, sid, transport): + """Return the list of possible upgrades for a client connection.""" + if not self.allow_upgrades or self._get_socket(sid).upgraded or \ + transport == 'websocket': + return [] + if self._async['websocket'] is None: # pragma: no cover + self._log_error_once( + 'The WebSocket transport is not available, you must install a ' + 'WebSocket server that is compatible with your async mode to ' + 'enable it. See the documentation for details.', + 'no-websocket') + return [] + return ['websocket'] + + def _get_socket(self, sid): + """Return the socket object for a given session.""" + try: + s = self.sockets[sid] + except KeyError: + raise KeyError('Session not found') + if s.closed: + del self.sockets[sid] + raise KeyError('Session is disconnected') + return s + + def _ok(self, packets=None, headers=None, jsonp_index=None): + """Generate a successful HTTP response.""" + if packets is not None: + if headers is None: + headers = [] + headers += [('Content-Type', 'text/plain; charset=UTF-8')] + return {'status': '200 OK', + 'headers': headers, + 'response': payload.Payload(packets=packets).encode( + jsonp_index=jsonp_index).encode('utf-8')} + else: + return {'status': '200 OK', + 'headers': [('Content-Type', 'text/plain')], + 'response': b'OK'} + + def _bad_request(self, message=None): + """Generate a bad request HTTP error response.""" + if message is None: + message = 'Bad Request' + message = packet.Packet.json.dumps(message) + return {'status': '400 BAD REQUEST', + 'headers': [('Content-Type', 'text/plain')], + 'response': message.encode('utf-8')} + + def _method_not_found(self): + """Generate a method not found HTTP error response.""" + return {'status': '405 METHOD NOT FOUND', + 'headers': [('Content-Type', 'text/plain')], + 'response': b'Method Not Found'} + + def _unauthorized(self, message=None): + """Generate a unauthorized HTTP error response.""" + if message is None: + message = 'Unauthorized' + message = packet.Packet.json.dumps(message) + return {'status': '401 UNAUTHORIZED', + 'headers': [('Content-Type', 'application/json')], + 'response': message.encode('utf-8')} + + def _cors_allowed_origins(self, environ): + if self.cors_allowed_origins is None: + allowed_origins = [] + if 'wsgi.url_scheme' in environ and 'HTTP_HOST' in environ: + allowed_origins.append('{scheme}://{host}'.format( + scheme=environ['wsgi.url_scheme'], + host=environ['HTTP_HOST'])) + if 'HTTP_X_FORWARDED_PROTO' in environ or \ + 'HTTP_X_FORWARDED_HOST' in environ: + scheme = environ.get( + 'HTTP_X_FORWARDED_PROTO', + environ['wsgi.url_scheme']).split(',')[0].strip() + allowed_origins.append('{scheme}://{host}'.format( + scheme=scheme, host=environ.get( + 'HTTP_X_FORWARDED_HOST', + environ['HTTP_HOST']).split( + ',')[0].strip())) + elif self.cors_allowed_origins == '*': + allowed_origins = None + elif isinstance(self.cors_allowed_origins, str): + allowed_origins = [self.cors_allowed_origins] + elif callable(self.cors_allowed_origins): + origin = environ.get('HTTP_ORIGIN') + try: + is_allowed = self.cors_allowed_origins(origin, environ) + except TypeError: + is_allowed = self.cors_allowed_origins(origin) + allowed_origins = [origin] if is_allowed else [] + else: + if '*' in self.cors_allowed_origins: + allowed_origins = None + else: + allowed_origins = self.cors_allowed_origins + return allowed_origins + + def _cors_headers(self, environ): + """Return the cross-origin-resource-sharing headers.""" + if self.cors_allowed_origins == []: + # special case, CORS handling is completely disabled + return [] + headers = [] + allowed_origins = self._cors_allowed_origins(environ) + if 'HTTP_ORIGIN' in environ and \ + (allowed_origins is None or environ['HTTP_ORIGIN'] in + allowed_origins): + headers = [('Access-Control-Allow-Origin', environ['HTTP_ORIGIN'])] + if environ['REQUEST_METHOD'] == 'OPTIONS': + headers += [('Access-Control-Allow-Methods', 'OPTIONS, GET, POST')] + if 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS' in environ: + headers += [('Access-Control-Allow-Headers', + environ['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])] + if self.cors_credentials: + headers += [('Access-Control-Allow-Credentials', 'true')] + return headers + + def _gzip(self, response): + """Apply gzip compression to a response.""" + bytesio = io.BytesIO() + with gzip.GzipFile(fileobj=bytesio, mode='w') as gz: + gz.write(response) + return bytesio.getvalue() + + def _deflate(self, response): + """Apply deflate compression to a response.""" + return zlib.compress(response) + + def _log_error_once(self, message, message_key): + """Log message with logging.ERROR level the first time, then log + with given level.""" + if message_key not in self.log_message_keys: + self.logger.error(message + ' (further occurrences of this error ' + 'will be logged with level INFO)') + self.log_message_keys.add(message_key) + else: + self.logger.info(message) diff --git a/venv/lib/python3.12/site-packages/engineio/base_socket.py b/venv/lib/python3.12/site-packages/engineio/base_socket.py new file mode 100644 index 0000000..6b5d7dc --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/base_socket.py @@ -0,0 +1,14 @@ +class BaseSocket: + upgrade_protocols = ['websocket'] + + def __init__(self, server, sid): + self.server = server + self.sid = sid + self.queue = self.server.create_queue() + self.last_ping = None + self.connected = False + self.upgrading = False + self.upgraded = False + self.closing = False + self.closed = False + self.session = {} diff --git a/venv/lib/python3.12/site-packages/engineio/client.py b/venv/lib/python3.12/site-packages/engineio/client.py new file mode 100644 index 0000000..c04e080 --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/client.py @@ -0,0 +1,632 @@ +from base64 import b64encode +from engineio.json import JSONDecodeError +import logging +import queue +import ssl +import threading +import time +import urllib + +try: + import requests +except ImportError: # pragma: no cover + requests = None +try: + import websocket +except ImportError: # pragma: no cover + websocket = None +from . import base_client +from . import exceptions +from . import packet +from . import payload + +default_logger = logging.getLogger('engineio.client') + + +class Client(base_client.BaseClient): + """An Engine.IO client. + + This class implements a fully compliant Engine.IO web client with support + for websocket and long-polling transports. + + :param logger: To enable logging set to ``True`` or pass a logger object to + use. To disable logging set to ``False``. The default is + ``False``. Note that fatal errors are logged even when + ``logger`` is ``False``. + :param json: An alternative json module to use for encoding and decoding + packets. Custom json modules must have ``dumps`` and ``loads`` + functions that are compatible with the standard library + versions. + :param request_timeout: A timeout in seconds for requests. The default is + 5 seconds. + :param http_session: an initialized ``requests.Session`` object to be used + when sending requests to the server. Use it if you + need to add special client options such as proxy + servers, SSL certificates, custom CA bundle, etc. + :param ssl_verify: ``True`` to verify SSL certificates, or ``False`` to + skip SSL certificate verification, allowing + connections to servers with self signed certificates. + The default is ``True``. + :param handle_sigint: Set to ``True`` to automatically handle disconnection + when the process is interrupted, or to ``False`` to + leave interrupt handling to the calling application. + Interrupt handling can only be enabled when the + client instance is created in the main thread. + :param websocket_extra_options: Dictionary containing additional keyword + arguments passed to + ``websocket.create_connection()``. + :param timestamp_requests: If ``True`` a timestamp is added to the query + string of Socket.IO requests as a cache-busting + measure. Set to ``False`` to disable. + """ + def connect(self, url, headers=None, transports=None, + engineio_path='engine.io'): + """Connect to an Engine.IO server. + + :param url: The URL of the Engine.IO server. It can include custom + query string parameters if required by the server. + :param headers: A dictionary with custom headers to send with the + connection request. + :param transports: The list of allowed transports. Valid transports + are ``'polling'`` and ``'websocket'``. If not + given, the polling transport is connected first, + then an upgrade to websocket is attempted. + :param engineio_path: The endpoint where the Engine.IO server is + installed. The default value is appropriate for + most cases. + + Example usage:: + + eio = engineio.Client() + eio.connect('http://localhost:5000') + """ + if self.state != 'disconnected': + raise ValueError('Client is not in a disconnected state') + valid_transports = ['polling', 'websocket'] + if transports is not None: + if isinstance(transports, str): + transports = [transports] + transports = [transport for transport in transports + if transport in valid_transports] + if not transports: + raise ValueError('No valid transports provided') + self.transports = transports or valid_transports + return getattr(self, '_connect_' + self.transports[0])( + url, headers or {}, engineio_path) + + def wait(self): + """Wait until the connection with the server ends. + + Client applications can use this function to block the main thread + during the life of the connection. + """ + if self.read_loop_task: + self.read_loop_task.join() + + def send(self, data): + """Send a message to the server. + + :param data: The data to send to the server. Data can be of type + ``str``, ``bytes``, ``list`` or ``dict``. If a ``list`` + or ``dict``, the data will be serialized as JSON. + """ + self._send_packet(packet.Packet(packet.MESSAGE, data=data)) + + def disconnect(self, abort=False, reason=None): + """Disconnect from the server. + + :param abort: If set to ``True``, do not wait for background tasks + associated with the connection to end. + """ + if self.state == 'connected': + self._send_packet(packet.Packet(packet.CLOSE)) + self.queue.put(None) + self.state = 'disconnecting' + self._trigger_event('disconnect', + reason or self.reason.CLIENT_DISCONNECT, + run_async=False) + if self.current_transport == 'websocket': + self.ws.close() + if not abort: + self.read_loop_task.join() + self.state = 'disconnected' + try: + base_client.connected_clients.remove(self) + except ValueError: # pragma: no cover + pass + self._reset() + + def start_background_task(self, target, *args, **kwargs): + """Start a background task. + + This is a utility function that applications can use to start a + background task. + + :param target: the target function to execute. + :param args: arguments to pass to the function. + :param kwargs: keyword arguments to pass to the function. + + This function returns an object that represents the background task, + on which the ``join()`` method can be invoked to wait for the task to + complete. + """ + th = threading.Thread(target=target, args=args, kwargs=kwargs, + daemon=True) + th.start() + return th + + def sleep(self, seconds=0): + """Sleep for the requested amount of time.""" + return time.sleep(seconds) + + def create_queue(self, *args, **kwargs): + """Create a queue object.""" + return queue.Queue(*args, **kwargs) + + def get_queue_empty_exception(self): + """Return the queue empty exception raised by queues created by the + ``create_queue()`` method. + """ + return queue.Empty + + def create_event(self, *args, **kwargs): + """Create an event object.""" + return threading.Event(*args, **kwargs) + + def _reset(self): + super()._reset() + while True: # pragma: no cover + try: + self.queue.get_nowait() + self.queue.task_done() + except self.queue_empty: + break + + def _connect_polling(self, url, headers, engineio_path): + """Establish a long-polling connection to the Engine.IO server.""" + if requests is None: # pragma: no cover + # not installed + self.logger.error('requests package is not installed -- cannot ' + 'send HTTP requests!') + return + self.base_url = self._get_engineio_url(url, engineio_path, 'polling') + self.logger.info('Attempting polling connection to ' + self.base_url) + r = self._send_request( + 'GET', self.base_url + self._get_url_timestamp(), headers=headers, + timeout=self.request_timeout) + if r is None or isinstance(r, str): + self._reset() + raise exceptions.ConnectionError( + r or 'Connection refused by the server') + if r.status_code < 200 or r.status_code >= 300: + self._reset() + try: + arg = r.json() + except JSONDecodeError: + arg = None + raise exceptions.ConnectionError( + 'Unexpected status code {} in server response'.format( + r.status_code), arg) + try: + p = payload.Payload(encoded_payload=r.content.decode('utf-8')) + except ValueError: + raise exceptions.ConnectionError( + 'Unexpected response from server') from None + open_packet = p.packets[0] + if open_packet.packet_type != packet.OPEN: + raise exceptions.ConnectionError( + 'OPEN packet not returned by server') + self.logger.info( + 'Polling connection accepted with ' + str(open_packet.data)) + self.sid = open_packet.data['sid'] + self.upgrades = open_packet.data['upgrades'] + self.ping_interval = int(open_packet.data['pingInterval']) / 1000.0 + self.ping_timeout = int(open_packet.data['pingTimeout']) / 1000.0 + self.current_transport = 'polling' + self.base_url += '&sid=' + self.sid + + self.state = 'connected' + base_client.connected_clients.append(self) + self._trigger_event('connect', run_async=False) + + for pkt in p.packets[1:]: + self._receive_packet(pkt) + + if 'websocket' in self.upgrades and 'websocket' in self.transports: + # attempt to upgrade to websocket + if self._connect_websocket(url, headers, engineio_path): + # upgrade to websocket succeeded, we're done here + return + + # start background tasks associated with this client + self.write_loop_task = self.start_background_task(self._write_loop) + self.read_loop_task = self.start_background_task( + self._read_loop_polling) + + def _connect_websocket(self, url, headers, engineio_path): + """Establish or upgrade to a WebSocket connection with the server.""" + if websocket is None: # pragma: no cover + # not installed + self.logger.error('websocket-client package not installed, only ' + 'polling transport is available') + return False + websocket_url = self._get_engineio_url(url, engineio_path, 'websocket') + if self.sid: + self.logger.info( + 'Attempting WebSocket upgrade to ' + websocket_url) + upgrade = True + websocket_url += '&sid=' + self.sid + else: + upgrade = False + self.base_url = websocket_url + self.logger.info( + 'Attempting WebSocket connection to ' + websocket_url) + + # get cookies and other settings from the long-polling connection + # so that they are preserved when connecting to the WebSocket route + cookies = None + extra_options = {} + if self.http: + # cookies + cookies = '; '.join([f"{cookie.name}={cookie.value}" + for cookie in self.http.cookies]) + for header, value in headers.items(): + if header.lower() == 'cookie': + if cookies: + cookies += '; ' + cookies += value + del headers[header] + break + + # auth + if 'Authorization' not in headers and self.http.auth is not None: + if not isinstance(self.http.auth, tuple): # pragma: no cover + raise ValueError('Only basic authentication is supported') + basic_auth = '{}:{}'.format( + self.http.auth[0], self.http.auth[1]).encode('utf-8') + basic_auth = b64encode(basic_auth).decode('utf-8') + headers['Authorization'] = 'Basic ' + basic_auth + + # cert + # this can be given as ('certfile', 'keyfile') or just 'certfile' + if isinstance(self.http.cert, tuple): + extra_options['sslopt'] = { + 'certfile': self.http.cert[0], + 'keyfile': self.http.cert[1]} + elif self.http.cert: + extra_options['sslopt'] = {'certfile': self.http.cert} + + # proxies + if self.http.proxies: + proxy_url = None + if websocket_url.startswith('ws://'): + proxy_url = self.http.proxies.get( + 'ws', self.http.proxies.get('http')) + else: # wss:// + proxy_url = self.http.proxies.get( + 'wss', self.http.proxies.get('https')) + if proxy_url: + parsed_url = urllib.parse.urlparse( + proxy_url if '://' in proxy_url + else 'scheme://' + proxy_url) + extra_options['http_proxy_host'] = parsed_url.hostname + extra_options['http_proxy_port'] = parsed_url.port + extra_options['http_proxy_auth'] = ( + (parsed_url.username, parsed_url.password) + if parsed_url.username or parsed_url.password + else None) + + # verify + if isinstance(self.http.verify, str): + if 'sslopt' in extra_options: + extra_options['sslopt']['ca_certs'] = self.http.verify + else: + extra_options['sslopt'] = {'ca_certs': self.http.verify} + elif not self.http.verify: + self.ssl_verify = False + + if not self.ssl_verify: + if 'sslopt' in extra_options: + extra_options['sslopt'].update({"cert_reqs": ssl.CERT_NONE}) + else: + extra_options['sslopt'] = {"cert_reqs": ssl.CERT_NONE} + + # combine internally generated options with the ones supplied by the + # caller. The caller's options take precedence. + headers.update(self.websocket_extra_options.pop('header', {})) + extra_options['header'] = headers + extra_options['cookie'] = cookies + extra_options['enable_multithread'] = True + extra_options['timeout'] = self.request_timeout + extra_options.update(self.websocket_extra_options) + try: + ws = websocket.create_connection( + websocket_url + self._get_url_timestamp(), **extra_options) + except (ConnectionError, OSError, websocket.WebSocketException): + if upgrade: + self.logger.warning( + 'WebSocket upgrade failed: connection error') + return False + else: + raise exceptions.ConnectionError('Connection error') + if upgrade: + p = packet.Packet(packet.PING, data='probe').encode() + try: + ws.send(p) + except Exception as e: # pragma: no cover + self.logger.warning( + 'WebSocket upgrade failed: unexpected send exception: %s', + str(e)) + return False + try: + p = ws.recv() + except Exception as e: # pragma: no cover + self.logger.warning( + 'WebSocket upgrade failed: unexpected recv exception: %s', + str(e)) + return False + pkt = packet.Packet(encoded_packet=p) + if pkt.packet_type != packet.PONG or pkt.data != 'probe': + self.logger.warning( + 'WebSocket upgrade failed: no PONG packet') + return False + p = packet.Packet(packet.UPGRADE).encode() + try: + ws.send(p) + except Exception as e: # pragma: no cover + self.logger.warning( + 'WebSocket upgrade failed: unexpected send exception: %s', + str(e)) + return False + self.current_transport = 'websocket' + self.logger.info('WebSocket upgrade was successful') + else: + try: + p = ws.recv() + except Exception as e: # pragma: no cover + raise exceptions.ConnectionError( + 'Unexpected recv exception: ' + str(e)) + open_packet = packet.Packet(encoded_packet=p) + if open_packet.packet_type != packet.OPEN: + raise exceptions.ConnectionError('no OPEN packet') + self.logger.info( + 'WebSocket connection accepted with ' + str(open_packet.data)) + self.sid = open_packet.data['sid'] + self.upgrades = open_packet.data['upgrades'] + self.ping_interval = int(open_packet.data['pingInterval']) / 1000.0 + self.ping_timeout = int(open_packet.data['pingTimeout']) / 1000.0 + self.current_transport = 'websocket' + + self.state = 'connected' + base_client.connected_clients.append(self) + self._trigger_event('connect', run_async=False) + self.ws = ws + self.ws.settimeout(self.ping_interval + self.ping_timeout) + + # start background tasks associated with this client + self.write_loop_task = self.start_background_task(self._write_loop) + self.read_loop_task = self.start_background_task( + self._read_loop_websocket) + return True + + def _receive_packet(self, pkt): + """Handle incoming packets from the server.""" + packet_name = packet.packet_names[pkt.packet_type] \ + if pkt.packet_type < len(packet.packet_names) else 'UNKNOWN' + self.logger.info( + 'Received packet %s data %s', packet_name, + pkt.data if not isinstance(pkt.data, bytes) else '') + if pkt.packet_type == packet.MESSAGE: + self._trigger_event('message', pkt.data, run_async=True) + elif pkt.packet_type == packet.PING: + self._send_packet(packet.Packet(packet.PONG, pkt.data)) + elif pkt.packet_type == packet.CLOSE: + self.disconnect(abort=True, reason=self.reason.SERVER_DISCONNECT) + elif pkt.packet_type == packet.NOOP: + pass + else: + self.logger.error('Received unexpected packet of type %s', + pkt.packet_type) + + def _send_packet(self, pkt): + """Queue a packet to be sent to the server.""" + if self.state != 'connected': + return + self.queue.put(pkt) + self.logger.info( + 'Sending packet %s data %s', + packet.packet_names[pkt.packet_type], + pkt.data if not isinstance(pkt.data, bytes) else '') + + def _send_request( + self, method, url, headers=None, body=None, + timeout=None): # pragma: no cover + if self.http is None: + self.http = requests.Session() + if not self.ssl_verify: + self.http.verify = False + try: + return self.http.request(method, url, headers=headers, data=body, + timeout=timeout) + except requests.exceptions.RequestException as exc: + self.logger.info('HTTP %s request to %s failed with error %s.', + method, url, exc) + return str(exc) + + def _trigger_event(self, event, *args, **kwargs): + """Invoke an event handler.""" + run_async = kwargs.pop('run_async', False) + if event in self.handlers: + if run_async: + return self.start_background_task(self.handlers[event], *args) + else: + try: + try: + return self.handlers[event](*args) + except TypeError: + if event == 'disconnect' and \ + len(args) == 1: # pragma: no branch + # legacy disconnect events do not have a reason + # argument + return self.handlers[event]() + else: # pragma: no cover + raise + except: + self.logger.exception(event + ' handler error') + + def _read_loop_polling(self): + """Read packets by polling the Engine.IO server.""" + while self.state == 'connected' and self.write_loop_task: + self.logger.info( + 'Sending polling GET request to ' + self.base_url) + r = self._send_request( + 'GET', self.base_url + self._get_url_timestamp(), + timeout=max(self.ping_interval, self.ping_timeout) + 5) + if r is None or isinstance(r, str): + self.logger.warning( + r or 'Connection refused by the server, aborting') + self.queue.put(None) + break + if r.status_code < 200 or r.status_code >= 300: + self.logger.warning('Unexpected status code %s in server ' + 'response, aborting', r.status_code) + self.queue.put(None) + break + try: + p = payload.Payload(encoded_payload=r.content.decode('utf-8')) + except ValueError: + self.logger.warning( + 'Unexpected packet from server, aborting') + self.queue.put(None) + break + for pkt in p.packets: + self._receive_packet(pkt) + + if self.write_loop_task: # pragma: no branch + self.logger.info('Waiting for write loop task to end') + self.write_loop_task.join() + if self.state == 'connected': + self._trigger_event('disconnect', self.reason.TRANSPORT_ERROR, + run_async=False) + try: + base_client.connected_clients.remove(self) + except ValueError: # pragma: no cover + pass + self._reset() + self.logger.info('Exiting read loop task') + + def _read_loop_websocket(self): + """Read packets from the Engine.IO WebSocket connection.""" + while self.state == 'connected': + p = None + try: + p = self.ws.recv() + if len(p) == 0 and not self.ws.connected: # pragma: no cover + # websocket client can return an empty string after close + raise websocket.WebSocketConnectionClosedException() + except websocket.WebSocketTimeoutException: + self.logger.warning( + 'Server has stopped communicating, aborting') + self.queue.put(None) + break + except websocket.WebSocketConnectionClosedException: + self.logger.warning( + 'WebSocket connection was closed, aborting') + self.queue.put(None) + break + except Exception as e: # pragma: no cover + if type(e) is OSError and e.errno == 9: + self.logger.info( + 'WebSocket connection is closing, aborting') + else: + self.logger.info( + 'Unexpected error receiving packet: "%s", aborting', + str(e)) + self.queue.put(None) + break + try: + pkt = packet.Packet(encoded_packet=p) + except Exception as e: # pragma: no cover + self.logger.info( + 'Unexpected error decoding packet: "%s", aborting', str(e)) + self.queue.put(None) + break + self._receive_packet(pkt) + + if self.write_loop_task: # pragma: no branch + self.logger.info('Waiting for write loop task to end') + self.write_loop_task.join() + if self.state == 'connected': + self._trigger_event('disconnect', self.reason.TRANSPORT_ERROR, + run_async=False) + try: + base_client.connected_clients.remove(self) + except ValueError: # pragma: no cover + pass + self._reset() + self.logger.info('Exiting read loop task') + + def _write_loop(self): + """This background task sends packages to the server as they are + pushed to the send queue. + """ + while self.state == 'connected': + # to simplify the timeout handling, use the maximum of the + # ping interval and ping timeout as timeout, with an extra 5 + # seconds grace period + timeout = max(self.ping_interval, self.ping_timeout) + 5 + packets = None + try: + packets = [self.queue.get(timeout=timeout)] + except self.queue_empty: + self.logger.error('packet queue is empty, aborting') + break + if packets == [None]: + self.queue.task_done() + packets = [] + else: + while True: + try: + packets.append(self.queue.get(block=False)) + except self.queue_empty: + break + if packets[-1] is None: + packets = packets[:-1] + self.queue.task_done() + break + if not packets: + # empty packet list returned -> connection closed + break + if self.current_transport == 'polling': + p = payload.Payload(packets=packets) + r = self._send_request( + 'POST', self.base_url, body=p.encode(), + headers={'Content-Type': 'text/plain'}, + timeout=self.request_timeout) + for pkt in packets: + self.queue.task_done() + if r is None or isinstance(r, str): + self.logger.warning( + r or 'Connection refused by the server, aborting') + break + if r.status_code < 200 or r.status_code >= 300: + self.logger.warning('Unexpected status code %s in server ' + 'response, aborting', r.status_code) + self.write_loop_task = None + break + else: + # websocket + try: + for pkt in packets: + encoded_packet = pkt.encode() + if pkt.binary: + self.ws.send_binary(encoded_packet) + else: + self.ws.send(encoded_packet) + self.queue.task_done() + except (websocket.WebSocketConnectionClosedException, + BrokenPipeError, OSError): + self.logger.warning( + 'WebSocket connection was closed, aborting') + break + self.logger.info('Exiting write loop task') diff --git a/venv/lib/python3.12/site-packages/engineio/exceptions.py b/venv/lib/python3.12/site-packages/engineio/exceptions.py new file mode 100644 index 0000000..fb0b3e0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/exceptions.py @@ -0,0 +1,22 @@ +class EngineIOError(Exception): + pass + + +class ContentTooLongError(EngineIOError): + pass + + +class UnknownPacketError(EngineIOError): + pass + + +class QueueEmpty(EngineIOError): + pass + + +class SocketIsClosedError(EngineIOError): + pass + + +class ConnectionError(EngineIOError): + pass diff --git a/venv/lib/python3.12/site-packages/engineio/json.py b/venv/lib/python3.12/site-packages/engineio/json.py new file mode 100644 index 0000000..b612556 --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/json.py @@ -0,0 +1,16 @@ +"""JSON-compatible module with sane defaults.""" + +from json import * # noqa: F401, F403 +from json import loads as original_loads + + +def _safe_int(s): + if len(s) > 100: + raise ValueError('Integer is too large') + return int(s) + + +def loads(*args, **kwargs): + if 'parse_int' not in kwargs: # pragma: no cover + kwargs['parse_int'] = _safe_int + return original_loads(*args, **kwargs) diff --git a/venv/lib/python3.12/site-packages/engineio/middleware.py b/venv/lib/python3.12/site-packages/engineio/middleware.py new file mode 100644 index 0000000..0e34fb0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/middleware.py @@ -0,0 +1,86 @@ +import os +from engineio.static_files import get_static_file + + +class WSGIApp: + """WSGI application middleware for Engine.IO. + + This middleware dispatches traffic to an Engine.IO application. It can + also serve a list of static files to the client, or forward unrelated + HTTP traffic to another WSGI application. + + :param engineio_app: The Engine.IO server. Must be an instance of the + ``engineio.Server`` class. + :param wsgi_app: The WSGI app that receives all other traffic. + :param static_files: A dictionary with static file mapping rules. See the + documentation for details on this argument. + :param engineio_path: The endpoint where the Engine.IO application should + be installed. The default value is appropriate for + most cases. + + Example usage:: + + import engineio + import eventlet + + eio = engineio.Server() + app = engineio.WSGIApp(eio, static_files={ + '/': {'content_type': 'text/html', 'filename': 'index.html'}, + '/index.html': {'content_type': 'text/html', + 'filename': 'index.html'}, + }) + eventlet.wsgi.server(eventlet.listen(('', 8000)), app) + """ + def __init__(self, engineio_app, wsgi_app=None, static_files=None, + engineio_path='engine.io'): + self.engineio_app = engineio_app + self.wsgi_app = wsgi_app + self.engineio_path = engineio_path + if not self.engineio_path.startswith('/'): + self.engineio_path = '/' + self.engineio_path + if not self.engineio_path.endswith('/'): + self.engineio_path += '/' + self.static_files = static_files or {} + + def __call__(self, environ, start_response): + if 'gunicorn.socket' in environ: + # gunicorn saves the socket under environ['gunicorn.socket'], while + # eventlet saves it under environ['eventlet.input']. Eventlet also + # stores the socket inside a wrapper class, while gunicon writes it + # directly into the environment. To give eventlet's WebSocket + # module access to this socket when running under gunicorn, here we + # copy the socket to the eventlet format. + class Input: + def __init__(self, socket): + self.socket = socket + + def get_socket(self): + return self.socket + + environ['eventlet.input'] = Input(environ['gunicorn.socket']) + path = environ['PATH_INFO'] + if path is not None and path.startswith(self.engineio_path): + return self.engineio_app.handle_request(environ, start_response) + else: + static_file = get_static_file(path, self.static_files) \ + if self.static_files else None + if static_file and os.path.exists(static_file['filename']): + start_response( + '200 OK', + [('Content-Type', static_file['content_type'])]) + with open(static_file['filename'], 'rb') as f: + return [f.read()] + elif self.wsgi_app is not None: + return self.wsgi_app(environ, start_response) + return self.not_found(start_response) + + def not_found(self, start_response): + start_response("404 Not Found", [('Content-Type', 'text/plain')]) + return [b'Not Found'] + + +class Middleware(WSGIApp): + """This class has been renamed to ``WSGIApp`` and is now deprecated.""" + def __init__(self, engineio_app, wsgi_app=None, + engineio_path='engine.io'): + super().__init__(engineio_app, wsgi_app, engineio_path=engineio_path) diff --git a/venv/lib/python3.12/site-packages/engineio/packet.py b/venv/lib/python3.12/site-packages/engineio/packet.py new file mode 100644 index 0000000..40bb6df --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/packet.py @@ -0,0 +1,82 @@ +import base64 +from engineio import json as _json + +(OPEN, CLOSE, PING, PONG, MESSAGE, UPGRADE, NOOP) = (0, 1, 2, 3, 4, 5, 6) +packet_names = ['OPEN', 'CLOSE', 'PING', 'PONG', 'MESSAGE', 'UPGRADE', 'NOOP'] + +binary_types = (bytes, bytearray) + + +class Packet: + """Engine.IO packet.""" + + json = _json + + def __init__(self, packet_type=NOOP, data=None, encoded_packet=None): + self.packet_type = packet_type + self.data = data + self.encode_cache = None + if isinstance(data, str): + self.binary = False + elif isinstance(data, binary_types): + self.binary = True + else: + self.binary = False + if self.binary and self.packet_type != MESSAGE: + raise ValueError('Binary packets can only be of type MESSAGE') + if encoded_packet is not None: + self.decode(encoded_packet) + + def encode(self, b64=False): + """Encode the packet for transmission. + + Note: as a performance optimization, subsequent calls to this method + will return a cached encoded packet, even if the data has changed. + """ + if self.encode_cache: + return self.encode_cache + if self.binary: + if b64: + encoded_packet = 'b' + base64.b64encode(self.data).decode( + 'utf-8') + else: + encoded_packet = self.data + else: + encoded_packet = str(self.packet_type) + if isinstance(self.data, str): + encoded_packet += self.data + elif isinstance(self.data, dict) or isinstance(self.data, list): + encoded_packet += self.json.dumps(self.data, + separators=(',', ':')) + elif self.data is not None: + encoded_packet += str(self.data) + self.encode_cache = encoded_packet + return encoded_packet + + def decode(self, encoded_packet): + """Decode a transmitted package.""" + self.binary = isinstance(encoded_packet, binary_types) + if not self.binary and len(encoded_packet) == 0: + raise ValueError('Invalid empty packet received') + b64 = not self.binary and encoded_packet[0] == 'b' + if b64: + self.binary = True + self.packet_type = MESSAGE + self.data = base64.b64decode(encoded_packet[1:]) + else: + if self.binary and not isinstance(encoded_packet, bytes): + encoded_packet = bytes(encoded_packet) + if self.binary: + self.packet_type = MESSAGE + self.data = encoded_packet + else: + self.packet_type = int(encoded_packet[0]) + try: + if encoded_packet[1].isnumeric(): + # do not allow integer payloads, see + # github.com/miguelgrinberg/python-engineio/issues/75 + # for background on this decision + raise ValueError + self.data = self.json.loads(encoded_packet[1:]) + except (ValueError, IndexError): + self.data = encoded_packet[1:] diff --git a/venv/lib/python3.12/site-packages/engineio/payload.py b/venv/lib/python3.12/site-packages/engineio/payload.py new file mode 100644 index 0000000..775241b --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/payload.py @@ -0,0 +1,46 @@ +import urllib + +from . import packet + + +class Payload: + """Engine.IO payload.""" + max_decode_packets = 16 + + def __init__(self, packets=None, encoded_payload=None): + self.packets = packets or [] + if encoded_payload is not None: + self.decode(encoded_payload) + + def encode(self, jsonp_index=None): + """Encode the payload for transmission.""" + encoded_payload = '' + for pkt in self.packets: + if encoded_payload: + encoded_payload += '\x1e' + encoded_payload += pkt.encode(b64=True) + if jsonp_index is not None: + encoded_payload = '___eio[' + \ + str(jsonp_index) + \ + ']("' + \ + encoded_payload.replace('"', '\\"') + \ + '");' + return encoded_payload + + def decode(self, encoded_payload): + """Decode a transmitted payload.""" + self.packets = [] + + if len(encoded_payload) == 0: + return + + # JSONP POST payload starts with 'd=' + if encoded_payload.startswith('d='): + encoded_payload = urllib.parse.parse_qs( + encoded_payload)['d'][0] + + encoded_packets = encoded_payload.split('\x1e') + if len(encoded_packets) > self.max_decode_packets: + raise ValueError('Too many packets in payload') + self.packets = [packet.Packet(encoded_packet=encoded_packet) + for encoded_packet in encoded_packets] diff --git a/venv/lib/python3.12/site-packages/engineio/server.py b/venv/lib/python3.12/site-packages/engineio/server.py new file mode 100644 index 0000000..59f690c --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/server.py @@ -0,0 +1,503 @@ +import logging +import urllib + +from . import base_server +from . import exceptions +from . import packet +from . import socket + +default_logger = logging.getLogger('engineio.server') + + +class Server(base_server.BaseServer): + """An Engine.IO server. + + This class implements a fully compliant Engine.IO web server with support + for websocket and long-polling transports. + + :param async_mode: The asynchronous model to use. See the Deployment + section in the documentation for a description of the + available options. Valid async modes are "threading", + "eventlet", "gevent" and "gevent_uwsgi". If this + argument is not given, "eventlet" is tried first, then + "gevent_uwsgi", then "gevent", and finally "threading". + The first async mode that has all its dependencies + installed is the one that is chosen. + :param ping_interval: The interval in seconds at which the server pings + the client. The default is 25 seconds. For advanced + control, a two element tuple can be given, where + the first number is the ping interval and the second + is a grace period added by the server. + :param ping_timeout: The time in seconds that the client waits for the + server to respond before disconnecting. The default + is 20 seconds. + :param max_http_buffer_size: The maximum size that is accepted for incoming + messages. The default is 1,000,000 bytes. In + spite of its name, the value set in this + argument is enforced for HTTP long-polling and + WebSocket connections. + :param allow_upgrades: Whether to allow transport upgrades or not. The + default is ``True``. + :param http_compression: Whether to compress packages when using the + polling transport. The default is ``True``. + :param compression_threshold: Only compress messages when their byte size + is greater than this value. The default is + 1024 bytes. + :param cookie: If set to a string, it is the name of the HTTP cookie the + server sends back tot he client containing the client + session id. If set to a dictionary, the ``'name'`` key + contains the cookie name and other keys define cookie + attributes, where the value of each attribute can be a + string, a callable with no arguments, or a boolean. If set + to ``None`` (the default), a cookie is not sent to the + client. + :param cors_allowed_origins: Origin or list of origins that are allowed to + connect to this server. Only the same origin + is allowed by default. Set this argument to + ``'*'`` or ``['*']`` to allow all origins, or + to ``[]`` to disable CORS handling. + :param cors_credentials: Whether credentials (cookies, authentication) are + allowed in requests to this server. The default + is ``True``. + :param logger: To enable logging set to ``True`` or pass a logger object to + use. To disable logging set to ``False``. The default is + ``False``. Note that fatal errors are logged even when + ``logger`` is ``False``. + :param json: An alternative json module to use for encoding and decoding + packets. Custom json modules must have ``dumps`` and ``loads`` + functions that are compatible with the standard library + versions. + :param async_handlers: If set to ``True``, run message event handlers in + non-blocking threads. To run handlers synchronously, + set to ``False``. The default is ``True``. + :param monitor_clients: If set to ``True``, a background task will ensure + inactive clients are closed. Set to ``False`` to + disable the monitoring task (not recommended). The + default is ``True``. + :param transports: The list of allowed transports. Valid transports + are ``'polling'`` and ``'websocket'``. Defaults to + ``['polling', 'websocket']``. + :param kwargs: Reserved for future extensions, any additional parameters + given as keyword arguments will be silently ignored. + """ + def send(self, sid, data): + """Send a message to a client. + + :param sid: The session id of the recipient client. + :param data: The data to send to the client. Data can be of type + ``str``, ``bytes``, ``list`` or ``dict``. If a ``list`` + or ``dict``, the data will be serialized as JSON. + """ + self.send_packet(sid, packet.Packet(packet.MESSAGE, data=data)) + + def send_packet(self, sid, pkt): + """Send a raw packet to a client. + + :param sid: The session id of the recipient client. + :param pkt: The packet to send to the client. + """ + try: + socket = self._get_socket(sid) + except KeyError: + # the socket is not available + self.logger.warning('Cannot send to sid %s', sid) + return + socket.send(pkt) + + def get_session(self, sid): + """Return the user session for a client. + + :param sid: The session id of the client. + + The return value is a dictionary. Modifications made to this + dictionary are not guaranteed to be preserved unless + ``save_session()`` is called, or when the ``session`` context manager + is used. + """ + socket = self._get_socket(sid) + return socket.session + + def save_session(self, sid, session): + """Store the user session for a client. + + :param sid: The session id of the client. + :param session: The session dictionary. + """ + socket = self._get_socket(sid) + socket.session = session + + def session(self, sid): + """Return the user session for a client with context manager syntax. + + :param sid: The session id of the client. + + This is a context manager that returns the user session dictionary for + the client. Any changes that are made to this dictionary inside the + context manager block are saved back to the session. Example usage:: + + @eio.on('connect') + def on_connect(sid, environ): + username = authenticate_user(environ) + if not username: + return False + with eio.session(sid) as session: + session['username'] = username + + @eio.on('message') + def on_message(sid, msg): + with eio.session(sid) as session: + print('received message from ', session['username']) + """ + class _session_context_manager: + def __init__(self, server, sid): + self.server = server + self.sid = sid + self.session = None + + def __enter__(self): + self.session = self.server.get_session(sid) + return self.session + + def __exit__(self, *args): + self.server.save_session(sid, self.session) + + return _session_context_manager(self, sid) + + def disconnect(self, sid=None): + """Disconnect a client. + + :param sid: The session id of the client to close. If this parameter + is not given, then all clients are closed. + """ + if sid is not None: + try: + socket = self._get_socket(sid) + except KeyError: # pragma: no cover + # the socket was already closed or gone + pass + else: + socket.close(reason=self.reason.SERVER_DISCONNECT) + if sid in self.sockets: # pragma: no cover + del self.sockets[sid] + else: + for client in self.sockets.copy().values(): + client.close(reason=self.reason.SERVER_DISCONNECT) + self.sockets = {} + + def handle_request(self, environ, start_response): + """Handle an HTTP request from the client. + + This is the entry point of the Engine.IO application, using the same + interface as a WSGI application. For the typical usage, this function + is invoked by the :class:`Middleware` instance, but it can be invoked + directly when the middleware is not used. + + :param environ: The WSGI environment. + :param start_response: The WSGI ``start_response`` function. + + This function returns the HTTP response body to deliver to the client + as a byte sequence. + """ + if self.cors_allowed_origins != []: + # Validate the origin header if present + # This is important for WebSocket more than for HTTP, since + # browsers only apply CORS controls to HTTP. + origin = environ.get('HTTP_ORIGIN') + if origin: + allowed_origins = self._cors_allowed_origins(environ) + if allowed_origins is not None and origin not in \ + allowed_origins: + self._log_error_once( + origin + ' is not an accepted origin.', 'bad-origin') + r = self._bad_request('Not an accepted origin.') + start_response(r['status'], r['headers']) + return [r['response']] + + method = environ['REQUEST_METHOD'] + query = urllib.parse.parse_qs(environ.get('QUERY_STRING', '')) + jsonp = False + jsonp_index = None + + # make sure the client uses an allowed transport + transport = query.get('transport', ['polling'])[0] + if transport not in self.transports: + self._log_error_once('Invalid transport', 'bad-transport') + r = self._bad_request('Invalid transport') + start_response(r['status'], r['headers']) + return [r['response']] + + # make sure the client speaks a compatible Engine.IO version + sid = query['sid'][0] if 'sid' in query else None + if sid is None and query.get('EIO') != ['4']: + self._log_error_once( + 'The client is using an unsupported version of the Socket.IO ' + 'or Engine.IO protocols', 'bad-version') + r = self._bad_request( + 'The client is using an unsupported version of the Socket.IO ' + 'or Engine.IO protocols') + start_response(r['status'], r['headers']) + return [r['response']] + + if 'j' in query: + jsonp = True + try: + jsonp_index = int(query['j'][0]) + except (ValueError, KeyError, IndexError): + # Invalid JSONP index number + pass + + if jsonp and jsonp_index is None: + self._log_error_once('Invalid JSONP index number', + 'bad-jsonp-index') + r = self._bad_request('Invalid JSONP index number') + elif method == 'GET': + upgrade_header = environ.get('HTTP_UPGRADE').lower() \ + if 'HTTP_UPGRADE' in environ else None + if sid is None: + # transport must be one of 'polling' or 'websocket'. + # if 'websocket', the HTTP_UPGRADE header must match. + if transport == 'polling' \ + or transport == upgrade_header == 'websocket': + r = self._handle_connect(environ, start_response, + transport, jsonp_index) + else: + self._log_error_once('Invalid websocket upgrade', + 'bad-upgrade') + r = self._bad_request('Invalid websocket upgrade') + else: + if sid not in self.sockets: + self._log_error_once(f'Invalid session {sid}', 'bad-sid') + r = self._bad_request(f'Invalid session {sid}') + else: + try: + socket = self._get_socket(sid) + except KeyError as e: # pragma: no cover + self._log_error_once(f'{e} {sid}', 'bad-sid') + r = self._bad_request(f'{e} {sid}') + else: + if self.transport(sid) != transport and \ + transport != upgrade_header: + self._log_error_once( + f'Invalid transport for session {sid}', + 'bad-transport') + r = self._bad_request('Invalid transport') + else: + try: + packets = socket.handle_get_request( + environ, start_response) + if isinstance(packets, list): + r = self._ok(packets, + jsonp_index=jsonp_index) + else: + r = packets + except exceptions.EngineIOError: + if sid in self.sockets: # pragma: no cover + self.disconnect(sid) + r = self._bad_request() + if sid in self.sockets and \ + self.sockets[sid].closed: + del self.sockets[sid] + elif method == 'POST': + if sid is None or sid not in self.sockets: + self._log_error_once(f'Invalid session {sid}', 'bad-sid') + r = self._bad_request(f'Invalid session {sid}') + else: + socket = self._get_socket(sid) + try: + socket.handle_post_request(environ) + r = self._ok(jsonp_index=jsonp_index) + except exceptions.EngineIOError: + if sid in self.sockets: # pragma: no cover + self.disconnect(sid) + r = self._bad_request() + except: # pragma: no cover + # for any other unexpected errors, we log the error + # and keep going + self.logger.exception('post request handler error') + r = self._ok(jsonp_index=jsonp_index) + elif method == 'OPTIONS': + r = self._ok() + else: + self.logger.warning('Method %s not supported', method) + r = self._method_not_found() + + if not isinstance(r, dict): + return r + if self.http_compression and \ + len(r['response']) >= self.compression_threshold: + encodings = [e.split(';')[0].strip() for e in + environ.get('HTTP_ACCEPT_ENCODING', '').split(',')] + for encoding in encodings: + if encoding in self.compression_methods: + r['response'] = \ + getattr(self, '_' + encoding)(r['response']) + r['headers'] += [('Content-Encoding', encoding)] + break + cors_headers = self._cors_headers(environ) + start_response(r['status'], r['headers'] + cors_headers) + return [r['response']] + + def shutdown(self): + """Stop Socket.IO background tasks. + + This method stops background activity initiated by the Socket.IO + server. It must be called before shutting down the web server. + """ + self.logger.info('Socket.IO is shutting down') + if self.service_task_event: # pragma: no cover + self.service_task_event.set() + self.service_task_handle.join() + self.service_task_handle = None + + def start_background_task(self, target, *args, **kwargs): + """Start a background task using the appropriate async model. + + This is a utility function that applications can use to start a + background task using the method that is compatible with the + selected async mode. + + :param target: the target function to execute. + :param args: arguments to pass to the function. + :param kwargs: keyword arguments to pass to the function. + + This function returns an object that represents the background task, + on which the ``join()`` methond can be invoked to wait for the task to + complete. + """ + th = self._async['thread'](target=target, args=args, kwargs=kwargs) + th.start() + return th # pragma: no cover + + def sleep(self, seconds=0): + """Sleep for the requested amount of time using the appropriate async + model. + + This is a utility function that applications can use to put a task to + sleep without having to worry about using the correct call for the + selected async mode. + """ + return self._async['sleep'](seconds) + + def _handle_connect(self, environ, start_response, transport, + jsonp_index=None): + """Handle a client connection request.""" + if self.start_service_task: + # start the service task to monitor connected clients + self.start_service_task = False + self.service_task_handle = self.start_background_task( + self._service_task) + + sid = self.generate_id() + s = socket.Socket(self, sid) + self.sockets[sid] = s + + pkt = packet.Packet(packet.OPEN, { + 'sid': sid, + 'upgrades': self._upgrades(sid, transport), + 'pingTimeout': int(self.ping_timeout * 1000), + 'pingInterval': int( + self.ping_interval + self.ping_interval_grace_period) * 1000, + 'maxPayload': self.max_http_buffer_size, + }) + s.send(pkt) + s.schedule_ping() + + # NOTE: some sections below are marked as "no cover" to workaround + # what seems to be a bug in the coverage package. All the lines below + # are covered by tests, but some are not reported as such for some + # reason + ret = self._trigger_event('connect', sid, environ, run_async=False) + if ret is not None and ret is not True: # pragma: no cover + del self.sockets[sid] + self.logger.warning('Application rejected connection') + return self._unauthorized(ret or None) + + if transport == 'websocket': # pragma: no cover + ret = s.handle_get_request(environ, start_response) + if s.closed and sid in self.sockets: + # websocket connection ended, so we are done + del self.sockets[sid] + return ret + else: # pragma: no cover + s.connected = True + headers = None + if self.cookie: + if isinstance(self.cookie, dict): + headers = [( + 'Set-Cookie', + self._generate_sid_cookie(sid, self.cookie) + )] + else: + headers = [( + 'Set-Cookie', + self._generate_sid_cookie(sid, { + 'name': self.cookie, 'path': '/', 'SameSite': 'Lax' + }) + )] + try: + return self._ok(s.poll(), headers=headers, + jsonp_index=jsonp_index) + except exceptions.QueueEmpty: + return self._bad_request() + + def _trigger_event(self, event, *args, **kwargs): + """Invoke an event handler.""" + run_async = kwargs.pop('run_async', False) + if event in self.handlers: + def run_handler(): + try: + try: + return self.handlers[event](*args) + except TypeError: + if event == 'disconnect' and \ + len(args) == 2: # pragma: no branch + # legacy disconnect events do not have a reason + # argument + return self.handlers[event](args[0]) + else: # pragma: no cover + raise + except: + self.logger.exception(event + ' handler error') + if event == 'connect': + # if connect handler raised error we reject the + # connection + return False + + if run_async: + return self.start_background_task(run_handler) + else: + return run_handler() + + def _service_task(self): # pragma: no cover + """Monitor connected clients and clean up those that time out.""" + self.service_task_event = self.create_event() + while not self.service_task_event.is_set(): + if len(self.sockets) == 0: + # nothing to do + if self.service_task_event.wait(timeout=self.ping_timeout): + break + continue + + # go through the entire client list in a ping interval cycle + sleep_interval = float(self.ping_timeout) / len(self.sockets) + + try: + # iterate over the current clients + for s in self.sockets.copy().values(): + if s.closed: + try: + del self.sockets[s.sid] + except KeyError: + # the socket could have also been removed by + # the _get_socket() method from another thread + pass + elif not s.closing: + s.check_ping_timeout() + if self.service_task_event.wait(timeout=sleep_interval): + raise KeyboardInterrupt() + except (SystemExit, KeyboardInterrupt): + self.logger.info('service task canceled') + break + except: + # an unexpected exception has occurred, log it and continue + self.logger.exception('service task exception') diff --git a/venv/lib/python3.12/site-packages/engineio/socket.py b/venv/lib/python3.12/site-packages/engineio/socket.py new file mode 100644 index 0000000..26bb94b --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/socket.py @@ -0,0 +1,256 @@ +import sys +import time + +from . import base_socket +from . import exceptions +from . import packet +from . import payload + + +class Socket(base_socket.BaseSocket): + """An Engine.IO socket.""" + def poll(self): + """Wait for packets to send to the client.""" + queue_empty = self.server.get_queue_empty_exception() + try: + packets = [self.queue.get( + timeout=self.server.ping_interval + self.server.ping_timeout)] + self.queue.task_done() + except queue_empty: + raise exceptions.QueueEmpty() + if packets == [None]: + return [] + while True: + try: + pkt = self.queue.get(block=False) + self.queue.task_done() + if pkt is None: + self.queue.put(None) + break + packets.append(pkt) + except queue_empty: + break + return packets + + def receive(self, pkt): + """Receive packet from the client.""" + packet_name = packet.packet_names[pkt.packet_type] \ + if pkt.packet_type < len(packet.packet_names) else 'UNKNOWN' + self.server.logger.info('%s: Received packet %s data %s', + self.sid, packet_name, + pkt.data if not isinstance(pkt.data, bytes) + else '') + if pkt.packet_type == packet.PONG: + self.schedule_ping() + elif pkt.packet_type == packet.MESSAGE: + self.server._trigger_event('message', self.sid, pkt.data, + run_async=self.server.async_handlers) + elif pkt.packet_type == packet.UPGRADE: + self.send(packet.Packet(packet.NOOP)) + elif pkt.packet_type == packet.CLOSE: + self.close(wait=False, abort=True, + reason=self.server.reason.CLIENT_DISCONNECT) + else: + raise exceptions.UnknownPacketError() + + def check_ping_timeout(self): + """Make sure the client is still responding to pings.""" + if self.closed: + raise exceptions.SocketIsClosedError() + if self.last_ping and \ + time.time() - self.last_ping > self.server.ping_timeout: + self.server.logger.info('%s: Client is gone, closing socket', + self.sid) + # Passing abort=False here will cause close() to write a + # CLOSE packet. This has the effect of updating half-open sockets + # to their correct state of disconnected + self.close(wait=False, abort=False, + reason=self.server.reason.PING_TIMEOUT) + return False + return True + + def send(self, pkt): + """Send a packet to the client.""" + if not self.check_ping_timeout(): + return + else: + self.queue.put(pkt) + self.server.logger.info('%s: Sending packet %s data %s', + self.sid, packet.packet_names[pkt.packet_type], + pkt.data if not isinstance(pkt.data, bytes) + else '') + + def handle_get_request(self, environ, start_response): + """Handle a long-polling GET request from the client.""" + connections = [ + s.strip() + for s in environ.get('HTTP_CONNECTION', '').lower().split(',')] + transport = environ.get('HTTP_UPGRADE', '').lower() + if 'upgrade' in connections and transport in self.upgrade_protocols: + self.server.logger.info('%s: Received request to upgrade to %s', + self.sid, transport) + return getattr(self, '_upgrade_' + transport)(environ, + start_response) + if self.upgrading or self.upgraded: + # we are upgrading to WebSocket, do not return any more packets + # through the polling endpoint + return [packet.Packet(packet.NOOP)] + try: + packets = self.poll() + except exceptions.QueueEmpty: + exc = sys.exc_info() + self.close(wait=False, reason=self.server.reason.TRANSPORT_ERROR) + raise exc[1].with_traceback(exc[2]) + return packets + + def handle_post_request(self, environ): + """Handle a long-polling POST request from the client.""" + length = int(environ.get('CONTENT_LENGTH', '0')) + if length > self.server.max_http_buffer_size: + raise exceptions.ContentTooLongError() + else: + body = environ['wsgi.input'].read(length).decode('utf-8') + p = payload.Payload(encoded_payload=body) + for pkt in p.packets: + self.receive(pkt) + + def close(self, wait=True, abort=False, reason=None): + """Close the socket connection.""" + if not self.closed and not self.closing: + self.closing = True + self.server._trigger_event( + 'disconnect', self.sid, + reason or self.server.reason.SERVER_DISCONNECT, + run_async=False) + if not abort: + self.send(packet.Packet(packet.CLOSE)) + self.closed = True + self.queue.put(None) + if wait: + self.queue.join() + + def schedule_ping(self): + self.server.start_background_task(self._send_ping) + + def _send_ping(self): + self.last_ping = None + self.server.sleep(self.server.ping_interval) + if not self.closing and not self.closed: + self.last_ping = time.time() + self.send(packet.Packet(packet.PING)) + + def _upgrade_websocket(self, environ, start_response): + """Upgrade the connection from polling to websocket.""" + if self.upgraded: + raise OSError('Socket has been upgraded already') + if self.server._async['websocket'] is None: + # the selected async mode does not support websocket + return self.server._bad_request() + ws = self.server._async['websocket']( + self._websocket_handler, self.server) + return ws(environ, start_response) + + def _websocket_handler(self, ws): + """Engine.IO handler for websocket transport.""" + def websocket_wait(): + data = ws.wait() + if data and len(data) > self.server.max_http_buffer_size: + raise ValueError('packet is too large') + return data + + # try to set a socket timeout matching the configured ping interval + # and timeout + for attr in ['_sock', 'socket']: # pragma: no cover + if hasattr(ws, attr) and hasattr(getattr(ws, attr), 'settimeout'): + getattr(ws, attr).settimeout( + self.server.ping_interval + self.server.ping_timeout) + + if self.connected: + # the socket was already connected, so this is an upgrade + self.upgrading = True # hold packet sends during the upgrade + + pkt = websocket_wait() + decoded_pkt = packet.Packet(encoded_packet=pkt) + if decoded_pkt.packet_type != packet.PING or \ + decoded_pkt.data != 'probe': + self.server.logger.info( + '%s: Failed websocket upgrade, no PING packet', self.sid) + self.upgrading = False + return [] + ws.send(packet.Packet(packet.PONG, data='probe').encode()) + self.queue.put(packet.Packet(packet.NOOP)) # end poll + + pkt = websocket_wait() + decoded_pkt = packet.Packet(encoded_packet=pkt) + if decoded_pkt.packet_type != packet.UPGRADE: + self.upgraded = False + self.server.logger.info( + ('%s: Failed websocket upgrade, expected UPGRADE packet, ' + 'received %s instead.'), + self.sid, pkt) + self.upgrading = False + return [] + self.upgraded = True + self.upgrading = False + else: + self.connected = True + self.upgraded = True + + # start separate writer thread + def writer(): + while True: + packets = None + try: + packets = self.poll() + except exceptions.QueueEmpty: + break + if not packets: + # empty packet list returned -> connection closed + break + try: + for pkt in packets: + ws.send(pkt.encode()) + except: + break + ws.close() + + writer_task = self.server.start_background_task(writer) + + self.server.logger.info( + '%s: Upgrade to websocket successful', self.sid) + + while True: + p = None + try: + p = websocket_wait() + except Exception as e: + # if the socket is already closed, we can assume this is a + # downstream error of that + if not self.closed: # pragma: no cover + self.server.logger.info( + '%s: Unexpected error "%s", closing connection', + self.sid, str(e)) + break + if p is None: + # connection closed by client + break + pkt = packet.Packet(encoded_packet=p) + try: + self.receive(pkt) + except exceptions.UnknownPacketError: # pragma: no cover + pass + except exceptions.SocketIsClosedError: # pragma: no cover + self.server.logger.info('Receive error -- socket is closed') + break + except: # pragma: no cover + # if we get an unexpected exception we log the error and exit + # the connection properly + self.server.logger.exception('Unknown receive error') + break + + self.queue.put(None) # unlock the writer task so that it can exit + writer_task.join() + self.close(wait=False, abort=True, + reason=self.server.reason.TRANSPORT_CLOSE) + + return [] diff --git a/venv/lib/python3.12/site-packages/engineio/static_files.py b/venv/lib/python3.12/site-packages/engineio/static_files.py new file mode 100644 index 0000000..77c8915 --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/static_files.py @@ -0,0 +1,60 @@ +content_types = { + 'css': 'text/css', + 'gif': 'image/gif', + 'html': 'text/html', + 'jpg': 'image/jpeg', + 'js': 'application/javascript', + 'json': 'application/json', + 'png': 'image/png', + 'txt': 'text/plain', +} + + +def get_static_file(path, static_files): + """Return the local filename and content type for the requested static + file URL. + + :param path: the path portion of the requested URL. + :param static_files: a static file configuration dictionary. + + This function returns a dictionary with two keys, "filename" and + "content_type". If the requested URL does not match any static file, the + return value is None. + """ + extra_path = '' + if path in static_files: + f = static_files[path] + else: + f = None + while path != '': + path, last = path.rsplit('/', 1) + extra_path = '/' + last + extra_path + if path in static_files: + f = static_files[path] + break + elif path + '/' in static_files: + f = static_files[path + '/'] + break + if f: + if isinstance(f, str): + f = {'filename': f} + else: + f = f.copy() # in case it is mutated below + if f['filename'].endswith('/') and extra_path.startswith('/'): + extra_path = extra_path[1:] + f['filename'] += extra_path + if f['filename'].endswith('/'): + if '' in static_files: + if isinstance(static_files[''], str): + f['filename'] += static_files[''] + else: + f['filename'] += static_files['']['filename'] + if 'content_type' in static_files['']: + f['content_type'] = static_files['']['content_type'] + else: + f['filename'] += 'index.html' + if 'content_type' not in f: + ext = f['filename'].rsplit('.')[-1] + f['content_type'] = content_types.get( + ext, 'application/octet-stream') + return f diff --git a/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/AUTHORS b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/AUTHORS new file mode 100644 index 0000000..1190dce --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/AUTHORS @@ -0,0 +1,174 @@ +Maintainer (i.e., Who To Hassle If You Find Bugs) +------------------------------------------------- +Jakub Stasiak +Nat Goodspeed + +The current maintainer(s) are volunteers with unrelated jobs. +We can only pay sporadic attention to responding to your issue and pull request submissions. +Your patience is greatly appreciated! + +Original Authors +---------------- +* Bob Ippolito +* Donovan Preston + +Contributors +------------ +* AG Projects +* Chris AtLee +* R\. Tyler Ballance +* Denis Bilenko +* Mike Barton +* Patrick Carlisle +* Ben Ford +* Andrew Godwin +* Brantley Harris +* Gregory Holt +* Joe Malicki +* Chet Murthy +* Eugene Oden +* radix +* Scott Robinson +* Tavis Rudd +* Sergey Shepelev +* Chuck Thier +* Nick V +* Daniele Varrazzo +* Ryan Williams +* Geoff Salmon +* Edward George +* Floris Bruynooghe +* Paul Oppenheim +* Jakub Stasiak +* Aldona Majorek +* Victor Sergeyev +* David Szotten +* Victor Stinner +* Samuel Merritt +* Eric Urban + +Linden Lab Contributors +----------------------- +* John Beisley +* Tess Chu +* Nat Goodspeed +* Dave Kaprielian +* Kartic Krishnamurthy +* Bryan O'Sullivan +* Kent Quirk +* Ryan Williams + +Thanks To +--------- +* AdamKG, giving the hint that invalid argument errors were introduced post-0.9.0 +* Luke Tucker, bug report regarding wsgi + webob +* Taso Du Val, reproing an exception squelching bug, saving children's lives ;-) +* Luci Stanescu, for reporting twisted hub bug +* Marcus Cavanaugh, for test case code that has been incredibly useful in tracking down bugs +* Brian Brunswick, for many helpful questions and suggestions on the mailing list +* Cesar Alaniz, for uncovering bugs of great import +* the grugq, for contributing patches, suggestions, and use cases +* Ralf Schmitt, for wsgi/webob incompatibility bug report and suggested fix +* Benoit Chesneau, bug report on green.os and patch to fix it +* Slant, better iterator implementation in tpool +* Ambroff, nice pygtk hub example +* Michael Carter, websocket patch to improve location handling +* Marcin Bachry, nice repro of a bug and good diagnosis leading to the fix +* David Ziegler, reporting issue #53 +* Favo Yang, twisted hub patch +* Schmir, patch that fixes readline method with chunked encoding in wsgi.py, advice on patcher +* Slide, for open-sourcing gogreen +* Holger Krekel, websocket example small fix +* mikepk, debugging MySQLdb/tpool issues +* Malcolm Cleaton, patch for Event exception handling +* Alexey Borzenkov, for finding and fixing issues with Windows error detection (#66, #69), reducing dependencies in zeromq hub (#71) +* Anonymous, finding and fixing error in websocket chat example (#70) +* Edward George, finding and fixing an issue in the [e]poll hubs (#74), and in convenience (#86) +* Ruijun Luo, figuring out incorrect openssl import for wrap_ssl (#73) +* rfk, patch to get green zmq to respect noblock flag. +* Soren Hansen, finding and fixing issue in subprocess (#77) +* Stefano Rivera, making tests pass in absence of postgres (#78) +* Joshua Kwan, fixing busy-wait in eventlet.green.ssl. +* Nick Vatamaniuc, Windows SO_REUSEADDR patch (#83) +* Clay Gerrard, wsgi handle socket closed by client (#95) +* Eric Windisch, zmq getsockopt(EVENTS) wake correct threads (pull request 22) +* Raymond Lu, fixing busy-wait in eventlet.green.ssl.socket.sendall() +* Thomas Grainger, webcrawler example small fix, "requests" library import bug report, Travis integration +* Peter Portante, save syscalls in socket.dup(), environ[REMOTE_PORT] in wsgi +* Peter Skirko, fixing socket.settimeout(0) bug +* Derk Tegeler, Pre-cache proxied GreenSocket methods (Bitbucket #136) +* David Malcolm, optional "timeout" argument to the subprocess module (Bitbucket #89) +* David Goetz, wsgi: Allow minimum_chunk_size to be overriden on a per request basis +* Dmitry Orlov, websocket: accept Upgrade: websocket (lowercase) +* Zhang Hua, profile: accumulate results between runs (Bitbucket #162) +* Astrum Kuo, python3 compatibility fixes; greenthread.unlink() method +* Davanum Srinivas, Python3 compatibility fixes +* Dmitriy Kruglyak, PyPy 2.3 compatibility fix +* Jan Grant, Michael Kerrin, second simultaneous read (GH-94) +* Simon Jagoe, Python3 octal literal fix +* Tushar Gohad, wsgi: Support optional headers w/ "100 Continue" responses +* raylu, fixing operator precedence bug in eventlet.wsgi +* Christoph Gysin, PEP 8 conformance +* Andrey Gubarev +* Corey Wright +* Deva +* Johannes Erdfelt +* Kevin +* QthCN +* Steven Hardy +* Stuart McLaren +* Tomaz Muraus +* ChangBo Guo(gcb), fixing typos in the documentation (GH-194) +* Marc Abramowitz, fixing the README so it renders correctly on PyPI (GH-183) +* Shaun Stanworth, equal chance to acquire semaphore from different greenthreads (GH-136) +* Lior Neudorfer, Make sure SSL retries are done using the exact same data buffer +* Sean Dague, wsgi: Provide python logging compatibility +* Tim Simmons, Use _socket_nodns and select in dnspython support +* Antonio Cuni, fix fd double close on PyPy +* Seyeong Kim +* Ihar Hrachyshka +* Janusz Harkot +* Fukuchi Daisuke +* Ramakrishnan G +* ashutosh-mishra +* Azhar Hussain +* Josh VanderLinden +* Levente Polyak +* Phus Lu +* Collin Stocks, fixing eventlet.green.urllib2.urlopen() so it accepts cafile, capath, or cadefault arguments +* Alexis Lee +* Steven Erenst +* Piët Delport +* Alex Villacís Lasso +* Yashwardhan Singh +* Tim Burke +* Ondřej Nový +* Jarrod Johnson +* Whitney Young +* Matthew D. Pagel +* Matt Yule-Bennett +* Artur Stawiarski +* Tal Wrii +* Roman Podoliaka +* Gevorg Davoian +* Ondřej Kobližek +* Yuichi Bando +* Feng +* Aayush Kasurde +* Linbing +* Geoffrey Thomas +* Costas Christofi, adding permessage-deflate weboscket extension support +* Peter Kovary, adding permessage-deflate weboscket extension support +* Konstantin Enchant +* James Page +* Stefan Nica +* Haikel Guemar +* Miguel Grinberg +* Chris Kerr +* Anthony Sottile +* Quan Tian +* orishoshan +* Matt Bennett +* Ralf Haferkamp +* Jake Tesler +* Aayush Kasurde diff --git a/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/LICENSE b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/LICENSE new file mode 100644 index 0000000..2ddd0d9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/LICENSE @@ -0,0 +1,23 @@ +Unless otherwise noted, the files in Eventlet are under the following MIT license: + +Copyright (c) 2005-2006, Bob Ippolito +Copyright (c) 2007-2010, Linden Research, Inc. +Copyright (c) 2008-2010, Eventlet Contributors (see AUTHORS) + +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/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/METADATA b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/METADATA new file mode 100644 index 0000000..3a1e185 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/METADATA @@ -0,0 +1,112 @@ +Metadata-Version: 2.1 +Name: eventlet +Version: 0.33.3 +Summary: Highly concurrent networking library +Home-page: http://eventlet.net +Author: Linden Lab +Author-email: eventletdev@lists.secondlife.com +Project-URL: Source, https://github.com/eventlet/eventlet +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python +Classifier: Topic :: Internet +Classifier: Topic :: Software Development :: Libraries :: Python Modules +License-File: LICENSE +License-File: AUTHORS +Requires-Dist: dnspython (>=1.15.0) +Requires-Dist: greenlet (>=0.3) +Requires-Dist: six (>=1.10.0) +Requires-Dist: monotonic (>=1.4) ; python_version < "3.5" + +Eventlet is a concurrent networking library for Python that allows you to change how you run your code, not how you write it. + +It uses epoll or libevent for highly scalable non-blocking I/O. Coroutines ensure that the developer uses a blocking style of programming that is similar to threading, but provide the benefits of non-blocking I/O. The event dispatch is implicit, which means you can easily use Eventlet from the Python interpreter, or as a small part of a larger application. + +It's easy to get started using Eventlet, and easy to convert existing +applications to use it. Start off by looking at the `examples`_, +`common design patterns`_, and the list of `basic API primitives`_. + +.. _examples: http://eventlet.net/doc/examples.html +.. _common design patterns: http://eventlet.net/doc/design_patterns.html +.. _basic API primitives: http://eventlet.net/doc/basic_usage.html + + +Quick Example +=============== + +Here's something you can try right on the command line:: + + % python3 + >>> import eventlet + >>> from eventlet.green.urllib.request import urlopen + >>> gt = eventlet.spawn(urlopen, 'http://eventlet.net') + >>> gt2 = eventlet.spawn(urlopen, 'http://secondlife.com') + >>> gt2.wait() + >>> gt.wait() + + +Getting Eventlet +================== + +The easiest way to get Eventlet is to use pip:: + + pip install -U eventlet + +To install latest development version once:: + + pip install -U https://github.com/eventlet/eventlet/archive/master.zip + + +Building the Docs Locally +========================= + +To build a complete set of HTML documentation, you must have Sphinx, which can be found at http://sphinx.pocoo.org/ (or installed with `pip install Sphinx`):: + + cd doc + make html + +The built html files can be found in doc/_build/html afterward. + + +Twisted +======= + +Eventlet had Twisted hub in the past, but community interest to this integration has dropped over time, +now it is not supported, so with apologies for any inconvenience we discontinue Twisted integration. + +If you have a project that uses Eventlet with Twisted, your options are: + +* use last working release eventlet==0.14 +* start a new project with only Twisted hub code, identify and fix problems. As of eventlet 0.13, `EVENTLET_HUB` environment variable can point to external modules. +* fork Eventlet, revert Twisted removal, identify and fix problems. This work may be merged back into main project. + +Apologies for any inconvenience. + +Supported Python versions +========================= + +Currently CPython 2.7 and 3.4+ are supported, but **2.7 and 3.4 support is deprecated and will be removed in the future**, only CPython 3.5+ support will remain. + +Flair +===== + +.. image:: https://img.shields.io/pypi/v/eventlet + :target: https://pypi.org/project/eventlet/ + +.. image:: https://img.shields.io/github/workflow/status/eventlet/eventlet/test/master + :target: https://github.com/eventlet/eventlet/actions?query=workflow%3Atest+branch%3Amaster + +.. image:: https://codecov.io/gh/eventlet/eventlet/branch/master/graph/badge.svg + :target: https://codecov.io/gh/eventlet/eventlet diff --git a/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/RECORD b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/RECORD new file mode 100644 index 0000000..d5010ff --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/RECORD @@ -0,0 +1,190 @@ +eventlet-0.33.3.dist-info/AUTHORS,sha256=qyCotEDXx0wuNTpDiFDomaSiPUSTrZm49r3m8LsMmY0,5837 +eventlet-0.33.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +eventlet-0.33.3.dist-info/LICENSE,sha256=vOygSX96gUdRFr_0E4cz-yAGC2sitnHmV7YVioYGVuI,1254 +eventlet-0.33.3.dist-info/METADATA,sha256=EZFIbbB1URHVFdvxoHEGargG-yXWhdtMscgJfEWRtRI,4346 +eventlet-0.33.3.dist-info/RECORD,, +eventlet-0.33.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +eventlet-0.33.3.dist-info/WHEEL,sha256=z9j0xAa_JmUKMpmz72K0ZGALSM_n-wQVmGbleXx2VHg,110 +eventlet-0.33.3.dist-info/top_level.txt,sha256=k0ONa2J0QJkHxkk-bj2NgX9uRyjA1ebSZzU_hOFSXCc,9 +eventlet/__init__.py,sha256=71yKdl6OdHxUxTIDbFwbHti-J8wdIhyMdgRXK2nbHlk,2469 +eventlet/__pycache__/__init__.cpython-312.pyc,, +eventlet/__pycache__/backdoor.cpython-312.pyc,, +eventlet/__pycache__/convenience.cpython-312.pyc,, +eventlet/__pycache__/corolocal.cpython-312.pyc,, +eventlet/__pycache__/coros.cpython-312.pyc,, +eventlet/__pycache__/dagpool.cpython-312.pyc,, +eventlet/__pycache__/db_pool.cpython-312.pyc,, +eventlet/__pycache__/debug.cpython-312.pyc,, +eventlet/__pycache__/event.cpython-312.pyc,, +eventlet/__pycache__/greenpool.cpython-312.pyc,, +eventlet/__pycache__/greenthread.cpython-312.pyc,, +eventlet/__pycache__/lock.cpython-312.pyc,, +eventlet/__pycache__/patcher.cpython-312.pyc,, +eventlet/__pycache__/pools.cpython-312.pyc,, +eventlet/__pycache__/queue.cpython-312.pyc,, +eventlet/__pycache__/semaphore.cpython-312.pyc,, +eventlet/__pycache__/timeout.cpython-312.pyc,, +eventlet/__pycache__/tpool.cpython-312.pyc,, +eventlet/__pycache__/websocket.cpython-312.pyc,, +eventlet/__pycache__/wsgi.cpython-312.pyc,, +eventlet/backdoor.py,sha256=-Uv0ckydgiCtGMIZ8ki_SGIaJPEvJKKKz0fu-RrBCl4,4130 +eventlet/convenience.py,sha256=xjaCXWHxhs_iFe83ygOTqQ-ui1tsYbt9lMtJxM04Awk,7173 +eventlet/corolocal.py,sha256=XLpOq3jdWyRxwvD9esg3edoGBIDSZ2NSZqX1WXeosAU,1741 +eventlet/coros.py,sha256=horXHsHTANuqNzWRY4wC-54OUVSy8ERC8U2jQKE-kUU,2077 +eventlet/dagpool.py,sha256=EvLUDyvKE_jWlkiwiA97E_-OR2zvjRWxekMVwGuIM34,26303 +eventlet/db_pool.py,sha256=60leZ6OYEpze5QZEIqAo_0zPwEZywWd1C1lOg1ElPtw,15763 +eventlet/debug.py,sha256=B6as5ianRkkwKGzE3o4iWEMh93gTnhB9316w0En3cmk,6326 +eventlet/event.py,sha256=iH9HQwWGN6py8Izqyc1X6rO_3OEWShRQZiuj3Xj9834,7518 +eventlet/green/BaseHTTPServer.py,sha256=2Ft0aojrxibdqpoeMlv7kcJi_QzFWbsbVbQ798wQOQg,346 +eventlet/green/CGIHTTPServer.py,sha256=BKc7T1hs9ZG9BWIAYih9D5Dq6OYqv_ntEo4LcDIC2mA,552 +eventlet/green/MySQLdb.py,sha256=X_FURsoOfZnv2htH6nG8_uoBygAqY16_g4groiSt9R8,1201 +eventlet/green/OpenSSL/SSL.py,sha256=ChOnAol6IPHQ2nBvxhq7zMj_g5V4C9FrLs08AzCPkuk,4533 +eventlet/green/OpenSSL/__init__.py,sha256=h3kX23byJXMSl1rEhBf1oPo5D9LLqmXjWngXmaHpON0,246 +eventlet/green/OpenSSL/__pycache__/SSL.cpython-312.pyc,, +eventlet/green/OpenSSL/__pycache__/__init__.cpython-312.pyc,, +eventlet/green/OpenSSL/__pycache__/crypto.cpython-312.pyc,, +eventlet/green/OpenSSL/__pycache__/tsafe.cpython-312.pyc,, +eventlet/green/OpenSSL/__pycache__/version.cpython-312.pyc,, +eventlet/green/OpenSSL/crypto.py,sha256=dcnjSGP6K274eAxalZEOttUZ1djAStBnbRH-wGBSJu4,29 +eventlet/green/OpenSSL/tsafe.py,sha256=DuY1rHdT2R0tiJkD13ECj-IU7_v-zQKjhTsK6CG8UEM,28 +eventlet/green/OpenSSL/version.py,sha256=3Ti2k01zP3lM6r0YuLbLS_QReJBEHaTJt5k0dNdXtI4,49 +eventlet/green/Queue.py,sha256=9M3RUmEBVR5P96TlKgCcwOIQvVlc7u16TyllHa9WPrQ,833 +eventlet/green/SimpleHTTPServer.py,sha256=WkrEBwvEdUNuKCJp1n4NY_4x_xhJR-lfws-BWbfclOA,277 +eventlet/green/SocketServer.py,sha256=wkmsIglVoBXR-XuRevVmEopJ4ylQtCXCXp1f-pVooiw,365 +eventlet/green/__init__.py,sha256=upnrKC57DQQBDNvpxXf_IhDapQ6NtEt2hgxIs1pZDao,84 +eventlet/green/__pycache__/BaseHTTPServer.cpython-312.pyc,, +eventlet/green/__pycache__/CGIHTTPServer.cpython-312.pyc,, +eventlet/green/__pycache__/MySQLdb.cpython-312.pyc,, +eventlet/green/__pycache__/Queue.cpython-312.pyc,, +eventlet/green/__pycache__/SimpleHTTPServer.cpython-312.pyc,, +eventlet/green/__pycache__/SocketServer.cpython-312.pyc,, +eventlet/green/__pycache__/__init__.cpython-312.pyc,, +eventlet/green/__pycache__/_socket_nodns.cpython-312.pyc,, +eventlet/green/__pycache__/asynchat.cpython-312.pyc,, +eventlet/green/__pycache__/asyncore.cpython-312.pyc,, +eventlet/green/__pycache__/builtin.cpython-312.pyc,, +eventlet/green/__pycache__/ftplib.cpython-312.pyc,, +eventlet/green/__pycache__/httplib.cpython-312.pyc,, +eventlet/green/__pycache__/os.cpython-312.pyc,, +eventlet/green/__pycache__/profile.cpython-312.pyc,, +eventlet/green/__pycache__/select.cpython-312.pyc,, +eventlet/green/__pycache__/selectors.cpython-312.pyc,, +eventlet/green/__pycache__/socket.cpython-312.pyc,, +eventlet/green/__pycache__/ssl.cpython-312.pyc,, +eventlet/green/__pycache__/subprocess.cpython-312.pyc,, +eventlet/green/__pycache__/thread.cpython-312.pyc,, +eventlet/green/__pycache__/threading.cpython-312.pyc,, +eventlet/green/__pycache__/time.cpython-312.pyc,, +eventlet/green/__pycache__/urllib2.cpython-312.pyc,, +eventlet/green/__pycache__/zmq.cpython-312.pyc,, +eventlet/green/_socket_nodns.py,sha256=Oc-5EYs3AST-0HH4Hpi24t2tLp_CrzRX3jDFHN_rPH4,795 +eventlet/green/asynchat.py,sha256=xEpHhpElLssgykgDAUKGRyUnuKS3Ddu0MRRKSt9YciE,212 +eventlet/green/asyncore.py,sha256=ZA8ZR2MYCP9guK51D2LTXxcjibhNERr0OHfZTjx2ymY,258 +eventlet/green/builtin.py,sha256=zg5ipE7NlFWs6UWGZQuKhQ6lZAmYdW7Y5iZlg5AZtK0,1287 +eventlet/green/ftplib.py,sha256=d23VMcAPqw7ZILheDJmueM8qOlWHnq0WFjjSgWouRdA,307 +eventlet/green/http/__init__.py,sha256=ve29vEVORq8ZV-_mp6ULOhZUGjdKkSnAISiFDwLhZd4,8793 +eventlet/green/http/__pycache__/__init__.cpython-312.pyc,, +eventlet/green/http/__pycache__/client.cpython-312.pyc,, +eventlet/green/http/__pycache__/cookiejar.cpython-312.pyc,, +eventlet/green/http/__pycache__/cookies.cpython-312.pyc,, +eventlet/green/http/__pycache__/server.cpython-312.pyc,, +eventlet/green/http/client.py,sha256=Lr6t7JJ3BzumxQ3qB40lCMkA8vIS9YVa8vu-UnAqHVM,58730 +eventlet/green/http/cookiejar.py,sha256=0HSmj_sac1TlnGDxJ27dSezwljwOvlAdUH_Bt3-Wy-w,79240 +eventlet/green/http/cookies.py,sha256=6ZJL0cirlTaG9Rmohy5M9NfwMkYRRbJxly8Nrd4446s,24188 +eventlet/green/http/server.py,sha256=jHfdMtiF8_WQHahLCEspBHpm2cCm7wmBKbBRByn7vQs,46596 +eventlet/green/httplib.py,sha256=a3RJ82XL7Z_2TbYBcvUeQ7S8d82jhmEITrUq6MNTMws,493 +eventlet/green/os.py,sha256=ygajkDgckbv6a3TRjtYN3jxGyR1FDbVw676mzs4BP6I,3399 +eventlet/green/profile.py,sha256=ROmWKx2ZThDlYdchObK0sUB0lup7J-UgFx5CoqFWwdQ,9554 +eventlet/green/select.py,sha256=Yay3PQaRZRkfTZyylZi7utQXHsvtv8HB_QWNVfpvEUs,2769 +eventlet/green/selectors.py,sha256=C_aeln-t0FsMG2WosmkIBhGst0KfKglcaJG8U50pxQM,948 +eventlet/green/socket.py,sha256=np5_HqSjA4_y_kYKdSFyHQN0vjzLW_qi_oLFH8bB0T0,1918 +eventlet/green/ssl.py,sha256=n43KsXLi2SraCrsTCrZSEedGVdhTRZGEl06QUUJBDnk,20289 +eventlet/green/subprocess.py,sha256=GRTiwbwzduBxOpEsvulVnu8sBkO_iCR66E1CK1GvqxE,5837 +eventlet/green/thread.py,sha256=bVZfCefqXiFLnKNpMv_QUtpODYQxj2HbEJpOhA-xIHw,3113 +eventlet/green/threading.py,sha256=9L-pKOAhzXQn-LT1zb_q80mQhUazHDiI9tKXQWd_WXY,3874 +eventlet/green/time.py,sha256=1W7BKbGrfTI1v2-pDnBvzBn01tbQ8zwyqz458BFrjt0,240 +eventlet/green/urllib/__init__.py,sha256=9tOImBCBOp1wPQ0C8UkfJ_9KJLwN0IDYZcLiSKd1N8E,1401 +eventlet/green/urllib/__pycache__/__init__.cpython-312.pyc,, +eventlet/green/urllib/__pycache__/error.cpython-312.pyc,, +eventlet/green/urllib/__pycache__/parse.cpython-312.pyc,, +eventlet/green/urllib/__pycache__/request.cpython-312.pyc,, +eventlet/green/urllib/__pycache__/response.cpython-312.pyc,, +eventlet/green/urllib/error.py,sha256=xlpHJIa8U4QTFolAa3NEy5gEVj_nM3oF2bB-FvdhCQg,157 +eventlet/green/urllib/parse.py,sha256=uJ1R4rbgqlQgINjKm_-oTxveLvCR9anu7U0i7aRS87k,83 +eventlet/green/urllib/request.py,sha256=Upa4bT0-9pSxJOmPuIk3t1rNmGbLEkAOUlpD4aUfCGM,1504 +eventlet/green/urllib/response.py,sha256=ytsGn0pXE94tlZh75hl9X1cFGagjGNBWm6k_PRXOBmM,86 +eventlet/green/urllib2.py,sha256=Su3dEhDc8VsKK9PqhIXwgFVOOHVI37TTXU_beqzvg44,488 +eventlet/green/zmq.py,sha256=fm9tpTPavBs0iorleCV08dfXDb6pv4OXaSrIL2J3kUA,18066 +eventlet/greenio/__init__.py,sha256=U-_kIaeIjRmZOSfrzUOWjjhqJETuO4A79KWgua4foYc,169 +eventlet/greenio/__pycache__/__init__.cpython-312.pyc,, +eventlet/greenio/__pycache__/base.cpython-312.pyc,, +eventlet/greenio/__pycache__/py2.cpython-312.pyc,, +eventlet/greenio/__pycache__/py3.cpython-312.pyc,, +eventlet/greenio/base.py,sha256=hROynTdsfnuqwmtaja1uviLkTGDZGLBuqamP23OvqkE,18168 +eventlet/greenio/py2.py,sha256=cOQ1iL0-15YKvJ6jgQ7dIN6gJMhtcWUwOjGPYzZyj3U,6814 +eventlet/greenio/py3.py,sha256=BVehuDNuV7j387OADu1U_Jkknqv-NXomRbKXo5g1BLo,6589 +eventlet/greenpool.py,sha256=01zT2bQGYY9njOt_mEsSO4-HxvYKq7dAl-yanJseQHg,9825 +eventlet/greenthread.py,sha256=ij4C-56p3yfW_LNt1nLGEixeem-tTWfnxy2s8sRwhmU,11596 +eventlet/hubs/__init__.py,sha256=82EPuFBcd7-xBgNeYpX9ta20FbUwpbH0Y4NK2RDuaME,6055 +eventlet/hubs/__pycache__/__init__.cpython-312.pyc,, +eventlet/hubs/__pycache__/epolls.cpython-312.pyc,, +eventlet/hubs/__pycache__/hub.cpython-312.pyc,, +eventlet/hubs/__pycache__/kqueue.cpython-312.pyc,, +eventlet/hubs/__pycache__/poll.cpython-312.pyc,, +eventlet/hubs/__pycache__/pyevent.cpython-312.pyc,, +eventlet/hubs/__pycache__/selects.cpython-312.pyc,, +eventlet/hubs/__pycache__/timer.cpython-312.pyc,, +eventlet/hubs/epolls.py,sha256=h55rCw84ZTCU4iHK0FKoIHSa-ZqhZNXK6KaTOKBkN9w,1027 +eventlet/hubs/hub.py,sha256=3FaQ7dkR9mv9kgMsEPyfdZoSGn3SUnCWCIMRiW_XGpY,17817 +eventlet/hubs/kqueue.py,sha256=CmfUJHquHoUyzrZbmxEVHoMG-0rWqjXaiVhgSDO_38s,3546 +eventlet/hubs/poll.py,sha256=tpEW-RCJIbO17crqYRwBkdXgOYUTL0T92qliw_sTOMw,4021 +eventlet/hubs/pyevent.py,sha256=J3cOmIITIfPjPMfnaZeBjHKu65AzABLT6udc4KPn5G8,5780 +eventlet/hubs/selects.py,sha256=tei0L7X00M7lDXtJwfzxv9goe5_s75U7kE9PevI9WuY,2054 +eventlet/hubs/timer.py,sha256=y0TJFRGRgHZchJXZSodWhY4FpnrC9wuMfr7VMbJ42fc,3195 +eventlet/lock.py,sha256=GGrKyItc5a0ANCrB2eS7243g_BiHVAS_ufjy1eWE7Es,1229 +eventlet/patcher.py,sha256=BgGpvx3C0SScG1EYh9hRNc2-oeViuOH72eR_vsB9fmE,19136 +eventlet/pools.py,sha256=LuqUR_95_fNuppZ6OK9CVIj2JxsUttc7HL-tqHnRsmo,6299 +eventlet/queue.py,sha256=hGMLS5ztBwHGepwW8hxAnwIkv2qbgVTTdt61eUPvlF8,18282 +eventlet/semaphore.py,sha256=0ZV1Uf9D1-0av2rTOqstiIjkHX6xz6F8-cowqtQUgXI,12226 +eventlet/support/__init__.py,sha256=RVxVJQdT9srs1CtnA0Dgz4LvbmK4NPYZppEiQ7-fWIE,2085 +eventlet/support/__pycache__/__init__.cpython-312.pyc,, +eventlet/support/__pycache__/greendns.cpython-312.pyc,, +eventlet/support/__pycache__/greenlets.cpython-312.pyc,, +eventlet/support/__pycache__/psycopg2_patcher.cpython-312.pyc,, +eventlet/support/__pycache__/pylib.cpython-312.pyc,, +eventlet/support/__pycache__/stacklesspypys.cpython-312.pyc,, +eventlet/support/__pycache__/stacklesss.cpython-312.pyc,, +eventlet/support/greendns.py,sha256=IR2x5zV8b0e2CBECFV6VMAJ2pXfdYw1AZ__bJIN-cjM,34168 +eventlet/support/greenlets.py,sha256=H8D0ym8RDU92fzoqgl1OusAJSBOeA5PnLUF57oUUOIM,298 +eventlet/support/psycopg2_patcher.py,sha256=Rzm9GYS7PmrNpKAw04lqJV7KPcxLovnaCUI8CXE328A,2272 +eventlet/support/pylib.py,sha256=EvZ1JZEX3wqWtzfga5HeVL-sLLb805_f_ywX2k5BDHo,274 +eventlet/support/stacklesspypys.py,sha256=6BwZcnsCtb1m4wdK6GygoiPvYV03v7P7YlBxPIE6Zns,275 +eventlet/support/stacklesss.py,sha256=aZfBXegfo_-jGl3-lAaZGTeXs0ulA7CleF9qPRNHq8s,1867 +eventlet/timeout.py,sha256=mFW8oEj3wxSFQQhXOejdtOyWYaqFgRK82ccfz5fojQ4,6644 +eventlet/tpool.py,sha256=_Tjl8nyF9eAQGjK68YI1gfFVjCSAj90EeMicbelR5D0,10765 +eventlet/websocket.py,sha256=2kyV9pstgq29nwMBN1LmZSgzhIyZOlfHdeAXtusW_54,34443 +eventlet/wsgi.py,sha256=C5BQa4YfcNeSizCz0MezJp79IZqtQ1AZBGOvnM9UxOI,38942 +eventlet/zipkin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +eventlet/zipkin/__pycache__/__init__.cpython-312.pyc,, +eventlet/zipkin/__pycache__/api.cpython-312.pyc,, +eventlet/zipkin/__pycache__/client.cpython-312.pyc,, +eventlet/zipkin/__pycache__/greenthread.cpython-312.pyc,, +eventlet/zipkin/__pycache__/http.cpython-312.pyc,, +eventlet/zipkin/__pycache__/log.cpython-312.pyc,, +eventlet/zipkin/__pycache__/patcher.cpython-312.pyc,, +eventlet/zipkin/__pycache__/wsgi.cpython-312.pyc,, +eventlet/zipkin/_thrift/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +eventlet/zipkin/_thrift/__pycache__/__init__.cpython-312.pyc,, +eventlet/zipkin/_thrift/zipkinCore/__init__.py,sha256=YFcZTT8Cm-6Y4oTiCaqq0DT1lw2W09WqoEc5_pTAwW0,34 +eventlet/zipkin/_thrift/zipkinCore/__pycache__/__init__.cpython-312.pyc,, +eventlet/zipkin/_thrift/zipkinCore/__pycache__/constants.cpython-312.pyc,, +eventlet/zipkin/_thrift/zipkinCore/__pycache__/ttypes.cpython-312.pyc,, +eventlet/zipkin/_thrift/zipkinCore/constants.py,sha256=cbgWT_mN04BRZbyzjr1LzT40xvotzFyz-vbYp8Q_klo,275 +eventlet/zipkin/_thrift/zipkinCore/ttypes.py,sha256=94RG3YtkmpeMmJ-EvKiwnYUtovYlfjrRVnh6sI27cJ0,13497 +eventlet/zipkin/api.py,sha256=8h3YuMLuKpwUuzRmGeCFRR_JC3QoduOKAFXnQFiyDa8,5444 +eventlet/zipkin/client.py,sha256=JXa6xSLgJ11xbNS0qPf9Hpo2MSdJMU8FPhZZ7bNIvNA,1699 +eventlet/zipkin/greenthread.py,sha256=ify1VnsJmrFneAwfPl6QE8kgHIPJE5fAE9Ks9wQzeVI,843 +eventlet/zipkin/http.py,sha256=qNd89Fq5iv4xXri_zTJyW9IKjjHa4dF8CWiX8R6KRxg,1767 +eventlet/zipkin/log.py,sha256=jElBHT8H3_vs9T3r8Q-JG30xyajQ7u6wNGWmmMPQ4AA,337 +eventlet/zipkin/patcher.py,sha256=t1g5tXcbuEvNix3ICtZyuIWaJKQtUHJ5ZUqsi14j9Dc,1388 +eventlet/zipkin/wsgi.py,sha256=732J1h_VKs3iAUynQ76joXYZDIA3utU0dF_-_H5qYBM,2276 diff --git a/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/REQUESTED b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/WHEEL b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/WHEEL new file mode 100644 index 0000000..0b18a28 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/top_level.txt b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/top_level.txt new file mode 100644 index 0000000..bfe34bc --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet-0.33.3.dist-info/top_level.txt @@ -0,0 +1 @@ +eventlet diff --git a/venv/lib/python3.12/site-packages/eventlet/__init__.py b/venv/lib/python3.12/site-packages/eventlet/__init__.py new file mode 100644 index 0000000..86a8384 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/__init__.py @@ -0,0 +1,78 @@ +import os +import sys +import warnings + +if sys.version_info < (3, 5): + warnings.warn( + "Support for your Python version is deprecated and will be removed in the future", + DeprecationWarning, + ) + +version_info = (0, 33, 3) +__version__ = '.'.join(map(str, version_info)) +# This is to make Debian packaging easier, it ignores import +# errors of greenlet so that the packager can still at least +# access the version. Also this makes easy_install a little quieter +if os.environ.get('EVENTLET_IMPORT_VERSION_ONLY') != '1': + from eventlet import convenience + from eventlet import event + from eventlet import greenpool + from eventlet import greenthread + from eventlet import patcher + from eventlet import queue + from eventlet import semaphore + from eventlet import support + from eventlet import timeout + import greenlet + # Force monotonic library search as early as possible. + # Helpful when CPython < 3.5 on Linux blocked in `os.waitpid(-1)` before first use of hub. + # Example: gunicorn + # https://github.com/eventlet/eventlet/issues/401#issuecomment-327500352 + try: + import monotonic + del monotonic + except ImportError: + pass + + connect = convenience.connect + listen = convenience.listen + serve = convenience.serve + StopServe = convenience.StopServe + wrap_ssl = convenience.wrap_ssl + + Event = event.Event + + GreenPool = greenpool.GreenPool + GreenPile = greenpool.GreenPile + + sleep = greenthread.sleep + spawn = greenthread.spawn + spawn_n = greenthread.spawn_n + spawn_after = greenthread.spawn_after + kill = greenthread.kill + + import_patched = patcher.import_patched + monkey_patch = patcher.monkey_patch + + Queue = queue.Queue + + Semaphore = semaphore.Semaphore + CappedSemaphore = semaphore.CappedSemaphore + BoundedSemaphore = semaphore.BoundedSemaphore + + Timeout = timeout.Timeout + with_timeout = timeout.with_timeout + wrap_is_timeout = timeout.wrap_is_timeout + is_timeout = timeout.is_timeout + + getcurrent = greenlet.greenlet.getcurrent + + # deprecated + TimeoutError, exc_after, call_after_global = ( + support.wrap_deprecated(old, new)(fun) for old, new, fun in ( + ('TimeoutError', 'Timeout', Timeout), + ('exc_after', 'greenthread.exc_after', greenthread.exc_after), + ('call_after_global', 'greenthread.call_after_global', greenthread.call_after_global), + )) + +del os diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..146302e8cfe0c6c6a5febffa1b96651acd1e036a GIT binary patch literal 2822 zcmZvdO>Emn7RQI8WJwnFZAt#D6)S00O)S}Qv0J2p(QTr}P8>UpjqIB3t|E(Li!x1W zhm;)!3BW8C2m%C1dfD`t%K}{l3*^}2qNf7Ai0cEjV`O2Vm*(a)J)}PEBPBTk%mMi3 zKfifH&b)au{2>_ZM^JwF?thhk_!0WiYII9qN4yz!BlHvrNMIzyw`+!H?6!+{VW!&k z?2d$5aBaE}B5@bOuBxx6qay_td!B=bRTw0=c~JY6&4G^c zQCkN=$M~47L!je)+}2^x1N?xkBcKy}!q$wG{5K{ixdu4&mEsBKLq?k2R;n^ zw+?&+_(u+W6!>`uK88cp3l97}9Innf@NwXuIPf%%R6lj#CxBma;1j?J0~;1-|0IKLCEyfuF&N>K`5WS>U__p9KD; z1J42fvjflLWcBQEUcGgk@l!(dZ%BvOW1@;si)vcHHG<0}4GZa#ETnftNlLHbG{IZSHZW06YZaVc zuWNOJvn;VLi&rPW5bHCCOpcBgZWb0-778oH`D@pgZmbk<7H%xhFD(|A78h12Go58< zpsdK-SQfEd#?bU&XlNk(8w6vyrYMr-WH+=5!6gBj-dag3S1_^s|5C?wYoK4nTcuh> zfti4V+FqO`HE|0ob*20#ygZ^B+HmwU~E)35gA{`$dLhH@+IpcV*G2=#Va z)Q@kMi=}l9lRJr?9m^eI8ZDP3sqIqSkd(EObZ4yB^H_KGL+Vmg%Bl^O`gTf07Ud0< za-AfFMXoEcvPiuUO_3xPcN zUltMTr%M8s;VfhQ_ejVxVar$>N}^z>&z?O*ocgyES*CYNZ_+a@?wl zG7Yqa9e3*9ghNV0qCJ6P`mCt6||ChM{d(pZx0{;W4dwK}fe*R+9-O~_{=w}1*=B5Nmz{pGyvu&{(tYx! z`@NU$30tJQ8bo@}9Y5%IdxCqu!4{W*7len+&R2D;zL{hskXGBkAbt=O0u z%Nnt)ezg@ynt>4`Frr`DPmG$0Ge+W!zSxQnoAJ{|{ItH{_^sr)namr>yuQ>*rOni| zk(zG02lhjW$I3$m*79z6D>-H+CynGJxJC!fXvT88S literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/backdoor.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/backdoor.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc1ddee80916a2b3b1186885d54bff5c94ac34cc GIT binary patch literal 7298 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/convenience.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/convenience.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c59be4db931ad2c8477ec1705aff33a379a93310 GIT binary patch literal 9156 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 XgH&z_cJKG!-78W#AyRq259R*`Q#2sc literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/corolocal.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/corolocal.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b658718cff6ec9b5bb9e5e8092ddd709cd95902 GIT binary patch literal 2873 zcmb_eO-vg{6rTODz4$jFsUhS?8-yTE8an~f97=mgX&TamN+oS7E9$a%7h|*7baqW- zWD-aua9C`pzsT_Lfx!0(YU8geLqAF6`LvK|~FFEzSU9T|&RaMlH{B~yE zn|XiVd*jD}fS15{{Qa*JKe!3`4HwRE)hD%&VDgYCL}4@%$1kffQ*4}Nh{7p+l8tkU zYk|ahmb^n0_X1Hos%xFIPsiPg06dSyYXKgwx*I$p&qTa`qTq0ZF?~s@su^Q~sUw@PHj$lDV~MObmX9SSWW&hEX4K3~OiPW$rt`)`Hq#e9-y73YhI)2d zPE5*4RgbA4Vt`UON3)jq(dj%zhb$t%L=OX*C%^jw_a>{su5z%e66~({dP;l`#gy&} zmlK{E9(qVr5@!?=XB7rA$71I4T!c5hlBH@)*3}4W`t6k@$uy~(L1$&fLr*w2!hHmA zLqhPYd0`ilfqT|SHwCDO2D8TKq5#0+V8FW={`g zQbtPFQnRv=%4S4}YHkX$tiwzdGg(8_bJNpVYN$%YW4g0rX*FS(9!bimwcB`ZlFm3c2MHnr`w|D%r`~ zCa7lVNf5M*@|Yi?qDEV*Nyvn639H+3cd2+&f=ZrL?zT&ddU2m?*KrIY8sy;$wLR_?o2=^K6`y!W&Jz~`5iM;_h!;?~z6ej}|(<>Lce ztw&48u2fowO8%kE(4nHwg70v|g|1}eDHUAtO43wT$!R$EOVTGfS*vqeATCnq&jjpel`^>KDYoOk-8Y^4j(3>N$wNtTqs}3;yRxw~w6+o*bMLhH%?3xE83ECzi zJ2jRwAj4KKrC7WsZvccTHZ6JxNW=|06I3(Zb{7IvLziwxHY%kZKy-{dg1QcC(=AQQ zMq+~Y!2VGbxDN!N95_@Bbd>{LD>q7KUatfO<}Yu%i7#CBie;}@@pjI?wdwILb(cNu zPs8m+cTr#TZ3SDG`aeq-xlLd2!NB6cmotyS>Mn=6SB%vg zl~8X<=(PjVj9j$P^5`;5Uf!cZ<1HB>^fX+x%PR|GoaGgL6_}=LM%Hqwj*1;TSwNgU z=s4GC$}y19FWxiDbA9&b`~ z3jez37lHi6F%0ty3I0LEr@rvAr{opqJ=-Zxi@DYxgs+ZJ+Ql(c;y= J34AQm{sCqPIA{O> literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/coros.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/coros.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96a79888d8f46ec5e3dd6cd85fe8e45b55b6ec61 GIT binary patch literal 2616 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

{VC$p)- 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/db_pool.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/db_pool.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e0d903196f226f02010abcabe1f655957b79e25 GIT binary patch literal 19063 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/debug.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/debug.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..442117e8bc524fe9aede42c2172b86275968e06b GIT binary patch literal 8530 zcmd5>TWl0pny%`uzPNpd1{>Sp0>_vp#tsvf%)$^v05eX&$#O|H&ZS-MI^8b2yQ=q8 zHIA+J#F=3f*hIo;Mgle(!OY7#o0XQVMmw*gc^Hi}(k>1l_g0wINZEbJ8^Nr!;$`>y zPjz)Sjv+fU`>>_%b57N{|L6a||8joY*cg-W{PtJ>Ht|pGlJo_=_)n-@SosYKmnB^q zm2_DTWaV67G$2zsm<^5wMXro0A`gv*L>?Xui##$K$w<2LGkG)`kXU$foj-Ur*F&Gk z-rHy_ARUwR@Ht73u=-De<*yC;Iz4($9&Oa?&Pk(9dJJi^UXQdzU#~ZyrWJW3@^yM# zZ$iEvc{B1jzPBLXpl{Gyk#C#{CN|Ep(-Tb9*?1wN<}AICWopVY9nCOpwcr?8!!ekx zYNoEH3uekOEPPK}TpeeQ!+1<|%ot`yJ$jazPL?^l^42utOjoshK5L{j(PiLET75!x zoATT+ouoh3NFwGccAibU;esjhI0nyYPI96!p3EAy!%W6)x3%W0W8~=5`q~yr+tE@} zw%bG>**htg7t^{ePTsPzwO_iZMV>QSZ8BHLIz~RrlALKe4cbDrVgi4}jr3(aYo(^J z$)wI4CWtT_eDk%ZQ{ItyX`uYF(t*NdiAkd}cqE`JdH}Qz>Oq{4G9xEKu5t<+9!ZrC zRCJ@x^uD4ZxhxgsqLf6*dx`lD-Vil8Du=Lmf2Qnyb*mVy>_g@u!%iJ+69+p`!9hc@J|D zbF&_q3)e|bz29uI!7o+wqU^6?E?SHh6){hwC{1pxw((2vH5J2a*MLg@;!s!d6vH3M zdR-gp>jr9h-ak8&En_nu{`BaznXEOgWoO~oC&v%DpOi&g1M0l zbHpMe7&i~}b(`?TwhBDO=)3JkhF?C)xy9XZI!Oz1BPmNP+Kpfq&2|g{g>Fdb9yiRf zJR>iV(QQsT))X_7Hgpv!G&dwlZW!7Ns}bw8QnWZ_(lX3M11Ivjp)|*)-H?DrH>mN9 z8!qoBN|Yp$xC*75c+m~oJmp59Jv0MDg~gn<8$@oCxC5Ij4<`^MvSZe44Z?b_G@AW4 zPzFHQ`j&;E4-S?#bX

(%a{UO3jT|f4BF>o?q<$bpOu}+}wERcFPN; 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/greenthread.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/greenthread.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d3cd571e3da76f238be0758d1d40c4273ca614f GIT binary patch literal 15042 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 Tx7Du(LgJ=74P9b8Q{w*xzo4!) literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/lock.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/lock.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..138284fd6d44f2947a21fa9dfc9b17be27c9b13a GIT binary patch literal 1613 zcmah}&2Jk;6rWk!@g`Z5k_Ji?5eC7bmZ(=Wh>9pds@j4og$R|Le3^`Q$KG|;yY9?v z8e2V7D2Jwia!mgPluQ2xBqXE?so^Wps_lhSZ$XAjPrTWW1R^A!ta)#q-}n3NJg(Iy z5YXfAo_jx*5&B(U3aaG7=rRa<$VDy=P!BgT*0K>8y;8G;k%2x%ZfP62WnS7ZWj@XF zpi&=up)Ylul)bnmfscQ|d#vw8f;*X6b{hF*G!Mca;;4yT)HGb&EV&ie*v8GWj^C^d z%Js3Nyb?JZ!ym4a0C;190IhkxB=EcX`OQmC)ax_lw*pS?_{t-TPQavGTvwjp%=N=g zL;J5cCQ{FvO8YV5M4{qaiu72>I$XR*Sm+Yv#gaHIBrQ&2$z9@yV46m4>Ew%xSrC$z zLY9e+e6gMeCYfDomMaKxBPydFr&7i3c5dht-RmL129TcM=r=g_&|TC8ewJLkaUCHs zskCKEuCb3Z=}UYUZQ=j)Dh+E0IR?#rkdChNogvXs)k4)Z3+a^a=VEvAR98qYWbazV z?Ue85Jz@kKtPH>ZEOPz!0LJREO+ShS*_0=HI)TVKQ4tZ$mmG$ED-M<4<2Qte#K}1M zmc#qXk3!ReZ>13P*ekc4N+-SgQ z61u73Yk~)yL5*>!cMTxX?1f&(TD_W7od_!DBC-D2L1NN7%m=j;dWT81!xe>ZB$<(p z$K5#K)L}tDJ3-W9K{81JILx^n`-0bvq%3*RPRta7fM==eAmrr@+XHObP875Tw&O9S z2KFWoH|@Z0+5Lg?qHv+{{$*Q2;!AzzYyi-u&A~(kT-mzpMt_i0^M;1jiq_<}S0RVI z1;;&fJb|opyGuJu58wW2a_;H${FApn{CWDy6YI+HD`>p7Ydx?Y;sbN`8|!FtYIpg; z@>4Kh*;)B&_0X~pEc+3D^ugi6wS$FgzgX9grp}KbDspoCY8f7s{44kb1bBA-GMxJg|B5xUW2iZVspk=USu z`=PIfGwCeOXy4IZvvAx)M;G3>U;f%UGOgX(PVLZ~J22;dHQyBH;osFdUV~@Lq%>sk z?bBp}(q5DOjs7T`6>~Im zt}-)v*Qm^A*4k#ZUP;ViyBW-;i)%v`@37D>Ko)pTOS<4R*-LXNEAnHZUu8WUc>#{& Z62|x$ntg^Y{y9eQ{QZyqLcmhTzX76eg+>4X literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/patcher.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/patcher.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b2d2dd11863b96a8f1d54cc328b1460250884ca4 GIT binary patch literal 20818 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 PQysXI_-_S2LXQ6hp=MYY literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/queue.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/queue.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..145cc557c48e33eac31d9fc394ed09ced9d0486c GIT binary patch literal 23262 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/semaphore.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/semaphore.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73867942e8c4a6aa6337ba438cb33f7ab0cd3be9 GIT binary patch literal 15949 zcmeHOeQX=&dB5YAD2WnfO0ua)mQS|)L0O_C+esV6b|TA>=OlI-M@<(kE`8#iBuW&i z+#M}bwTmJrCC@?h5(gEEcV&s4vrwtie3@G-GY`JLQ zV%UDq``#T-6qP3F2J9ne>*ewNc;4szc)lP0tgfy`!1J?r{(179UP1UdeX$;|`LZ^K zm#cyzC}L6=<)7lHi0`hXIO7_1iGoWwAt>$_1jVDeZ@BrmQ4g#2qSmK+>{>6YtwOEe z(btz1Bh|m8fu|#4(KDGD)8iZrFJ0yr6BkvW`A1!fIOl>L=A1d3G|b zsq*(Q`(R4Sq{h;jl%gt7&x1 zVW=~hG&La^=?$Rf0H47!XOt4UG_PuDNzdrBYD&=w11S!C3BZJ=swra<%T%yJBayTa zmB!R~dPdb5s8l+|K*$qvA{DJ+bt97ijQ@jQI@yDsv$ByGOPcE*h$m%TA2`PWW1gtJ zo#)KqCk+}bOYwBdP%jwLjGU4uRIQt~0vKj=pqK&@Qbxy?CsOFjwsb6+j!zE^a2zB$ z3+aetL*I8G!jO}AXS5EO-z7;&$8@YHrW={@@qG~v;pDiKN*fY(CY~5is7klgbzV+p z)GkR;$K_1Yz;>mj{&h&trgfdI6)nlcOu~@NbCit4jJi2)7Ha8fpbXTa=CFM-r|nkUQx>gT9r0<@Szk({?9pz6W?;`nw^jQ*U|tkrG(UTcE!Nvq$a*V% zTJ(kyj0aImR@fyN_F6gx?a(FActz~&SxVR$)fV)NE(@m{CbUa#qtK6iAuJpfje<5@< zA9^?!diY)C@9zD{^m6FT!q7^n?b?&y>Rk$S6t=Z3cwP>ygm>k`kz6>k9Nxd+d8O{7 zJ3*o0p4)<}x}y*}o)0~m3qAVx$CsXX>SvGMeEONCvtM0)I<_1-N03^t4Sg%R6p((i z>P7Fn`Zgf*eC=RZ_;I-UShM@b9lggI-0w7a@gBFy1buw?V?^{lyj&G73RCzjf2QoK zT52(DQZhKEXH_oEJjC_lEMFCMfaq7q3B8|T0MXl-AOg25v{)E(uh!t zzFAq5XLOyGCrR2?s>5Kqg5~GT>w3^bKZ4>pp%B`7HTr5aAByBcksloVf%bOijSDx9 zP<8jK-GAm@JpSgXH%=`}2Ugu;=+J`i!{FZQdvd|PC4b)<00`?&f%cM}!Uep1np433 z(rqt$oF`Ig|Ly&Cm+x-_?eDss^=d7EuH8cg?V`4w3fe`j9mTr6tBOJ9(AI5nk_f9h zKz;jAlkLiok=PZlek>fA@;$zT|{K7eoJIG zMyG3SUKAh6XH}VRD6N&(+=o7TBMLABpU}{B`QnQguRWU!N=tre<7&{4uZGU?ssR1a zH9uxy%mm>B5;!KQ4|+m3)KomnM1BpSNROq0eBI(((GuSpl4oaC#YPY+2_GX#!*2=N z9?a-mQI)CpIgih*dVsnTonuXYp`n?r<8<5>j%cwt$&XNrV}U@M-iKS_Qe2l@7hT_I zK#iLT;@Xm+Ry@{SB%!JY(a+iHEww6cKoRvVxbeUqgs znKuDPTpYa)t+u!>fw&@S!bKWcq3yh=wN`GKvQ>-{q>?%zLUqBqUr>CPJPzE6*vcO4 zpwANY_X;9$>k8bJuRuHS$<&gm=CVof)!L)qGK2W?0j27aN5KgzE(OQg=H!2%gSf|4%D+C*js%99TDahzFSQ2ta6CL`Dvj4o9pQz(gU zcrzwtZoUyqVn$awC1J3Z?S@Gb*HnTe!R8ptO6io!%q5L+aw#zbvmhZGYBC#@VA!A& z;1UcQzY;9Q)w?Cq8OaXhwjbF?QX2Ll;lXUeN!XoaD`GjOQMxYz5n%(Ik*8sj7*6cj zj8Vz54;8|anQpUMdY0BoD<+vh^Xh3@v0=l*2F<|gO3cEPw9KOj!vG47rgC9KW?BiG zxkNIFbpQ|b0yJ}&;h50|m}WaZBV}d@CEz*((6ooJSFsy2a#os6#HaaQ8j=R{Gdu@Ps{-IIW!D9Qc_4H2F`hzAK+JVE@94s zMcTkNU92^&J(vD*iyYw=xM}CjWOR0RP|*_XLc!t(nViSA(0S1F%m2W0np=K*P5b?~ zjh>IfrLos?He{K%fR2Im7vaMFJX$Gi@)5qTfspv3rO(FZ<~KCec+T;um!Qx~huwG``3 z*fGusimvpuPWm`oJ+}#}OagT(MrPF@dg^}x?4cNS34z8dV=o;kY`tgUM4>jcRNKDz zh3ijz{{a|!OWTK+LnoF3C;lP0tq|P06x_M8Ra)wJVtMPCrG_(wt=kr;<=XMrAARl7 z@180&hJVr0k#FhFwRA7HL>KBmtPNg1{Nmwj{qNVdEqdOp`_sBYaQouOn`hrRn+qN& zG&EhUd$lg#(3xxKT>R?IhC{2a=DLSB|X^v`DTca((aH_b#{Ge@k#T z?<}~$1dK!+}``)_Ks_=Usqw& zEx*v#Uf9vO8uDyuDKv$zo_h6EzG+{sY2Wqs51PKXS|_ybSP8ei9(XPAo%(#ZCl~Iy z8SX8#?s)z1Ylp9Qyx+RN(AKe9gd`-7PJ@`IGte-&egG;R3UdR-g__7N_@4_@3dY@Xmoj zaoFSj$sre&_tl{4U03UHgYa(qA-w->W9#q^;XRLcxUTBG>MGQ{*HAs&=6HVjH>J#CrpEkSbeVa({VK?3@8#SFki5WFS5oJ$F zFUtyViKk(d+>ag`^mv%UpcR!2u|`|>hp)n{d=6vQYEi%x^9L>mUJSfa&+QVYnGX$s z+a=$}3n2uh2XEz%BF;m(h0EHJK~Xh zuw!SqKiR3YbVClC+61v25*!<}60TxInE z20_K0$~G*A&cgvR2Qz6Ukmi|LUGkB7$l}tjZ%-mzhCb(mp zxLIN%1vfwUzteY(+!>6sk3@VW*2*}kMh1JaNsmveN+zksNWjG=lIbxyS*(r0!))?D z)0Lv#i|*@v0jA#KvLzQGjPQl+il)+>w`#tOOb^Klb8de{?z@=*J2-SRa2)JlqZMuY1pXLcH}eT-4^+OJFjS9YBle>eqsyO4URjF}$-6vdyT3{! zt#%QfUit^u75651xaWkqqpD`p(9p&W?H~q5ESHq&+oT1VE-8&<%zU$hl1tlv1x-3R z96-x_>#mKw{>*F7TpxUYdrzUcnM^x@%B)(zKg(G69v=rYD^cdYGSQ!i}vqw>0M1BiB% ziVhU(7$hM{B7SWc#bzh$@YYv!jNLGq>O9PM_=ZIJOIR;LUc?Z{CgIvCBSv!gsXADu zE={KAC~A{MTx!nL&>e2p4(Flo zGV{uY#>ewEnwNdIu`o&8#KK;<2G2d>e=NSmOpOw_H1-xukib}*(I+wXDD5MQYKD+D z!J4?vX7tH*101{8&$u39n53OsYecMomWD0IR0!s!nD38GS2|rsgs{Xj$rLjcMA%yv zF9rnbkj-6mxYi=>a!2VNTv?s2p6NQQ0vDB2an;Ei&8lnEwG9P$W1Hzb{BC@)^Ua<& zdY0S!xC5C{2WXD?cf>UUMPMY-z+AM&npkWm%{X?f7>LE5Mfk?_g~K_kq~q{zlLJPR zsMtjXY1>*C6-=2R?=~Z5#&)z*_*(RHgVxAuk5g-e3L;QO(HbestRP0HpGNV5aLcpR z>tE}u@pj)1yS@FlYh2#_xBYH!$L$)Aw|Oma#M^ZH>!Rr0w&ttF>pgC7+wB^Bg~QZW zf6G`c~kWa8Mh!r^In()siak%j2`tu9N}wrzp0ZM zDd(LmZw2-3mY%YMVkFTu8D<}X-X~$Jr(u@C5X&&ronJ=X3Py1woCS-Ck7Q(xJQh6o z4o!rj@hnygVvEZNY9$QrBmsdjq-;QJQm!u{5S^ygfya?q1gwp{m?yWYR9y#cJuW=x=QR#ka+)4mVUO4th~|1!`$2&Iwl{#aXR`h8$(W z24O$1o`Hi!`wD=u>s)L|Yl(>j98EEkhkytv#G{cPo<%Tp0wYP!@jFX2FyB&s1MGO- zpQBlCMe)&d!ivA{W_|mjC+Ck|&)oFiTab1ycs}rNXVU#_+9{n|d}jd*A|G`JKP zTnRSaY~HqK zSVZLblC*n>(;8b96>s5>1*ShD0pa!fe^u}D?)kO9*8AWs0R^9#ddBf*Kcte`s!pk58Bu=ps!{5Y8ATaUHTV`#>nJnI($h!lvw=u`F<5rg^4GvLAQ4O- zE-3&TGo#pb_&=5!#Wf={F^XJvwr`y$MoE=Zv&;*KEU$6$^;>yaws~@92r|QD=C%Caf;k6@)27kT`66QH0*R{VvSWkv zp4fk7x>^}MeVU{-rb2-H?8i0$$>xP@6=Al6HE8vYXfh(ilq+(BxDOtdsO4hDy)&xp zNQ-h9jJCXm%3-k_!O>2zVK!Q@*DEGSr{XU1V`WQ5kcYe2OBs_qUM9(sW!p*Ii0hUn zpj_?N=XdR8^tYFcI2LFyjUX6yDQPbwpuG_fmm8!O7i*L0Ik@{6b1K%dd&s|-a-`^G z0E=EW*3^U~hb9y*!`fDaRsddq3I+KW8Yqzo(qmN++rLn_|L85Z*#6*}AT~ZEqN?s8 z@k4aE_oXAhAb0n%<>yn)1WFj7)zRyGyBClE$b7ym#smu`B;HkoNW1n+6uWz8fKf#S2z-$gY>T$qYt4d%cR+mgS6<$OMN-1@9oa~ zfn&J?$0!l$xJa2uUDuz=1rILy4{qFlo60U?hEKEq*ex1c_Mq0*T}w@YeH;8AgkxOpV-J>35kEMu^Q%c*7M)(8I;e%V}d+??cm6$Y3Cj0!> z@@0{hqAIIsTWdi9<&UzD^1&Us;EsF{xo3NpgZq~J`~I8Ip8uFUtM%?1CaFphMl6-l z0dqu@FO?h<8&A+w!+wQEZGwsp6zgm;`yLYqBWb}hhiDJW9&6{3IRs;~5@-212QbUs zsd&dL1v0F;o7^Hw6STmAqSYVh-H4d`Jtq&3j< zdA5d(-#?J^^7CwsH2dKLXFB9qmzipArA^pZ=6_JO2EW5d(&QdqAD04*Ne+SdxnyA8 zT9yxS40X=f8*}7tWSXX@`Y!b*SkDQ!_6QAON6rDl!Fhi;=MU%oQqC{k^zU4CRaJ)y zjob2#yK;@Y@{Rj*jr*4ydzJz{AFaB)XbLps18uoLTRzbFexUQZmd9`M%4XFcCiE)VU1cMI6QBdX+Ny8oZV#N*h8 z3eG{YMXhu!`Y{vP!LUr3y$g@tc~#58h(Rt`GBI6BM*_8!2foIT;e)scnZoH=X4|MR zmNowC4M$Y_nP8CDp|yP*PJk zSuz4;r1~)gN}VGdNwCe*QNwo2yD5lO<|5W zQGoT>>T4%znEDD-${$OKq1ZO3$=^d4?Ieozp>U9gckY`L#Ub&^_^ zD{v7&23>Rn#BxYFb`dUFWm-{1Vlh>cQm(S%q{>#JKmPy*Q`}7C6#e+|9|-V6MgQd7 zp2vb9Xnkf1=;`Uc-F^G^z31M(`-?;(D&YCWfBbLZe!C$2mOk7k;8a#WL*=@l3aY3J zgZ5h-6#2Jr(1*8Am&W{q0g?J7JvbH`42i;dLG`~Y4u*Y#7P;k2H5l~?Cj~WdSx|#o z?3QFV560EdWnnO(hEXO#-=H;WO}Bh@FWaOwj_z?LSo2aNTHK`>OsP?fZLNwJ-J&(} zK6iGnFCF_mjnAevcgBNKTNEI4G%cRk|z=`c*$T4QN4QG!Ia}K&8(O|xQjJVBD&u*zpp_n;$&kxqMbp$&xyxs+&3ccbCv^(hD>wWGZ8n8no4C; zHu42ct>~J%Usg&gTKRP4l3tZ7hBi{s<>H83Em!t3O&-^kQmI%vFPmk18M$c6YLRJq zQ?ELEa@dp0!=q?6WCi1}XH&aq%414NIj=FpUdkxTd8H%|YqStnF3F?@GB#yuV-O^y zyD82%fl7ZKD&oGl#R;xIMWO|>7uz` z2CMvp%c>?rVT~f*XyE)2(+7t2a{humQ7-L;L^TbAZ056h$1u3wlETvWY8J(HVM?45 za=e3zDIdN>_fr+{KuQ?KLX^Rhv*OEKE) z*6MF>zLt8MTbmMGq+h?c|1W+vlIY`jOiPp3`W-~#Cg-Y^EDscbSoq5;I?zfkmk3r2 zlU4GB!epb|90@XjN=Bhv(N&Ic02pVCc5z%U=8FKU{c;5k$uPV%m|Rc{V3AhxW&t?m zH7u2xd}a@aOGPJ4uM$ErK=cb71~vZ0QNkWTtB(|!VP?}YD>SZ^R3N5pZ%G^yH#!S0 z&4q8vKU^;B%#S{+#YN*BahyA^mxmSI3cLQ(io$t1ZU%N;VC9LMB5TA*gDgd6q&19` zY%<|m9%t_mk^Co$8DTjrw6tCC`A*NWAV$6_*5WVyTwRQxUX|!;p<(;oSi_Z`xt@jC z_FDV4*<){=UQP;)Ewi!Zgph2$a&GS2LSol@lOOH=N#^6sXS;9jJ^6XN{C4}D_U9Jc zpQ|;tT#sLi|DbTEqkFNVd#U58I~|7>I}UyFG^XEjROHh|`E>UK!j>IhKJI6||JqAq zzskqnZAi_=SPJ`i2pMSa92rib@;GF;vCvQA=&U ze)!tqJE=X3sXYtvJ$F-WkM7!YCPNti*j?{@%&tjZ>mC4vB|yxW?el4tN(pd+5C^lv>k_Y*yh_6u#D9Gu#(|6+A7USD)$87`s_UYw-5a0QzevrrFR`EMj9;Y80 zY5z3&OFss!dzhq()4q~V^-aTXZiMPH68fZ4gDP#@PhNRiQvFln+oBq%`&2XLE(PyJ z){Mt}gEENOC|+6jye&>i`1gS#^iHH-NQbkNVcAhg3lJ!KLC}#s?19)b>6f7!lMy!x zKk`8AO^2*VF2_UbT#h1_k}{^{a*W$BIdDrVXcsMCQRNVx_H($kB76kV)+r5=8EFQG zVVa9+Ptck=G0w%L=^&^y;l zpwPBuuJ3N!-Vb(vn7Nr*YX8M0T4FJoJ4t()gg+-+@}JyF-x8w2m3`_Z|N%*8z~)OL5if94<0eEgM#)S-p=p<1Ge zLWudwVq$mgK=+5Q-Foe(&n~21T!_DTH+|s4t+%%R%j=(*KYQcf-dIRw7vkARm6urO z%G-;|W0kiNhf7rJp1OylSf3H>)VWY6EC-JomV zjDX0G&!6^Xv*{$G43!nl<;KdCx&Uy;bGg5+D7p>a7IYfc3H+tI6OV_j@Hi`vYs{?L zc+JI>rrs_px~X`U3gU%PxXhQc18QP{CxE_!iZOuVvT)BA4TM+Q{DIb$crwtkEDOQp zJzpTuzAB-%OtqC{IMA`u5(}IXSGF_<&WbCYQlMoeDh1k>qnum}v~co!KGf_5X|4R) zv!(`8&eb?H1sWrbZlE!$XnJGZ3+S}pq_O;|yVdDHM2H}BhTMa%h}qr~|6ZI5jQ-K= zLcg#}Fg-2aEwJ=-a4I+zfc9;l^4C|ch-~weKkxe*l9bcIH$d@a^M1pjoDe63i_+JG z32~S3Wush5TovDa6`4X=v5^$9IK^jjXSJy5bmvOTBbAaJnDNAtBAa%G@dY=gx37g9 zTs_herKsathy<5rRv4wHM#@NGQC#N?UlZ%{l$k@Q$RoO9hr8U`Y!J5+&Zr!3q$G_r zULHqu@N!>&e_!?_S1fL}=@3V3E9~-O`4x8Fu>2P$RD=Bq4$=xalUY9PqCsayJ_3)e zr{vff72m?Y@l6zf?x@h(e*M(7Q}elhE-bY?H}mr6$=0jqmy#VbCzpNUKs!`5(){14 zC*}thQu}TUET;A$uIRw%_U{$aEfk8>1_d-3wzekQ{q)!8hqiN~WW|8VC|Iu#Y(@v3E z(gAA8NYv8Z{Gud1bjKq!=eYkJRNjOkn4TGI1dVfE{K!&uGf+k3b)9i@LeBFLbMOZB zXmr%ey@dV{;y@hb8K^nB8n{>BvndJ87ataFs zt{TYgUcglh0g7CrM?gxTQ53;XF`Szk>ll&8A;Ze>d8^`nb_8vk-!v9r*^Y4k*frfLq zumBB3Lc?kNfH39DdE`?*gbKt~TIAjVj`bycS-}evWS;BOF8~tkcyjAw zc}4F{3^h7Xd@&>ZI?;6X>4n73`DYgsJ7;~hwym>1INXM&*@*4=f>sDABf4>-VsMpU zM=+K%Wkon>AdzHP0b0^PEbub*QmUDYV#q-Wf(=H+JJfRw1y;Y`D1;l{{Q9M@Uo~%s zwtl{4=Z|`qwj4x8wt)vSzY8Sa%w5V|9asu%S@lVQW^!9kEK?2nqd5}GTn;C2 zI-AkQe`X>zW}E0M?*5jP(-?zhN%(bQjc(aqpIrY>@NeuyF(Z5)Yxr(xUYdXHgJ+f+ zyO&}Ie;Mol@#xP}pXp1__AmJR?Niyr=(5hvqOihlGTdr(YZ>|>0oK51mkdAp30A`E z!>?e5o%ch)LixU&6|{dgVMjUTi%_cL?(a;tqoBR>({neL8?F>}WI7Ct_Nzr`hlJDz z`wOD`b1F87XkbfrY?&RqFVK7CwA3KBuk3CT8}4-q{^YDaf9$q|^wst|sa=byT{p5Hom@)w h%$!+`isFEHFC?@)G2in+$0wVYQb*Amq*gA+{{w*HvzY(@ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/tpool.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/tpool.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c1d2792055fe2f3975e95fb74694be565d50cf8 GIT binary patch literal 15170 zcmd6Odu$v>dS~}MdpJ*WI21`e*pjHnP!jc6?|LQ6vc;D~S*9h6>qOkmj)v1saYn-# zs=FzP3YDY0x_D@7SEJa0Xuvqjfe&k+9jp@sh%N{Yj&J`;E{7SD8;*LJM7Sux83!D+ zqVBL!aJc(@)zjT0in0xy?@14Nl@DKFbYpJ|o8uIarBn$i-6kkej8RArDKvLtdngtanTp62^Q(zOlNYx-tKd ze=IN*;EgeYL&4LWF>_ziSE%W`IJH$!#S2(Fb-uaQ!7;R{m6h+-`nafBNx}RmVQAJKlx_3OOrAB2%_iE`eIX|JrUAkAkAg3lYS$7{(@)ss`@0oNq zo6KhQuqtbb+{9SI=&TOv9yOmjBWt?PC`so|>+a(d@&x*xrDTZ^NYtBqU+EnfJlZ>$ z7(D!P@4=p9FTHd$aiV{y_lri<_oWC_Gq{kUjak}?zBfP|K29_1xJ-4+tO8^t-aL*= z+!Uuej&QH?=wlKam+{rMHuC0%sy&ai^grHGa(wJG$NvG2{}O-6F~w(skGGt1nC<9L zF7VXyYeKN~HdV=enn;yj53eSz8Pi`C``tU$x)w@31w)F68BCv^!guc`KB;p?c5eR)QIWIvLTypz32RG%Ia;CW{*ZcfW_)BiB$(%3K zZ077#Y1AxK&a~R)X3m_E=d?9uS*Fd*+36HN5qk!FBW5BGjQ*({jD<+YtCS#G1-A>}q{D9+> z4O+cfmT53^b~@#j94+|F5W`v)>{XrQuKu+}lFos+;|oHBFNmxa>KKm_>B(o$Dzxo- z=%A)4>0z*1gDiy;Cr>1Lf3y39?nj*HK6>!PiQW^sQ=L?GLB5bmq;n&AJ#bD|)O0>) zeEC2}DR~&=rUz14IjJN@Kv=S>2NOhMMpYt#LDCm==dqJ}bRUhxnks}Ex^sL&1FtyG zc!ihP0P%W%Vt7)MwLE>tJ-TyDJ*|k;+%+a^qq-}poK|(une&t?>rtW7d4W$LC`2`o zDChIa8Cg+C2p^#q4-r+#+v$rgHmtf4zaD>cTcP`&hYN%Y&r|{pl?|K9LR+D}&{J%m z^;ClO1;;&y*SG(6eXOXJ>Nn4xE7xx>bb(X@;cHL7{`4Y}7iV6)`qBdr=L=o)&G?GD zi^Ih&^FnK-b5BXwQ*crwAu0|cn(ae$^Y{l1i$dsHXeRX5Z_f)G@9kt9!NWz?5l4vL z4m1!)i2n0~2p4H)OrjE~Ukrm^IDPvU>l<&ZxxR*&(qgz_A>39Dx83~C=i&Hbq-7zp zxg6PiQ~6;1dlL&=_LaBnn~yy6Pxrj&_E06#_)%k5kb7U~TEl%7^mVm3KWpYGUUP7D z_de&p-^(LT5hFJPZE6^QOO22{H#p6zGtnDlfEpuYi1jQBX1hzyDW@%s&T;Bm&60~5 zi$LtoWK&l9?Zx=%;*HC5riycB4*A`&5fn@F}vQ(O}S{) zDaVM*Ca5V_W|diQr`CvWjcAgEDYs@ZI#W$k^4eOv-W*kOuvH{$`$|q4NLy#NwniA3 za{mA{e93dkb;)b9IdvPvbv0xx_GXrF+>{5a@tO)A&d6@P*=}X#%=MsM+$_uB@~}KC zA9c!of{VM7e+i_hBf9G~^fs}BNVG#t=ftFVj0tAZ(9gDuOgDg#JQ|?*0IKe7-w8bk0q5!aYDtINkk)g1%1-0 zi2qt%Jg+3j#bi#*4`<|*CZcjIFHK}+v`K2zCYRUPU|?Lic@5obCVjaio=tlssWnn){fEU z=Zp!fO}2}GlI?n4HCBtYsL-gyJQ(^YM(JeJC`8W6CW@HIXwuzKyC=pKQaSZd^~|a& zxGtEwxb94kjq5&A{Su>Cq3%oMsxvD71%1b1CTT zm?kUh&a9kM2>qbTf3LrHP@%h_9>yu>v;>5QJg!0f=zgOBeN$(Y-PFXZV3X6T92bK|R444Ez$Ax4B`g|2M*WY7rnv`AoV)JIA?Sbg ztqYN@<;d2#NXK-4#T$9+aM`=&edkYu?*-ov|HGHvA+V_&*mN`bLH!3^ z^MReLWPLfXem<~qQSj5u#e>DOvm54xwH1GGx_=2P;!+md#s@KO#^zr~<_0(A%vg*b z1=S$&lEY$5J}_Z0OE6(4(+FL;utw+1!W4fRb$`JBp~tS*Ea5at|ta%@1r&DJ5{{}pL#}2vn8;Z zz>=WVNFvmp2{0>7+>p!XOs2Ylco~S5*w?93gUW<_>eO~x0-IL_fdYs%K;DV$FE!XnrCq(Lijk86U?n7<@|i*Q~q`W&`JVe%v74 zK46qW+6Al+f_b<)KVmZkLt`Y2w8>tt8z*x^f(}P{1yu$7smBpbbN8A#A@thC%NL6~ zZpP-lTW$wKMOP`KTMG_G$i>llZ`)$HvH0?A z^IUjCN!V~FTwm}h$1rLNe^xMwap?+*Zg4U;#7i6ua{Q1}YLpyTco^lJFuk}X7h;d( zM(mYhk{2}sQUR$?@}aa&szdCT{D=cm0CA8RgF?(06lTVt2r~xNlObrRL2ej|N)5<0 zGBZ$2jtw#oQuiD}lKv;o3XB02-vwpds!R*?;=fT%*irbbzJCS@VkRZ;** zzp&&yDIIB&=<0Ut48#`=I|;&+8Jl3erS!;%%v78zcq2-Fj7>k5#NtOMh9Q-~!_ZSP zc*j`!^r!}mC!%9ROyuMX;~-F3dW1wt!%-%3Ig;Pylm6wdZwBJ=W)URQwI2dR( zwA<&CU>!&qZ5v8}!6soZux!3JS6I2dbH#bj1B^rO1HTi7HWicrZW}vEViBvM^3H%h7u&QAp;u4nLFy+LT z$<|;<{E#1jUC))kF+(@BxN_La8tVolhbb^SqC3%08N}dv$ZTx1PzfMn+r~9tNWc)0 z))I+}A!94e?>|8^m5funx&F$yV|%YxN5G{d7s-Lh(Y zp5v4z&9eJ`)i@`(8RrSEz!#haZo~-*BEW9!AeCwWN;?yBh-+sdX-GD>rJo@B{YMw> zUUA{B(>U>}md7(K&;hXAnOcmg&H&(7+nX@uux)A9!u_^Ez&2$=4+PN4NemNrGpVe6 zA9)2PGfuJPyvG*px(Do)ZkO<+KuSf)7)aL?WXIeq4z?c@TFMyCq19Xx^MNBW8Hz`1TLy!`nac-5OkY_IMfpcO9oy95)s|w`yl8va=Fx zEFAvtE|;(M9!%Md1^?sZL>R@$={J$7C8Vhp2??buU{1z^&db6SZ<;nt+;G+42HQ|$ zKkyVVmPo`MYy)*ClmeA#(!A)MLH?1=GTQ$oid7;7>=kyT{oT&DJ3nauS^UHJLdSFE zj^{p&&V_qRLa(u6TA>*TR<>e-;hSACXrj71U$$mw|J5}+4W!8e5Z!G^Z-UI^n@SEJ z_S&s}e~n^wJ)$SB{Zn}^e6%DSWvd@}>>Bz1>c9huge1%3sr>k4BJpAM#9=HR%Rh4D zXKC3DCVkjFO0@1LocZ(Rj^{sZp9>!<35Wjw!gAu|Y{7~`@Ymw@1TEAOiAJlrFInXN za>xE#(p@xllH^@ille*)~dGigDv0B!(W@m9- zOe9EQP){I&vf+=-HZHX6DC56>$6~bkoz|N@KOJ~);FF#|?f;|xQp@2_`Ogu(Ty8l$ z7ws{Ve@qx)klYwf=KNN;%q93Uobw8pH?~ozXS*n`ASlFn%J7 z@g)+_HDv`P#v2lqPRCHil#9q-44br7ZMTR*mCWEPFqCJ@9na2%50r!hE3c8TZt0sA z9wvVMgnQyDL00kw(kU&;0kh*H)OCrLYEhTb`0r_@8&+N^QCBJ4eM|kkhQNqi%|Ll} zq#`oVtXCwjDfR$jg3UA4!2(ekRck@SlUhPP3$-|49SFYu5u>a0(g-{aN3TuJOwM-W z{ir0gJ$?cfNZcPNL^kDd1NUDE+J?E1h* z3qtn!D}s<-?T9@z6rZ}W_xj#9ey1d?{pNwwUBj@kt_&U2Mc585%1Ry(TI#b^^q>~X zs`mWGzbFaoz644MeZKw{UgGI8z0x&{y%yr4^@#kle2z>S!_^v!pJJ?z-5RPYkRz=< z!WKY<)bNq}GW-4~T4UTeiC}%PogT;ENE94+GAK6QXu00<`g4`&s=^C*qs_AoZ|*Gx zsu$>5T(>m-qhD+Bw!_iN0{u(0eBA}Zn6~0!DsN))i**JeP%91~sx8LKOnv_2_D@`Y z>iZ+#TzFqe*vF1B?)Z>X-a<12nOD(0nG7oMUZ_hXj3-0np=h3+NM@@wKx-+V0yUnZ zj%kYMR>9P3g_KQ&6fveVF!`vEvK z>n`-~T#OK`3I#q!@PDjv9Ke$}6fxC%1iquD@OYKC9Sf>Y=B<)L;<66%Q_5JcM%J*L zn`X6;HI(Y9SI*{todT7K2a_E;^SGIMsCdD_mKIh*p#)9m4X3oIQjG~YGb zWS%i|cB<_+^OA4MbrBrk<2noL-MBQm6V~2NxibgN)^=L66J2I$rq|5b>6GOg&U04> zT%2~utjmxJvMgvv%mQ;g$<5kJW?AN#nX^;8bXVmX58HLzKQPGrA}((2PSP8viYJ-$ z$Ryr6Pa94a@Jb%WLl%2%W*!+A|IHAT2^i%K{|tI+Hs7Z325jW3Z?Nn+m>$efQ4`Es zbY*WF#!z@E=pL0*XPCL4nRSdwWf88hX(gXyufjFM;K^G_GpCj(IYD=R@jnsbyzU)K zUQp8)<+$UbZ}*<3J9pvVZo?mh*xAK!)pM4uDX~e7J9LkRmuPa|C2<&!>!Ad57D*(t zwC+iz8~tXP*9H8J~S#=>ozbq_SagXvIHFhz_NO#Ie5=lu?bUv-~nrY0oOnL{_v zpkZUX7ZGiRqy_tY)4S6h8|h{^}8pUKLrm+@e_uQf7@=YNvKf zTb$Tlo|l{Kd}h7f%gmW`61>TnOTK5o2~P z`uUE*J%2`p#BuQw?1Vg1L@J<_KWbsEqhgA76C--Haq;XLhbuGT~g&I)|sgc;$-byOa`D@1;;zThV=R$ub3d0+nN znfK1TKSsa!s9$VaeItE6eN!%`=bE<994Z_yIx2zSwZ56Y;-0HVDlxp@6%H01_%%mO z=YKu+Ai@d3lDBzLh%N}tWuf_<*u2oT=tWrzIeG_X0(f?=j}<&Cl~n=}@WEK?<$+&6 zXyGC;a`>*l9f*(*c>P0_?iqivzq4mO_w)6>o*mAgZ|4y|;-!sUGWdjm3>gpUw2fOV z&qOeUyI{Iw)*9aYXs=N8J=$oxQ^7wz(tNq!xDbCEVoM|ACf@Dg{G%L11ISeTZbs3J zpA0zis7Q+xLYl80 zV7~~fUf}6B2;VYLl&Kx0{W4^Q11tSXt*S*C=uFy6jbg$y0774bG@hs2Mv6%7HC)fg zAg_B2R~B|9X7X4U6!|piEDFPFw&Tn0@ehUlhK1pPvVbw@MAh#j1`_c6lF0G+O_Gxj z|I*d8OD%x-yi=i56=p^j;PCD2k@z5Rns z$-j46xFZCn-FJK~g;fi_ma?y9cI%vPL1--tt<&y`oxKxk00=be?LfHbxKVe#?ndZ(sMNOi z)}Fb@fw{nQm@Cp);u@#j1$U*Psl+u+dkRgJ`ljg+d8!KcJz+j}zrBTTzyIAfK6Jl_ zkMa!e?-ibN2(PtWZu^x(|IQVnO$|R}cJSYI^|Wz6Z}atRcm8}EkNAH9eP*^7 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/__pycache__/websocket.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/__pycache__/websocket.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bdb4c014ffcd16940cef7ad2b94a490a924e2d3c GIT binary patch literal 40147 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 Fk^o2n5{>`> literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/backdoor.py b/venv/lib/python3.12/site-packages/eventlet/backdoor.py new file mode 100644 index 0000000..f49a969 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/backdoor.py @@ -0,0 +1,144 @@ +from __future__ import print_function + +from code import InteractiveConsole +import errno +import socket +import sys +import errno +import traceback + +import eventlet +from eventlet import hubs +from eventlet.support import greenlets, get_errno + +try: + sys.ps1 +except AttributeError: + sys.ps1 = '>>> ' +try: + sys.ps2 +except AttributeError: + sys.ps2 = '... ' + + +class FileProxy(object): + def __init__(self, f): + self.f = f + + def isatty(self): + return True + + def flush(self): + pass + + def write(self, data, *a, **kw): + try: + self.f.write(data, *a, **kw) + self.f.flush() + except socket.error as e: + if get_errno(e) != errno.EPIPE: + raise + + def readline(self, *a): + return self.f.readline(*a).replace('\r\n', '\n') + + def __getattr__(self, attr): + return getattr(self.f, attr) + + +# @@tavis: the `locals` args below mask the built-in function. Should +# be renamed. +class SocketConsole(greenlets.greenlet): + def __init__(self, desc, hostport, locals): + self.hostport = hostport + self.locals = locals + # mangle the socket + self.desc = FileProxy(desc) + greenlets.greenlet.__init__(self) + + def run(self): + try: + console = InteractiveConsole(self.locals) + console.interact() + finally: + self.switch_out() + self.finalize() + + def switch(self, *args, **kw): + self.saved = sys.stdin, sys.stderr, sys.stdout + sys.stdin = sys.stdout = sys.stderr = self.desc + greenlets.greenlet.switch(self, *args, **kw) + + def switch_out(self): + sys.stdin, sys.stderr, sys.stdout = self.saved + + def finalize(self): + # restore the state of the socket + self.desc = None + if len(self.hostport) >= 2: + host = self.hostport[0] + port = self.hostport[1] + print("backdoor closed to %s:%s" % (host, port,)) + else: + print('backdoor closed') + + +def backdoor_server(sock, locals=None): + """ Blocking function that runs a backdoor server on the socket *sock*, + accepting connections and running backdoor consoles for each client that + connects. + + The *locals* argument is a dictionary that will be included in the locals() + of the interpreters. It can be convenient to stick important application + variables in here. + """ + listening_on = sock.getsockname() + if sock.family == socket.AF_INET: + # Expand result to IP + port + listening_on = '%s:%s' % listening_on + elif sock.family == socket.AF_INET6: + ip, port, _, _ = listening_on + listening_on = '%s:%s' % (ip, port,) + # No action needed if sock.family == socket.AF_UNIX + + print("backdoor server listening on %s" % (listening_on,)) + try: + while True: + socketpair = None + try: + socketpair = sock.accept() + backdoor(socketpair, locals) + except socket.error as e: + # Broken pipe means it was shutdown + if get_errno(e) != errno.EPIPE: + raise + finally: + if socketpair: + socketpair[0].close() + finally: + sock.close() + + +def backdoor(conn_info, locals=None): + """Sets up an interactive console on a socket with a single connected + client. This does not block the caller, as it spawns a new greenlet to + handle the console. This is meant to be called from within an accept loop + (such as backdoor_server). + """ + conn, addr = conn_info + if conn.family == socket.AF_INET: + host, port = addr + print("backdoor to %s:%s" % (host, port)) + elif conn.family == socket.AF_INET6: + host, port, _, _ = addr + print("backdoor to %s:%s" % (host, port)) + else: + print('backdoor opened') + fl = conn.makefile("rw") + console = SocketConsole(fl, addr, locals) + hub = hubs.get_hub() + hub.schedule_call_global(0, console.switch) + + +if __name__ == '__main__': + backdoor_server(eventlet.listen(('127.0.0.1', 9000)), {}) diff --git a/venv/lib/python3.12/site-packages/eventlet/convenience.py b/venv/lib/python3.12/site-packages/eventlet/convenience.py new file mode 100644 index 0000000..5f99ade --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/convenience.py @@ -0,0 +1,189 @@ +import sys +import warnings + +from eventlet import greenpool +from eventlet import greenthread +from eventlet import support +from eventlet.green import socket +from eventlet.support import greenlets as greenlet + + +def connect(addr, family=socket.AF_INET, bind=None): + """Convenience function for opening client sockets. + + :param addr: Address of the server to connect to. For TCP sockets, this is a (host, port) tuple. + :param family: Socket family, optional. See :mod:`socket` documentation for available families. + :param bind: Local address to bind to, optional. + :return: The connected green socket object. + """ + sock = socket.socket(family, socket.SOCK_STREAM) + if bind is not None: + sock.bind(bind) + sock.connect(addr) + return sock + + +class ReuseRandomPortWarning(Warning): + pass + + +class ReusePortUnavailableWarning(Warning): + pass + + +def listen(addr, family=socket.AF_INET, backlog=50, reuse_addr=True, reuse_port=None): + """Convenience function for opening server sockets. This + socket can be used in :func:`~eventlet.serve` or a custom ``accept()`` loop. + + Sets SO_REUSEADDR on the socket to save on annoyance. + + :param addr: Address to listen on. For TCP sockets, this is a (host, port) tuple. + :param family: Socket family, optional. See :mod:`socket` documentation for available families. + :param backlog: + + The maximum number of queued connections. Should be at least 1; the maximum + value is system-dependent. + + :return: The listening green socket object. + """ + sock = socket.socket(family, socket.SOCK_STREAM) + if reuse_addr and sys.platform[:3] != 'win': + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if family in (socket.AF_INET, socket.AF_INET6) and addr[1] == 0: + if reuse_port: + warnings.warn( + '''listen on random port (0) with SO_REUSEPORT is dangerous. + Double check your intent. + Example problem: https://github.com/eventlet/eventlet/issues/411''', + ReuseRandomPortWarning, stacklevel=3) + elif reuse_port is None: + reuse_port = True + if reuse_port and hasattr(socket, 'SO_REUSEPORT'): + # NOTE(zhengwei): linux kernel >= 3.9 + try: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + # OSError is enough on Python 3+ + except (OSError, socket.error) as ex: + if support.get_errno(ex) in (22, 92): + # A famous platform defines unsupported socket option. + # https://github.com/eventlet/eventlet/issues/380 + # https://github.com/eventlet/eventlet/issues/418 + warnings.warn( + '''socket.SO_REUSEPORT is defined but not supported. + On Windows: known bug, wontfix. + On other systems: please comment in the issue linked below. + More information: https://github.com/eventlet/eventlet/issues/380''', + ReusePortUnavailableWarning, stacklevel=3) + + sock.bind(addr) + sock.listen(backlog) + return sock + + +class StopServe(Exception): + """Exception class used for quitting :func:`~eventlet.serve` gracefully.""" + pass + + +def _stop_checker(t, server_gt, conn): + try: + try: + t.wait() + finally: + conn.close() + except greenlet.GreenletExit: + pass + except Exception: + greenthread.kill(server_gt, *sys.exc_info()) + + +def serve(sock, handle, concurrency=1000): + """Runs a server on the supplied socket. Calls the function *handle* in a + separate greenthread for every incoming client connection. *handle* takes + two arguments: the client socket object, and the client address:: + + def myhandle(client_sock, client_addr): + print("client connected", client_addr) + + eventlet.serve(eventlet.listen(('127.0.0.1', 9999)), myhandle) + + Returning from *handle* closes the client socket. + + :func:`serve` blocks the calling greenthread; it won't return until + the server completes. If you desire an immediate return, + spawn a new greenthread for :func:`serve`. + + Any uncaught exceptions raised in *handle* are raised as exceptions + from :func:`serve`, terminating the server, so be sure to be aware of the + exceptions your application can raise. The return value of *handle* is + ignored. + + Raise a :class:`~eventlet.StopServe` exception to gracefully terminate the + server -- that's the only way to get the server() function to return rather + than raise. + + The value in *concurrency* controls the maximum number of + greenthreads that will be open at any time handling requests. When + the server hits the concurrency limit, it stops accepting new + connections until the existing ones complete. + """ + pool = greenpool.GreenPool(concurrency) + server_gt = greenthread.getcurrent() + + while True: + try: + conn, addr = sock.accept() + gt = pool.spawn(handle, conn, addr) + gt.link(_stop_checker, server_gt, conn) + conn, addr, gt = None, None, None + except StopServe: + return + + +def wrap_ssl(sock, *a, **kw): + """Convenience function for converting a regular socket into an + SSL socket. Has the same interface as :func:`ssl.wrap_socket`, + but can also use PyOpenSSL. Though, note that it ignores the + `cert_reqs`, `ssl_version`, `ca_certs`, `do_handshake_on_connect`, + and `suppress_ragged_eofs` arguments when using PyOpenSSL. + + The preferred idiom is to call wrap_ssl directly on the creation + method, e.g., ``wrap_ssl(connect(addr))`` or + ``wrap_ssl(listen(addr), server_side=True)``. This way there is + no "naked" socket sitting around to accidentally corrupt the SSL + session. + + :return Green SSL object. + """ + return wrap_ssl_impl(sock, *a, **kw) + +try: + from eventlet.green import ssl + wrap_ssl_impl = ssl.wrap_socket +except ImportError: + # trying PyOpenSSL + try: + from eventlet.green.OpenSSL import SSL + except ImportError: + def wrap_ssl_impl(*a, **kw): + raise ImportError( + "To use SSL with Eventlet, you must install PyOpenSSL or use Python 2.7 or later.") + else: + def wrap_ssl_impl(sock, keyfile=None, certfile=None, server_side=False, + cert_reqs=None, ssl_version=None, ca_certs=None, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, ciphers=None): + # theoretically the ssl_version could be respected in this line + context = SSL.Context(SSL.SSLv23_METHOD) + if certfile is not None: + context.use_certificate_file(certfile) + if keyfile is not None: + context.use_privatekey_file(keyfile) + context.set_verify(SSL.VERIFY_NONE, lambda *x: True) + + connection = SSL.Connection(context, sock) + if server_side: + connection.set_accept_state() + else: + connection.set_connect_state() + return connection diff --git a/venv/lib/python3.12/site-packages/eventlet/corolocal.py b/venv/lib/python3.12/site-packages/eventlet/corolocal.py new file mode 100644 index 0000000..1a1a8df --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/corolocal.py @@ -0,0 +1,53 @@ +import weakref + +from eventlet import greenthread + +__all__ = ['get_ident', 'local'] + + +def get_ident(): + """ Returns ``id()`` of current greenlet. Useful for debugging.""" + return id(greenthread.getcurrent()) + + +# the entire purpose of this class is to store off the constructor +# arguments in a local variable without calling __init__ directly +class _localbase(object): + __slots__ = '_local__args', '_local__greens' + + def __new__(cls, *args, **kw): + self = object.__new__(cls) + object.__setattr__(self, '_local__args', (args, kw)) + object.__setattr__(self, '_local__greens', weakref.WeakKeyDictionary()) + if (args or kw) and (cls.__init__ is object.__init__): + raise TypeError("Initialization arguments are not supported") + return self + + +def _patch(thrl): + greens = object.__getattribute__(thrl, '_local__greens') + # until we can store the localdict on greenlets themselves, + # we store it in _local__greens on the local object + cur = greenthread.getcurrent() + if cur not in greens: + # must be the first time we've seen this greenlet, call __init__ + greens[cur] = {} + cls = type(thrl) + if cls.__init__ is not object.__init__: + args, kw = object.__getattribute__(thrl, '_local__args') + thrl.__init__(*args, **kw) + object.__setattr__(thrl, '__dict__', greens[cur]) + + +class local(_localbase): + def __getattribute__(self, attr): + _patch(self) + return object.__getattribute__(self, attr) + + def __setattr__(self, attr, value): + _patch(self) + return object.__setattr__(self, attr, value) + + def __delattr__(self, attr): + _patch(self) + return object.__delattr__(self, attr) diff --git a/venv/lib/python3.12/site-packages/eventlet/coros.py b/venv/lib/python3.12/site-packages/eventlet/coros.py new file mode 100644 index 0000000..431e6f0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/coros.py @@ -0,0 +1,61 @@ +from __future__ import print_function + +from eventlet import event as _event + + +class metaphore(object): + """This is sort of an inverse semaphore: a counter that starts at 0 and + waits only if nonzero. It's used to implement a "wait for all" scenario. + + >>> from eventlet import coros, spawn_n + >>> count = coros.metaphore() + >>> count.wait() + >>> def decrementer(count, id): + ... print("{0} decrementing".format(id)) + ... count.dec() + ... + >>> _ = spawn_n(decrementer, count, 'A') + >>> _ = spawn_n(decrementer, count, 'B') + >>> count.inc(2) + >>> count.wait() + A decrementing + B decrementing + """ + + def __init__(self): + self.counter = 0 + self.event = _event.Event() + # send() right away, else we'd wait on the default 0 count! + self.event.send() + + def inc(self, by=1): + """Increment our counter. If this transitions the counter from zero to + nonzero, make any subsequent :meth:`wait` call wait. + """ + assert by > 0 + self.counter += by + if self.counter == by: + # If we just incremented self.counter by 'by', and the new count + # equals 'by', then the old value of self.counter was 0. + # Transitioning from 0 to a nonzero value means wait() must + # actually wait. + self.event.reset() + + def dec(self, by=1): + """Decrement our counter. If this transitions the counter from nonzero + to zero, a current or subsequent wait() call need no longer wait. + """ + assert by > 0 + self.counter -= by + if self.counter <= 0: + # Don't leave self.counter < 0, that will screw things up in + # future calls. + self.counter = 0 + # Transitioning from nonzero to 0 means wait() need no longer wait. + self.event.send() + + def wait(self): + """Suspend the caller only if our count is nonzero. In that case, + resume the caller once the count decrements to zero again. + """ + self.event.wait() diff --git a/venv/lib/python3.12/site-packages/eventlet/dagpool.py b/venv/lib/python3.12/site-packages/eventlet/dagpool.py new file mode 100644 index 0000000..68bb49a --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/dagpool.py @@ -0,0 +1,602 @@ +# @file dagpool.py +# @author Nat Goodspeed +# @date 2016-08-08 +# @brief Provide DAGPool class + +from eventlet.event import Event +from eventlet import greenthread +import six +import collections + + +# value distinguished from any other Python value including None +_MISSING = object() + + +class Collision(Exception): + """ + DAGPool raises Collision when you try to launch two greenthreads with the + same key, or post() a result for a key corresponding to a greenthread, or + post() twice for the same key. As with KeyError, str(collision) names the + key in question. + """ + pass + + +class PropagateError(Exception): + """ + When a DAGPool greenthread terminates with an exception instead of + returning a result, attempting to retrieve its value raises + PropagateError. + + Attributes: + + key + the key of the greenthread which raised the exception + + exc + the exception object raised by the greenthread + """ + def __init__(self, key, exc): + # initialize base class with a reasonable string message + msg = "PropagateError({0}): {1}: {2}" \ + .format(key, exc.__class__.__name__, exc) + super(PropagateError, self).__init__(msg) + self.msg = msg + # Unless we set args, this is unpickleable: + # https://bugs.python.org/issue1692335 + self.args = (key, exc) + self.key = key + self.exc = exc + + def __str__(self): + return self.msg + + +class DAGPool(object): + """ + A DAGPool is a pool that constrains greenthreads, not by max concurrency, + but by data dependencies. + + This is a way to implement general DAG dependencies. A simple dependency + tree (flowing in either direction) can straightforwardly be implemented + using recursion and (e.g.) + :meth:`GreenThread.imap() `. + What gets complicated is when a given node depends on several other nodes + as well as contributing to several other nodes. + + With DAGPool, you concurrently launch all applicable greenthreads; each + will proceed as soon as it has all required inputs. The DAG is implicit in + which items are required by each greenthread. + + Each greenthread is launched in a DAGPool with a key: any value that can + serve as a Python dict key. The caller also specifies an iterable of other + keys on which this greenthread depends. This iterable may be empty. + + The greenthread callable must accept (key, results), where: + + key + is its own key + + results + is an iterable of (key, value) pairs. + + A newly-launched DAGPool greenthread is entered immediately, and can + perform any necessary setup work. At some point it will iterate over the + (key, value) pairs from the passed 'results' iterable. Doing so blocks the + greenthread until a value is available for each of the keys specified in + its initial dependencies iterable. These (key, value) pairs are delivered + in chronological order, *not* the order in which they are initially + specified: each value will be delivered as soon as it becomes available. + + The value returned by a DAGPool greenthread becomes the value for its + key, which unblocks any other greenthreads waiting on that key. + + If a DAGPool greenthread terminates with an exception instead of returning + a value, attempting to retrieve the value raises :class:`PropagateError`, + which binds the key of the original greenthread and the original + exception. Unless the greenthread attempting to retrieve the value handles + PropagateError, that exception will in turn be wrapped in a PropagateError + of its own, and so forth. The code that ultimately handles PropagateError + can follow the chain of PropagateError.exc attributes to discover the flow + of that exception through the DAG of greenthreads. + + External greenthreads may also interact with a DAGPool. See :meth:`wait_each`, + :meth:`waitall`, :meth:`post`. + + It is not recommended to constrain external DAGPool producer greenthreads + in a :class:`GreenPool `: it may be hard to + provably avoid deadlock. + + .. automethod:: __init__ + .. automethod:: __getitem__ + """ + + _Coro = collections.namedtuple("_Coro", ("greenthread", "pending")) + + def __init__(self, preload={}): + """ + DAGPool can be prepopulated with an initial dict or iterable of (key, + value) pairs. These (key, value) pairs are of course immediately + available for any greenthread that depends on any of those keys. + """ + try: + # If a dict is passed, copy it. Don't risk a subsequent + # modification to passed dict affecting our internal state. + iteritems = six.iteritems(preload) + except AttributeError: + # Not a dict, just an iterable of (key, value) pairs + iteritems = preload + + # Load the initial dict + self.values = dict(iteritems) + + # track greenthreads + self.coros = {} + + # The key to blocking greenthreads is the Event. + self.event = Event() + + def waitall(self): + """ + waitall() blocks the calling greenthread until there is a value for + every DAGPool greenthread launched by :meth:`spawn`. It returns a dict + containing all :class:`preload data `, all data from + :meth:`post` and all values returned by spawned greenthreads. + + See also :meth:`wait`. + """ + # waitall() is an alias for compatibility with GreenPool + return self.wait() + + def wait(self, keys=_MISSING): + """ + *keys* is an optional iterable of keys. If you omit the argument, it + waits for all the keys from :class:`preload data `, from + :meth:`post` calls and from :meth:`spawn` calls: in other words, all + the keys of which this DAGPool is aware. + + wait() blocks the calling greenthread until all of the relevant keys + have values. wait() returns a dict whose keys are the relevant keys, + and whose values come from the *preload* data, from values returned by + DAGPool greenthreads or from :meth:`post` calls. + + If a DAGPool greenthread terminates with an exception, wait() will + raise :class:`PropagateError` wrapping that exception. If more than + one greenthread terminates with an exception, it is indeterminate + which one wait() will raise. + + If an external greenthread posts a :class:`PropagateError` instance, + wait() will raise that PropagateError. If more than one greenthread + posts PropagateError, it is indeterminate which one wait() will raise. + + See also :meth:`wait_each_success`, :meth:`wait_each_exception`. + """ + # This is mostly redundant with wait_each() functionality. + return dict(self.wait_each(keys)) + + def wait_each(self, keys=_MISSING): + """ + *keys* is an optional iterable of keys. If you omit the argument, it + waits for all the keys from :class:`preload data `, from + :meth:`post` calls and from :meth:`spawn` calls: in other words, all + the keys of which this DAGPool is aware. + + wait_each() is a generator producing (key, value) pairs as a value + becomes available for each requested key. wait_each() blocks the + calling greenthread until the next value becomes available. If the + DAGPool was prepopulated with values for any of the relevant keys, of + course those can be delivered immediately without waiting. + + Delivery order is intentionally decoupled from the initial sequence of + keys: each value is delivered as soon as it becomes available. If + multiple keys are available at the same time, wait_each() delivers + each of the ready ones in arbitrary order before blocking again. + + The DAGPool does not distinguish between a value returned by one of + its own greenthreads and one provided by a :meth:`post` call or *preload* data. + + The wait_each() generator terminates (raises StopIteration) when all + specified keys have been delivered. Thus, typical usage might be: + + :: + + for key, value in dagpool.wait_each(keys): + # process this ready key and value + # continue processing now that we've gotten values for all keys + + By implication, if you pass wait_each() an empty iterable of keys, it + returns immediately without yielding anything. + + If the value to be delivered is a :class:`PropagateError` exception object, the + generator raises that PropagateError instead of yielding it. + + See also :meth:`wait_each_success`, :meth:`wait_each_exception`. + """ + # Build a local set() and then call _wait_each(). + return self._wait_each(self._get_keyset_for_wait_each(keys)) + + def wait_each_success(self, keys=_MISSING): + """ + wait_each_success() filters results so that only success values are + yielded. In other words, unlike :meth:`wait_each`, wait_each_success() + will not raise :class:`PropagateError`. Not every provided (or + defaulted) key will necessarily be represented, though naturally the + generator will not finish until all have completed. + + In all other respects, wait_each_success() behaves like :meth:`wait_each`. + """ + for key, value in self._wait_each_raw(self._get_keyset_for_wait_each(keys)): + if not isinstance(value, PropagateError): + yield key, value + + def wait_each_exception(self, keys=_MISSING): + """ + wait_each_exception() filters results so that only exceptions are + yielded. Not every provided (or defaulted) key will necessarily be + represented, though naturally the generator will not finish until + all have completed. + + Unlike other DAGPool methods, wait_each_exception() simply yields + :class:`PropagateError` instances as values rather than raising them. + + In all other respects, wait_each_exception() behaves like :meth:`wait_each`. + """ + for key, value in self._wait_each_raw(self._get_keyset_for_wait_each(keys)): + if isinstance(value, PropagateError): + yield key, value + + def _get_keyset_for_wait_each(self, keys): + """ + wait_each(), wait_each_success() and wait_each_exception() promise + that if you pass an iterable of keys, the method will wait for results + from those keys -- but if you omit the keys argument, the method will + wait for results from all known keys. This helper implements that + distinction, returning a set() of the relevant keys. + """ + if keys is not _MISSING: + return set(keys) + else: + # keys arg omitted -- use all the keys we know about + return set(six.iterkeys(self.coros)) | set(six.iterkeys(self.values)) + + def _wait_each(self, pending): + """ + When _wait_each() encounters a value of PropagateError, it raises it. + + In all other respects, _wait_each() behaves like _wait_each_raw(). + """ + for key, value in self._wait_each_raw(pending): + yield key, self._value_or_raise(value) + + @staticmethod + def _value_or_raise(value): + # Most methods attempting to deliver PropagateError should raise that + # instead of simply returning it. + if isinstance(value, PropagateError): + raise value + return value + + def _wait_each_raw(self, pending): + """ + pending is a set() of keys for which we intend to wait. THIS SET WILL + BE DESTRUCTIVELY MODIFIED: as each key acquires a value, that key will + be removed from the passed 'pending' set. + + _wait_each_raw() does not treat a PropagateError instance specially: + it will be yielded to the caller like any other value. + + In all other respects, _wait_each_raw() behaves like wait_each(). + """ + while True: + # Before even waiting, show caller any (key, value) pairs that + # are already available. Copy 'pending' because we want to be able + # to remove items from the original set while iterating. + for key in pending.copy(): + value = self.values.get(key, _MISSING) + if value is not _MISSING: + # found one, it's no longer pending + pending.remove(key) + yield (key, value) + + if not pending: + # Once we've yielded all the caller's keys, done. + break + + # There are still more keys pending, so wait. + self.event.wait() + + def spawn(self, key, depends, function, *args, **kwds): + """ + Launch the passed *function(key, results, ...)* as a greenthread, + passing it: + + - the specified *key* + - an iterable of (key, value) pairs + - whatever other positional args or keywords you specify. + + Iterating over the *results* iterable behaves like calling + :meth:`wait_each(depends) `. + + Returning from *function()* behaves like + :meth:`post(key, return_value) `. + + If *function()* terminates with an exception, that exception is wrapped + in :class:`PropagateError` with the greenthread's *key* and (effectively) posted + as the value for that key. Attempting to retrieve that value will + raise that PropagateError. + + Thus, if the greenthread with key 'a' terminates with an exception, + and greenthread 'b' depends on 'a', when greenthread 'b' attempts to + iterate through its *results* argument, it will encounter + PropagateError. So by default, an uncaught exception will propagate + through all the downstream dependencies. + + If you pass :meth:`spawn` a key already passed to spawn() or :meth:`post`, spawn() + raises :class:`Collision`. + """ + if key in self.coros or key in self.values: + raise Collision(key) + + # The order is a bit tricky. First construct the set() of keys. + pending = set(depends) + # It's important that we pass to _wait_each() the same 'pending' set() + # that we store in self.coros for this key. The generator-iterator + # returned by _wait_each() becomes the function's 'results' iterable. + newcoro = greenthread.spawn(self._wrapper, function, key, + self._wait_each(pending), + *args, **kwds) + # Also capture the same (!) set in the new _Coro object for this key. + # We must be able to observe ready keys being removed from the set. + self.coros[key] = self._Coro(newcoro, pending) + + def _wrapper(self, function, key, results, *args, **kwds): + """ + This wrapper runs the top-level function in a DAGPool greenthread, + posting its return value (or PropagateError) to the DAGPool. + """ + try: + # call our passed function + result = function(key, results, *args, **kwds) + except Exception as err: + # Wrap any exception it may raise in a PropagateError. + result = PropagateError(key, err) + finally: + # function() has returned (or terminated with an exception). We no + # longer need to track this greenthread in self.coros. Remove it + # first so post() won't complain about a running greenthread. + del self.coros[key] + + try: + # as advertised, try to post() our return value + self.post(key, result) + except Collision: + # if we've already post()ed a result, oh well + pass + + # also, in case anyone cares... + return result + + def spawn_many(self, depends, function, *args, **kwds): + """ + spawn_many() accepts a single *function* whose parameters are the same + as for :meth:`spawn`. + + The difference is that spawn_many() accepts a dependency dict + *depends*. A new greenthread is spawned for each key in the dict. That + dict key's value should be an iterable of other keys on which this + greenthread depends. + + If the *depends* dict contains any key already passed to :meth:`spawn` + or :meth:`post`, spawn_many() raises :class:`Collision`. It is + indeterminate how many of the other keys in *depends* will have + successfully spawned greenthreads. + """ + # Iterate over 'depends' items, relying on self.spawn() not to + # context-switch so no one can modify 'depends' along the way. + for key, deps in six.iteritems(depends): + self.spawn(key, deps, function, *args, **kwds) + + def kill(self, key): + """ + Kill the greenthread that was spawned with the specified *key*. + + If no such greenthread was spawned, raise KeyError. + """ + # let KeyError, if any, propagate + self.coros[key].greenthread.kill() + # once killed, remove it + del self.coros[key] + + def post(self, key, value, replace=False): + """ + post(key, value) stores the passed *value* for the passed *key*. It + then causes each greenthread blocked on its results iterable, or on + :meth:`wait_each(keys) `, to check for new values. + A waiting greenthread might not literally resume on every single + post() of a relevant key, but the first post() of a relevant key + ensures that it will resume eventually, and when it does it will catch + up with all relevant post() calls. + + Calling post(key, value) when there is a running greenthread with that + same *key* raises :class:`Collision`. If you must post(key, value) instead of + letting the greenthread run to completion, you must first call + :meth:`kill(key) `. + + The DAGPool implicitly post()s the return value from each of its + greenthreads. But a greenthread may explicitly post() a value for its + own key, which will cause its return value to be discarded. + + Calling post(key, value, replace=False) (the default *replace*) when a + value for that key has already been posted, by any means, raises + :class:`Collision`. + + Calling post(key, value, replace=True) when a value for that key has + already been posted, by any means, replaces the previously-stored + value. However, that may make it complicated to reason about the + behavior of greenthreads waiting on that key. + + After a post(key, value1) followed by post(key, value2, replace=True), + it is unspecified which pending :meth:`wait_each([key...]) ` + calls (or greenthreads iterating over *results* involving that key) + will observe *value1* versus *value2*. It is guaranteed that + subsequent wait_each([key...]) calls (or greenthreads spawned after + that point) will observe *value2*. + + A successful call to + post(key, :class:`PropagateError(key, ExceptionSubclass) `) + ensures that any subsequent attempt to retrieve that key's value will + raise that PropagateError instance. + """ + # First, check if we're trying to post() to a key with a running + # greenthread. + # A DAGPool greenthread is explicitly permitted to post() to its + # OWN key. + coro = self.coros.get(key, _MISSING) + if coro is not _MISSING and coro.greenthread is not greenthread.getcurrent(): + # oh oh, trying to post a value for running greenthread from + # some other greenthread + raise Collision(key) + + # Here, either we're posting a value for a key with no greenthread or + # we're posting from that greenthread itself. + + # Has somebody already post()ed a value for this key? + # Unless replace == True, this is a problem. + if key in self.values and not replace: + raise Collision(key) + + # Either we've never before posted a value for this key, or we're + # posting with replace == True. + + # update our database + self.values[key] = value + # and wake up pending waiters + self.event.send() + # The comment in Event.reset() says: "it's better to create a new + # event rather than reset an old one". Okay, fine. We do want to be + # able to support new waiters, so create a new Event. + self.event = Event() + + def __getitem__(self, key): + """ + __getitem__(key) (aka dagpool[key]) blocks until *key* has a value, + then delivers that value. + """ + # This is a degenerate case of wait_each(). Construct a tuple + # containing only this 'key'. wait_each() will yield exactly one (key, + # value) pair. Return just its value. + for _, value in self.wait_each((key,)): + return value + + def get(self, key, default=None): + """ + get() returns the value for *key*. If *key* does not yet have a value, + get() returns *default*. + """ + return self._value_or_raise(self.values.get(key, default)) + + def keys(self): + """ + Return a snapshot tuple of keys for which we currently have values. + """ + # Explicitly return a copy rather than an iterator: don't assume our + # caller will finish iterating before new values are posted. + return tuple(six.iterkeys(self.values)) + + def items(self): + """ + Return a snapshot tuple of currently-available (key, value) pairs. + """ + # Don't assume our caller will finish iterating before new values are + # posted. + return tuple((key, self._value_or_raise(value)) + for key, value in six.iteritems(self.values)) + + def running(self): + """ + Return number of running DAGPool greenthreads. This includes + greenthreads blocked while iterating through their *results* iterable, + that is, greenthreads waiting on values from other keys. + """ + return len(self.coros) + + def running_keys(self): + """ + Return keys for running DAGPool greenthreads. This includes + greenthreads blocked while iterating through their *results* iterable, + that is, greenthreads waiting on values from other keys. + """ + # return snapshot; don't assume caller will finish iterating before we + # next modify self.coros + return tuple(six.iterkeys(self.coros)) + + def waiting(self): + """ + Return number of waiting DAGPool greenthreads, that is, greenthreads + still waiting on values from other keys. This explicitly does *not* + include external greenthreads waiting on :meth:`wait`, + :meth:`waitall`, :meth:`wait_each`. + """ + # n.b. if Event would provide a count of its waiters, we could say + # something about external greenthreads as well. + # The logic to determine this count is exactly the same as the general + # waiting_for() call. + return len(self.waiting_for()) + + # Use _MISSING instead of None as the default 'key' param so we can permit + # None as a supported key. + def waiting_for(self, key=_MISSING): + """ + waiting_for(key) returns a set() of the keys for which the DAGPool + greenthread spawned with that *key* is still waiting. If you pass a + *key* for which no greenthread was spawned, waiting_for() raises + KeyError. + + waiting_for() without argument returns a dict. Its keys are the keys + of DAGPool greenthreads still waiting on one or more values. In the + returned dict, the value of each such key is the set of other keys for + which that greenthread is still waiting. + + This method allows diagnosing a "hung" DAGPool. If certain + greenthreads are making no progress, it's possible that they are + waiting on keys for which there is no greenthread and no :meth:`post` data. + """ + # We may have greenthreads whose 'pending' entry indicates they're + # waiting on some keys even though values have now been posted for + # some or all of those keys, because those greenthreads have not yet + # regained control since values were posted. So make a point of + # excluding values that are now available. + available = set(six.iterkeys(self.values)) + + if key is not _MISSING: + # waiting_for(key) is semantically different than waiting_for(). + # It's just that they both seem to want the same method name. + coro = self.coros.get(key, _MISSING) + if coro is _MISSING: + # Hmm, no running greenthread with this key. But was there + # EVER a greenthread with this key? If not, let KeyError + # propagate. + self.values[key] + # Oh good, there's a value for this key. Either the + # greenthread finished, or somebody posted a value. Just say + # the greenthread isn't waiting for anything. + return set() + else: + # coro is the _Coro for the running greenthread with the + # specified key. + return coro.pending - available + + # This is a waiting_for() call, i.e. a general query rather than for a + # specific key. + + # Start by iterating over (key, coro) pairs in self.coros. Generate + # (key, pending) pairs in which 'pending' is the set of keys on which + # the greenthread believes it's waiting, minus the set of keys that + # are now available. Filter out any pair in which 'pending' is empty, + # that is, that greenthread will be unblocked next time it resumes. + # Make a dict from those pairs. + return dict((key, pending) + for key, pending in ((key, (coro.pending - available)) + for key, coro in six.iteritems(self.coros)) + if pending) diff --git a/venv/lib/python3.12/site-packages/eventlet/db_pool.py b/venv/lib/python3.12/site-packages/eventlet/db_pool.py new file mode 100644 index 0000000..45b1771 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/db_pool.py @@ -0,0 +1,461 @@ +from __future__ import print_function + +from collections import deque +from contextlib import contextmanager +import sys +import time + +from eventlet.pools import Pool +from eventlet import timeout +from eventlet import hubs +from eventlet.hubs.timer import Timer +from eventlet.greenthread import GreenThread + + +_MISSING = object() + + +class ConnectTimeout(Exception): + pass + + +def cleanup_rollback(conn): + conn.rollback() + + +class BaseConnectionPool(Pool): + def __init__(self, db_module, + min_size=0, max_size=4, + max_idle=10, max_age=30, + connect_timeout=5, + cleanup=cleanup_rollback, + *args, **kwargs): + """ + Constructs a pool with at least *min_size* connections and at most + *max_size* connections. Uses *db_module* to construct new connections. + + The *max_idle* parameter determines how long pooled connections can + remain idle, in seconds. After *max_idle* seconds have elapsed + without the connection being used, the pool closes the connection. + + *max_age* is how long any particular connection is allowed to live. + Connections that have been open for longer than *max_age* seconds are + closed, regardless of idle time. If *max_age* is 0, all connections are + closed on return to the pool, reducing it to a concurrency limiter. + + *connect_timeout* is the duration in seconds that the pool will wait + before timing out on connect() to the database. If triggered, the + timeout will raise a ConnectTimeout from get(). + + The remainder of the arguments are used as parameters to the + *db_module*'s connection constructor. + """ + assert(db_module) + self._db_module = db_module + self._args = args + self._kwargs = kwargs + self.max_idle = max_idle + self.max_age = max_age + self.connect_timeout = connect_timeout + self._expiration_timer = None + self.cleanup = cleanup + super(BaseConnectionPool, self).__init__(min_size=min_size, + max_size=max_size, + order_as_stack=True) + + def _schedule_expiration(self): + """Sets up a timer that will call _expire_old_connections when the + oldest connection currently in the free pool is ready to expire. This + is the earliest possible time that a connection could expire, thus, the + timer will be running as infrequently as possible without missing a + possible expiration. + + If this function is called when a timer is already scheduled, it does + nothing. + + If max_age or max_idle is 0, _schedule_expiration likewise does nothing. + """ + if self.max_age == 0 or self.max_idle == 0: + # expiration is unnecessary because all connections will be expired + # on put + return + + if (self._expiration_timer is not None + and not getattr(self._expiration_timer, 'called', False)): + # the next timer is already scheduled + return + + try: + now = time.time() + self._expire_old_connections(now) + # the last item in the list, because of the stack ordering, + # is going to be the most-idle + idle_delay = (self.free_items[-1][0] - now) + self.max_idle + oldest = min([t[1] for t in self.free_items]) + age_delay = (oldest - now) + self.max_age + + next_delay = min(idle_delay, age_delay) + except (IndexError, ValueError): + # no free items, unschedule ourselves + self._expiration_timer = None + return + + if next_delay > 0: + # set up a continuous self-calling loop + self._expiration_timer = Timer(next_delay, GreenThread(hubs.get_hub().greenlet).switch, + self._schedule_expiration, [], {}) + self._expiration_timer.schedule() + + def _expire_old_connections(self, now): + """Iterates through the open connections contained in the pool, closing + ones that have remained idle for longer than max_idle seconds, or have + been in existence for longer than max_age seconds. + + *now* is the current time, as returned by time.time(). + """ + original_count = len(self.free_items) + expired = [ + conn + for last_used, created_at, conn in self.free_items + if self._is_expired(now, last_used, created_at)] + + new_free = [ + (last_used, created_at, conn) + for last_used, created_at, conn in self.free_items + if not self._is_expired(now, last_used, created_at)] + self.free_items.clear() + self.free_items.extend(new_free) + + # adjust the current size counter to account for expired + # connections + self.current_size -= original_count - len(self.free_items) + + for conn in expired: + self._safe_close(conn, quiet=True) + + def _is_expired(self, now, last_used, created_at): + """Returns true and closes the connection if it's expired. + """ + if (self.max_idle <= 0 or self.max_age <= 0 + or now - last_used > self.max_idle + or now - created_at > self.max_age): + return True + return False + + def _unwrap_connection(self, conn): + """If the connection was wrapped by a subclass of + BaseConnectionWrapper and is still functional (as determined + by the __nonzero__, or __bool__ in python3, method), returns + the unwrapped connection. If anything goes wrong with this + process, returns None. + """ + base = None + try: + if conn: + base = conn._base + conn._destroy() + else: + base = None + except AttributeError: + pass + return base + + def _safe_close(self, conn, quiet=False): + """Closes the (already unwrapped) connection, squelching any + exceptions. + """ + try: + conn.close() + except AttributeError: + pass # conn is None, or junk + except Exception: + if not quiet: + print("Connection.close raised: %s" % (sys.exc_info()[1])) + + def get(self): + conn = super(BaseConnectionPool, self).get() + + # None is a flag value that means that put got called with + # something it couldn't use + if conn is None: + try: + conn = self.create() + except Exception: + # unconditionally increase the free pool because + # even if there are waiters, doing a full put + # would incur a greenlib switch and thus lose the + # exception stack + self.current_size -= 1 + raise + + # if the call to get() draws from the free pool, it will come + # back as a tuple + if isinstance(conn, tuple): + _last_used, created_at, conn = conn + else: + created_at = time.time() + + # wrap the connection so the consumer can call close() safely + wrapped = PooledConnectionWrapper(conn, self) + # annotating the wrapper so that when it gets put in the pool + # again, we'll know how old it is + wrapped._db_pool_created_at = created_at + return wrapped + + def put(self, conn, cleanup=_MISSING): + created_at = getattr(conn, '_db_pool_created_at', 0) + now = time.time() + conn = self._unwrap_connection(conn) + + if self._is_expired(now, now, created_at): + self._safe_close(conn, quiet=False) + conn = None + elif cleanup is not None: + if cleanup is _MISSING: + cleanup = self.cleanup + # by default, call rollback in case the connection is in the middle + # of a transaction. However, rollback has performance implications + # so optionally do nothing or call something else like ping + try: + if conn: + cleanup(conn) + except Exception as e: + # we don't care what the exception was, we just know the + # connection is dead + print("WARNING: cleanup %s raised: %s" % (cleanup, e)) + conn = None + except: + conn = None + raise + + if conn is not None: + super(BaseConnectionPool, self).put((now, created_at, conn)) + else: + # wake up any waiters with a flag value that indicates + # they need to manufacture a connection + if self.waiting() > 0: + super(BaseConnectionPool, self).put(None) + else: + # no waiters -- just change the size + self.current_size -= 1 + self._schedule_expiration() + + @contextmanager + def item(self, cleanup=_MISSING): + conn = self.get() + try: + yield conn + finally: + self.put(conn, cleanup=cleanup) + + def clear(self): + """Close all connections that this pool still holds a reference to, + and removes all references to them. + """ + if self._expiration_timer: + self._expiration_timer.cancel() + free_items, self.free_items = self.free_items, deque() + for item in free_items: + # Free items created using min_size>0 are not tuples. + conn = item[2] if isinstance(item, tuple) else item + self._safe_close(conn, quiet=True) + self.current_size -= 1 + + def __del__(self): + self.clear() + + +class TpooledConnectionPool(BaseConnectionPool): + """A pool which gives out :class:`~eventlet.tpool.Proxy`-based database + connections. + """ + + def create(self): + now = time.time() + return now, now, self.connect( + self._db_module, self.connect_timeout, *self._args, **self._kwargs) + + @classmethod + def connect(cls, db_module, connect_timeout, *args, **kw): + t = timeout.Timeout(connect_timeout, ConnectTimeout()) + try: + from eventlet import tpool + conn = tpool.execute(db_module.connect, *args, **kw) + return tpool.Proxy(conn, autowrap_names=('cursor',)) + finally: + t.cancel() + + +class RawConnectionPool(BaseConnectionPool): + """A pool which gives out plain database connections. + """ + + def create(self): + now = time.time() + return now, now, self.connect( + self._db_module, self.connect_timeout, *self._args, **self._kwargs) + + @classmethod + def connect(cls, db_module, connect_timeout, *args, **kw): + t = timeout.Timeout(connect_timeout, ConnectTimeout()) + try: + return db_module.connect(*args, **kw) + finally: + t.cancel() + + +# default connection pool is the tpool one +ConnectionPool = TpooledConnectionPool + + +class GenericConnectionWrapper(object): + def __init__(self, baseconn): + self._base = baseconn + + # Proxy all method calls to self._base + # FIXME: remove repetition; options to consider: + # * for name in (...): + # setattr(class, name, lambda self, *a, **kw: getattr(self._base, name)(*a, **kw)) + # * def __getattr__(self, name): if name in (...): return getattr(self._base, name) + # * other? + def __enter__(self): + return self._base.__enter__() + + def __exit__(self, exc, value, tb): + return self._base.__exit__(exc, value, tb) + + def __repr__(self): + return self._base.__repr__() + + _proxy_funcs = ( + 'affected_rows', + 'autocommit', + 'begin', + 'change_user', + 'character_set_name', + 'close', + 'commit', + 'cursor', + 'dump_debug_info', + 'errno', + 'error', + 'errorhandler', + 'insert_id', + 'literal', + 'ping', + 'query', + 'rollback', + 'select_db', + 'server_capabilities', + 'set_character_set', + 'set_isolation_level', + 'set_server_option', + 'set_sql_mode', + 'show_warnings', + 'shutdown', + 'sqlstate', + 'stat', + 'store_result', + 'string_literal', + 'thread_id', + 'use_result', + 'warning_count', + ) +for _proxy_fun in GenericConnectionWrapper._proxy_funcs: + # excess wrapper for early binding (closure by value) + def _wrapper(_proxy_fun=_proxy_fun): + def _proxy_method(self, *args, **kwargs): + return getattr(self._base, _proxy_fun)(*args, **kwargs) + _proxy_method.func_name = _proxy_fun + _proxy_method.__name__ = _proxy_fun + _proxy_method.__qualname__ = 'GenericConnectionWrapper.' + _proxy_fun + return _proxy_method + setattr(GenericConnectionWrapper, _proxy_fun, _wrapper(_proxy_fun)) +del GenericConnectionWrapper._proxy_funcs +del _proxy_fun +del _wrapper + + +class PooledConnectionWrapper(GenericConnectionWrapper): + """A connection wrapper where: + - the close method returns the connection to the pool instead of closing it directly + - ``bool(conn)`` returns a reasonable value + - returns itself to the pool if it gets garbage collected + """ + + def __init__(self, baseconn, pool): + super(PooledConnectionWrapper, self).__init__(baseconn) + self._pool = pool + + def __nonzero__(self): + return (hasattr(self, '_base') and bool(self._base)) + + __bool__ = __nonzero__ + + def _destroy(self): + self._pool = None + try: + del self._base + except AttributeError: + pass + + def close(self): + """Return the connection to the pool, and remove the + reference to it so that you can't use it again through this + wrapper object. + """ + if self and self._pool: + self._pool.put(self) + self._destroy() + + def __del__(self): + return # this causes some issues if __del__ is called in the + # main coroutine, so for now this is disabled + # self.close() + + +class DatabaseConnector(object): + """ + This is an object which will maintain a collection of database + connection pools on a per-host basis. + """ + + def __init__(self, module, credentials, + conn_pool=None, *args, **kwargs): + """constructor + *module* + Database module to use. + *credentials* + Mapping of hostname to connect arguments (e.g. username and password) + """ + assert(module) + self._conn_pool_class = conn_pool + if self._conn_pool_class is None: + self._conn_pool_class = ConnectionPool + self._module = module + self._args = args + self._kwargs = kwargs + # this is a map of hostname to username/password + self._credentials = credentials + self._databases = {} + + def credentials_for(self, host): + if host in self._credentials: + return self._credentials[host] + else: + return self._credentials.get('default', None) + + def get(self, host, dbname): + """Returns a ConnectionPool to the target host and schema. + """ + key = (host, dbname) + if key not in self._databases: + new_kwargs = self._kwargs.copy() + new_kwargs['db'] = dbname + new_kwargs['host'] = host + new_kwargs.update(self.credentials_for(host)) + dbpool = self._conn_pool_class( + self._module, *self._args, **new_kwargs) + self._databases[key] = dbpool + + return self._databases[key] diff --git a/venv/lib/python3.12/site-packages/eventlet/debug.py b/venv/lib/python3.12/site-packages/eventlet/debug.py new file mode 100644 index 0000000..6481aea --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/debug.py @@ -0,0 +1,174 @@ +"""The debug module contains utilities and functions for better +debugging Eventlet-powered applications.""" +from __future__ import print_function + +import os +import sys +import linecache +import re +import inspect + +__all__ = ['spew', 'unspew', 'format_hub_listeners', 'format_hub_timers', + 'hub_listener_stacks', 'hub_exceptions', 'tpool_exceptions', + 'hub_prevent_multiple_readers', 'hub_timer_stacks', + 'hub_blocking_detection'] + +_token_splitter = re.compile('\W+') + + +class Spew(object): + + def __init__(self, trace_names=None, show_values=True): + self.trace_names = trace_names + self.show_values = show_values + + def __call__(self, frame, event, arg): + if event == 'line': + lineno = frame.f_lineno + if '__file__' in frame.f_globals: + filename = frame.f_globals['__file__'] + if (filename.endswith('.pyc') or + filename.endswith('.pyo')): + filename = filename[:-1] + name = frame.f_globals['__name__'] + line = linecache.getline(filename, lineno) + else: + name = '[unknown]' + try: + src = inspect.getsourcelines(frame) + line = src[lineno] + except IOError: + line = 'Unknown code named [%s]. VM instruction #%d' % ( + frame.f_code.co_name, frame.f_lasti) + if self.trace_names is None or name in self.trace_names: + print('%s:%s: %s' % (name, lineno, line.rstrip())) + if not self.show_values: + return self + details = [] + tokens = _token_splitter.split(line) + for tok in tokens: + if tok in frame.f_globals: + details.append('%s=%r' % (tok, frame.f_globals[tok])) + if tok in frame.f_locals: + details.append('%s=%r' % (tok, frame.f_locals[tok])) + if details: + print("\t%s" % ' '.join(details)) + return self + + +def spew(trace_names=None, show_values=False): + """Install a trace hook which writes incredibly detailed logs + about what code is being executed to stdout. + """ + sys.settrace(Spew(trace_names, show_values)) + + +def unspew(): + """Remove the trace hook installed by spew. + """ + sys.settrace(None) + + +def format_hub_listeners(): + """ Returns a formatted string of the current listeners on the current + hub. This can be useful in determining what's going on in the event system, + especially when used in conjunction with :func:`hub_listener_stacks`. + """ + from eventlet import hubs + hub = hubs.get_hub() + result = ['READERS:'] + for l in hub.get_readers(): + result.append(repr(l)) + result.append('WRITERS:') + for l in hub.get_writers(): + result.append(repr(l)) + return os.linesep.join(result) + + +def format_hub_timers(): + """ Returns a formatted string of the current timers on the current + hub. This can be useful in determining what's going on in the event system, + especially when used in conjunction with :func:`hub_timer_stacks`. + """ + from eventlet import hubs + hub = hubs.get_hub() + result = ['TIMERS:'] + for l in hub.timers: + result.append(repr(l)) + return os.linesep.join(result) + + +def hub_listener_stacks(state=False): + """Toggles whether or not the hub records the stack when clients register + listeners on file descriptors. This can be useful when trying to figure + out what the hub is up to at any given moment. To inspect the stacks + of the current listeners, call :func:`format_hub_listeners` at critical + junctures in the application logic. + """ + from eventlet import hubs + hubs.get_hub().set_debug_listeners(state) + + +def hub_timer_stacks(state=False): + """Toggles whether or not the hub records the stack when timers are set. + To inspect the stacks of the current timers, call :func:`format_hub_timers` + at critical junctures in the application logic. + """ + from eventlet.hubs import timer + timer._g_debug = state + + +def hub_prevent_multiple_readers(state=True): + """Toggle prevention of multiple greenlets reading from a socket + + When multiple greenlets read from the same socket it is often hard + to predict which greenlet will receive what data. To achieve + resource sharing consider using ``eventlet.pools.Pool`` instead. + + But if you really know what you are doing you can change the state + to ``False`` to stop the hub from protecting against this mistake. + """ + from eventlet.hubs import hub + hub.g_prevent_multiple_readers = state + + +def hub_exceptions(state=True): + """Toggles whether the hub prints exceptions that are raised from its + timers. This can be useful to see how greenthreads are terminating. + """ + from eventlet import hubs + hubs.get_hub().set_timer_exceptions(state) + from eventlet import greenpool + greenpool.DEBUG = state + + +def tpool_exceptions(state=False): + """Toggles whether tpool itself prints exceptions that are raised from + functions that are executed in it, in addition to raising them like + it normally does.""" + from eventlet import tpool + tpool.QUIET = not state + + +def hub_blocking_detection(state=False, resolution=1): + """Toggles whether Eventlet makes an effort to detect blocking + behavior in an application. + + It does this by telling the kernel to raise a SIGALARM after a + short timeout, and clearing the timeout every time the hub + greenlet is resumed. Therefore, any code that runs for a long + time without yielding to the hub will get interrupted by the + blocking detector (don't use it in production!). + + The *resolution* argument governs how long the SIGALARM timeout + waits in seconds. The implementation uses :func:`signal.setitimer` + and can be specified as a floating-point value. + The shorter the resolution, the greater the chance of false + positives. + """ + from eventlet import hubs + assert resolution > 0 + hubs.get_hub().debug_blocking = state + hubs.get_hub().debug_blocking_resolution = resolution + if not state: + hubs.get_hub().block_detect_post() diff --git a/venv/lib/python3.12/site-packages/eventlet/event.py b/venv/lib/python3.12/site-packages/eventlet/event.py new file mode 100644 index 0000000..6ab455f --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/event.py @@ -0,0 +1,220 @@ +from __future__ import print_function + +from eventlet import hubs +from eventlet.support import greenlets as greenlet + +__all__ = ['Event'] + + +class NOT_USED: + def __repr__(self): + return 'NOT_USED' + +NOT_USED = NOT_USED() + + +class Event(object): + """An abstraction where an arbitrary number of coroutines + can wait for one event from another. + + Events are similar to a Queue that can only hold one item, but differ + in two important ways: + + 1. calling :meth:`send` never unschedules the current greenthread + 2. :meth:`send` can only be called once; create a new event to send again. + + They are good for communicating results between coroutines, and + are the basis for how + :meth:`GreenThread.wait() ` + is implemented. + + >>> from eventlet import event + >>> import eventlet + >>> evt = event.Event() + >>> def baz(b): + ... evt.send(b + 1) + ... + >>> _ = eventlet.spawn_n(baz, 3) + >>> evt.wait() + 4 + """ + _result = None + _exc = None + + def __init__(self): + self._waiters = set() + self.reset() + + def __str__(self): + params = (self.__class__.__name__, hex(id(self)), + self._result, self._exc, len(self._waiters)) + return '<%s at %s result=%r _exc=%r _waiters[%d]>' % params + + def reset(self): + # this is kind of a misfeature and doesn't work perfectly well, + # it's better to create a new event rather than reset an old one + # removing documentation so that we don't get new use cases for it + assert self._result is not NOT_USED, 'Trying to re-reset() a fresh event.' + self._result = NOT_USED + self._exc = None + + def ready(self): + """ Return true if the :meth:`wait` call will return immediately. + Used to avoid waiting for things that might take a while to time out. + For example, you can put a bunch of events into a list, and then visit + them all repeatedly, calling :meth:`ready` until one returns ``True``, + and then you can :meth:`wait` on that one.""" + return self._result is not NOT_USED + + def has_exception(self): + return self._exc is not None + + def has_result(self): + return self._result is not NOT_USED and self._exc is None + + def poll(self, notready=None): + if self.ready(): + return self.wait() + return notready + + # QQQ make it return tuple (type, value, tb) instead of raising + # because + # 1) "poll" does not imply raising + # 2) it's better not to screw up caller's sys.exc_info() by default + # (e.g. if caller wants to calls the function in except or finally) + def poll_exception(self, notready=None): + if self.has_exception(): + return self.wait() + return notready + + def poll_result(self, notready=None): + if self.has_result(): + return self.wait() + return notready + + def wait(self, timeout=None): + """Wait until another coroutine calls :meth:`send`. + Returns the value the other coroutine passed to :meth:`send`. + + >>> import eventlet + >>> evt = eventlet.Event() + >>> def wait_on(): + ... retval = evt.wait() + ... print("waited for {0}".format(retval)) + >>> _ = eventlet.spawn(wait_on) + >>> evt.send('result') + >>> eventlet.sleep(0) + waited for result + + Returns immediately if the event has already occurred. + + >>> evt.wait() + 'result' + + When the timeout argument is present and not None, it should be a floating point number + specifying a timeout for the operation in seconds (or fractions thereof). + """ + current = greenlet.getcurrent() + if self._result is NOT_USED: + hub = hubs.get_hub() + self._waiters.add(current) + timer = None + if timeout is not None: + timer = hub.schedule_call_local(timeout, self._do_send, None, None, current) + try: + result = hub.switch() + if timer is not None: + timer.cancel() + return result + finally: + self._waiters.discard(current) + if self._exc is not None: + current.throw(*self._exc) + return self._result + + def send(self, result=None, exc=None): + """Makes arrangements for the waiters to be woken with the + result and then returns immediately to the parent. + + >>> from eventlet import event + >>> import eventlet + >>> evt = event.Event() + >>> def waiter(): + ... print('about to wait') + ... result = evt.wait() + ... print('waited for {0}'.format(result)) + >>> _ = eventlet.spawn(waiter) + >>> eventlet.sleep(0) + about to wait + >>> evt.send('a') + >>> eventlet.sleep(0) + waited for a + + It is an error to call :meth:`send` multiple times on the same event. + + >>> evt.send('whoops') + Traceback (most recent call last): + ... + AssertionError: Trying to re-send() an already-triggered event. + + Use :meth:`reset` between :meth:`send` s to reuse an event object. + """ + assert self._result is NOT_USED, 'Trying to re-send() an already-triggered event.' + self._result = result + if exc is not None and not isinstance(exc, tuple): + exc = (exc, ) + self._exc = exc + hub = hubs.get_hub() + for waiter in self._waiters: + hub.schedule_call_global( + 0, self._do_send, self._result, self._exc, waiter) + + def _do_send(self, result, exc, waiter): + if waiter in self._waiters: + if exc is None: + waiter.switch(result) + else: + waiter.throw(*exc) + + def send_exception(self, *args): + """Same as :meth:`send`, but sends an exception to waiters. + + The arguments to send_exception are the same as the arguments + to ``raise``. If a single exception object is passed in, it + will be re-raised when :meth:`wait` is called, generating a + new stacktrace. + + >>> from eventlet import event + >>> evt = event.Event() + >>> evt.send_exception(RuntimeError()) + >>> evt.wait() + Traceback (most recent call last): + File "", line 1, in + File "eventlet/event.py", line 120, in wait + current.throw(*self._exc) + RuntimeError + + If it's important to preserve the entire original stack trace, + you must pass in the entire :func:`sys.exc_info` tuple. + + >>> import sys + >>> evt = event.Event() + >>> try: + ... raise RuntimeError() + ... except RuntimeError: + ... evt.send_exception(*sys.exc_info()) + ... + >>> evt.wait() + Traceback (most recent call last): + File "", line 1, in + File "eventlet/event.py", line 120, in wait + current.throw(*self._exc) + File "", line 2, in + RuntimeError + + Note that doing so stores a traceback object directly on the + Event object, which may cause reference cycles. See the + :func:`sys.exc_info` documentation. + """ + # the arguments and the same as for greenlet.throw + return self.send(None, args) diff --git a/venv/lib/python3.12/site-packages/eventlet/green/BaseHTTPServer.py b/venv/lib/python3.12/site-packages/eventlet/green/BaseHTTPServer.py new file mode 100644 index 0000000..493efd2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/BaseHTTPServer.py @@ -0,0 +1,16 @@ +from eventlet import patcher +from eventlet.green import socket +from eventlet.green import SocketServer +import six + +patcher.inject( + 'BaseHTTPServer' if six.PY2 else 'http.server', + globals(), + ('socket', socket), + ('SocketServer', SocketServer), + ('socketserver', SocketServer)) + +del patcher + +if __name__ == '__main__': + test() diff --git a/venv/lib/python3.12/site-packages/eventlet/green/CGIHTTPServer.py b/venv/lib/python3.12/site-packages/eventlet/green/CGIHTTPServer.py new file mode 100644 index 0000000..c384db5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/CGIHTTPServer.py @@ -0,0 +1,19 @@ +from eventlet import patcher +from eventlet.green import BaseHTTPServer +from eventlet.green import SimpleHTTPServer +from eventlet.green import urllib +from eventlet.green import select + +test = None # bind prior to patcher.inject to silence pyflakes warning below +patcher.inject( + 'CGIHTTPServer', + globals(), + ('BaseHTTPServer', BaseHTTPServer), + ('SimpleHTTPServer', SimpleHTTPServer), + ('urllib', urllib), + ('select', select)) + +del patcher + +if __name__ == '__main__': + test() # pyflakes false alarm here unless test = None above diff --git a/venv/lib/python3.12/site-packages/eventlet/green/MySQLdb.py b/venv/lib/python3.12/site-packages/eventlet/green/MySQLdb.py new file mode 100644 index 0000000..3593542 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/MySQLdb.py @@ -0,0 +1,37 @@ +__MySQLdb = __import__('MySQLdb') + +__all__ = __MySQLdb.__all__ +__patched__ = ["connect", "Connect", 'Connection', 'connections'] + +from eventlet.patcher import slurp_properties +slurp_properties( + __MySQLdb, globals(), + ignore=__patched__, srckeys=dir(__MySQLdb)) + +from eventlet import tpool + +__orig_connections = __import__('MySQLdb.connections').connections + + +def Connection(*args, **kw): + conn = tpool.execute(__orig_connections.Connection, *args, **kw) + return tpool.Proxy(conn, autowrap_names=('cursor',)) +connect = Connect = Connection + + +# replicate the MySQLdb.connections module but with a tpooled Connection factory +class MySQLdbConnectionsModule(object): + pass + +connections = MySQLdbConnectionsModule() +for var in dir(__orig_connections): + if not var.startswith('__'): + setattr(connections, var, getattr(__orig_connections, var)) +connections.Connection = Connection + +cursors = __import__('MySQLdb.cursors').cursors +converters = __import__('MySQLdb.converters').converters + +# TODO support instantiating cursors.FooCursor objects directly +# TODO though this is a low priority, it would be nice if we supported +# subclassing eventlet.green.MySQLdb.connections.Connection diff --git a/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/SSL.py b/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/SSL.py new file mode 100644 index 0000000..f534cea --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/SSL.py @@ -0,0 +1,124 @@ +from OpenSSL import SSL as orig_SSL +from OpenSSL.SSL import * +from eventlet.support import get_errno +from eventlet import greenio +from eventlet.hubs import trampoline +import socket + + +class GreenConnection(greenio.GreenSocket): + """ Nonblocking wrapper for SSL.Connection objects. + """ + + def __init__(self, ctx, sock=None): + if sock is not None: + fd = orig_SSL.Connection(ctx, sock) + else: + # if we're given a Connection object directly, use it; + # this is used in the inherited accept() method + fd = ctx + super(ConnectionType, self).__init__(fd) + + def do_handshake(self): + """ Perform an SSL handshake (usually called after renegotiate or one of + set_accept_state or set_accept_state). This can raise the same exceptions as + send and recv. """ + if self.act_non_blocking: + return self.fd.do_handshake() + while True: + try: + return self.fd.do_handshake() + except WantReadError: + trampoline(self.fd.fileno(), + read=True, + timeout=self.gettimeout(), + timeout_exc=socket.timeout) + except WantWriteError: + trampoline(self.fd.fileno(), + write=True, + timeout=self.gettimeout(), + timeout_exc=socket.timeout) + + def dup(self): + raise NotImplementedError("Dup not supported on SSL sockets") + + def makefile(self, mode='r', bufsize=-1): + raise NotImplementedError("Makefile not supported on SSL sockets") + + def read(self, size): + """Works like a blocking call to SSL_read(), whose behavior is + described here: http://www.openssl.org/docs/ssl/SSL_read.html""" + if self.act_non_blocking: + return self.fd.read(size) + while True: + try: + return self.fd.read(size) + except WantReadError: + trampoline(self.fd.fileno(), + read=True, + timeout=self.gettimeout(), + timeout_exc=socket.timeout) + except WantWriteError: + trampoline(self.fd.fileno(), + write=True, + timeout=self.gettimeout(), + timeout_exc=socket.timeout) + except SysCallError as e: + if get_errno(e) == -1 or get_errno(e) > 0: + return '' + + recv = read + + def write(self, data): + """Works like a blocking call to SSL_write(), whose behavior is + described here: http://www.openssl.org/docs/ssl/SSL_write.html""" + if not data: + return 0 # calling SSL_write() with 0 bytes to be sent is undefined + if self.act_non_blocking: + return self.fd.write(data) + while True: + try: + return self.fd.write(data) + except WantReadError: + trampoline(self.fd.fileno(), + read=True, + timeout=self.gettimeout(), + timeout_exc=socket.timeout) + except WantWriteError: + trampoline(self.fd.fileno(), + write=True, + timeout=self.gettimeout(), + timeout_exc=socket.timeout) + + send = write + + def sendall(self, data): + """Send "all" data on the connection. This calls send() repeatedly until + all data is sent. If an error occurs, it's impossible to tell how much data + has been sent. + + No return value.""" + tail = self.send(data) + while tail < len(data): + tail += self.send(data[tail:]) + + def shutdown(self): + if self.act_non_blocking: + return self.fd.shutdown() + while True: + try: + return self.fd.shutdown() + except WantReadError: + trampoline(self.fd.fileno(), + read=True, + timeout=self.gettimeout(), + timeout_exc=socket.timeout) + except WantWriteError: + trampoline(self.fd.fileno(), + write=True, + timeout=self.gettimeout(), + timeout_exc=socket.timeout) + +Connection = ConnectionType = GreenConnection + +del greenio diff --git a/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__init__.py b/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__init__.py new file mode 100644 index 0000000..1b25009 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__init__.py @@ -0,0 +1,9 @@ +from . import crypto +from . import SSL +try: + # pyopenssl tsafe module was deprecated and removed in v20.0.0 + # https://github.com/pyca/pyopenssl/pull/913 + from . import tsafe +except ImportError: + pass +from .version import __version__ diff --git a/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__pycache__/SSL.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__pycache__/SSL.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62c97373f136dc1e5fbf98fe123a0d676b726a45 GIT binary patch literal 6153 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 T9SG;qz^r%vE5CF4Z03IhVO9k{ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8855728408c1f1451dc576447a05fabd99f99bd3 GIT binary patch literal 396 zcmXv}yH3ME5Zuk#PD}_Af&z&Og(d|#0WG4VK#HJ9f#T-mI7e*Zm(E9m{{hib(9!W5 zwD1F13Jn!qL_~yAu|{Ew*_qqf$GtQfD?su3{O->wf0W`sj3t?Mh}^>f1{gww4r0kG z#4An(!7dde#T@omF+wx4919^gn;?x{Q1sq|+qE&=upW%DWql%zY++$}Do=GnjCH#m zN~X@-OG%_I#7L?vNMa$*TGfJvP>U*WQYJxBKaJ8v=@X?ARaBQ=Wl`w~W`wY~;C>QG z?j>PA=brCso%4~5M?4JrJk7PA#E0$u1D*w1?xn7Gm`>wD z1aY8+Xs7w2b`&Lp>rfu62JI=olC=q@3=t==If3SyHVrs^l>=Ok6?Cuby!$*hl;otA=%*K@rsnBGjn;=4tyfU_i^C>2 fKczG$)vkyGXbU3{7lRldm>C%v?=r{~u>m;%m|i=C literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__pycache__/tsafe.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/__pycache__/tsafe.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42494a371c13e45f5427776b93c1b093a2d6ffe7 GIT binary patch literal 216 zcmX@j%ge<81ZNK2%a8%mk3k$5V1hC}^8p#t8B!Qh7;_lbGBGk#GHEiu1W9T#-eT19 z1CqS{1*v(#!9IE=#ffRDD;Yk6WPgR|XXNLm>L=&tBvtAsXC#)CRO*+d=9TH^WG3ks zRF-7q=Nao68tE5jmZa(yBqnDkrl%I`rvjCfH7_{WM-M7p#0=C>!~!H%GJFPU z{uQR5k)NBYpPZkQRH>hwkyuhvsb7|wSEiqnnWSG(S(1^TXRK#vq+gs_lB!#fn4F!M zo?5J*3RF^(lUkymUX+@erw_FSs1fW4y@JYL95%W6DWy57c14^(YZ!sJm>WoZU}j`w Pyw6~8fx)1N9mod&UhhXu literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/crypto.py b/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/crypto.py new file mode 100644 index 0000000..0a57f6f --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/crypto.py @@ -0,0 +1 @@ +from OpenSSL.crypto import * diff --git a/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/tsafe.py b/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/tsafe.py new file mode 100644 index 0000000..dd0dd8c --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/tsafe.py @@ -0,0 +1 @@ +from OpenSSL.tsafe import * diff --git a/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/version.py b/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/version.py new file mode 100644 index 0000000..c886ef0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/OpenSSL/version.py @@ -0,0 +1 @@ +from OpenSSL.version import __version__, __doc__ diff --git a/venv/lib/python3.12/site-packages/eventlet/green/Queue.py b/venv/lib/python3.12/site-packages/eventlet/green/Queue.py new file mode 100644 index 0000000..f999c3b --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/Queue.py @@ -0,0 +1,32 @@ +from eventlet import queue + +__all__ = ['Empty', 'Full', 'LifoQueue', 'PriorityQueue', 'Queue'] + +__patched__ = ['LifoQueue', 'PriorityQueue', 'Queue'] + +# these classes exist to paper over the major operational difference between +# eventlet.queue.Queue and the stdlib equivalents + + +class Queue(queue.Queue): + def __init__(self, maxsize=0): + if maxsize == 0: + maxsize = None + super(Queue, self).__init__(maxsize) + + +class PriorityQueue(queue.PriorityQueue): + def __init__(self, maxsize=0): + if maxsize == 0: + maxsize = None + super(PriorityQueue, self).__init__(maxsize) + + +class LifoQueue(queue.LifoQueue): + def __init__(self, maxsize=0): + if maxsize == 0: + maxsize = None + super(LifoQueue, self).__init__(maxsize) + +Empty = queue.Empty +Full = queue.Full diff --git a/venv/lib/python3.12/site-packages/eventlet/green/SimpleHTTPServer.py b/venv/lib/python3.12/site-packages/eventlet/green/SimpleHTTPServer.py new file mode 100644 index 0000000..89d8b28 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/SimpleHTTPServer.py @@ -0,0 +1,14 @@ +from eventlet import patcher +from eventlet.green import BaseHTTPServer +from eventlet.green import urllib + +patcher.inject( + 'SimpleHTTPServer', + globals(), + ('BaseHTTPServer', BaseHTTPServer), + ('urllib', urllib)) + +del patcher + +if __name__ == '__main__': + test() diff --git a/venv/lib/python3.12/site-packages/eventlet/green/SocketServer.py b/venv/lib/python3.12/site-packages/eventlet/green/SocketServer.py new file mode 100644 index 0000000..17a4d43 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/SocketServer.py @@ -0,0 +1,15 @@ +from eventlet import patcher + +from eventlet.green import socket +from eventlet.green import select +from eventlet.green import threading +import six + +patcher.inject( + 'SocketServer' if six.PY2 else 'socketserver', + globals(), + ('socket', socket), + ('select', select), + ('threading', threading)) + +# QQQ ForkingMixIn should be fixed to use green waitpid? diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__init__.py b/venv/lib/python3.12/site-packages/eventlet/green/__init__.py new file mode 100644 index 0000000..d965325 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/__init__.py @@ -0,0 +1 @@ +# this package contains modules from the standard library converted to use eventlet diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/BaseHTTPServer.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/BaseHTTPServer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..300ccf0b28bf3aeee056d681df7b7c118adb37a3 GIT binary patch literal 624 zcmY+Bzi-n(6vyBB&aoRO9-u<}T0lsUft)BSVqie9ph_(R24p$*BTnL9Iv)}41VV_8 zY)CAyLjMcaE`=#I3aZ4!7O6~~I6JipZ+Q24@80{qce>qfw++g6zy1sz0`RK}8Z(dK z@QZ@?5I{g8h>1f;#ag7r)S)#~#mr$fvq*Pz4UmjGjnK)7`q(~+8WLKdCnRX8E_3HN z85l^PX@QAM`IXA==*uetj|r{>?a4I&VXB^X(?g(O1?ySAtHK^Bjm(pNh(cj2PyGo> zMV*bBHc(7aJSgouUXJ%RH`kYmEi05V>*w{YU`j_+W6RF6f0-{$*NweV;<`PnG;xZF zj8F<%71~YM9}2|e9|@X|UX-m-GDcsPbp6pSy<1F&BjZ|zt1?Cgb)#s}jYTmAHOpINW( w&-Cv0bdQ~x>8Ia4*<+WR^N*#ID+?R+&H=Tyx4zSJ`^5LZ7{_Ol$t1hmEGxyAiWe`^RotMxiU$R?7ZJLrcnI0e(@ok*vP`B6=|L(Y z_UO&8p!8dK?Pc+l23885ye-sIPdTyL!z!GILqY4B2pvnhj*`a%AqB8Ueb~Yl>gqLFH*3T^Irj z^ldF@0Yhzrf!YDStsU`y0hGL@&<<91*9nmpW|~bVB{|P|-~L?%_GxKOeKih2B6gqo z8N3<}-)w=L)t25?6i>xf8{d;cL?evn83-7w*QNdZ<>qCcWLQ}ml~mJ66Q#5~FZQFv z^ZK3Af*B-AfRcLU>Nd~8L;@uLO=k)+Rq`m=!G@(d5$VVmSyeUhWAMChQt)u!07v z*}cNJIbZ9~+5N(>4`*|Bqq+asoZedYh_Q02cMkV|=xZm;`uJk06Lwo(!(M*@2py(* literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/MySQLdb.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/MySQLdb.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..76fecbadd27c7a3dd8bb7d0290a6a124092609b6 GIT binary patch literal 1622 zcmZ8gO>7%Q6rNfCti9{M{5U@Z(X=WAmnI8EL_uvMkyt_q8d2r85*ls1BmZRWHM8p^ zjsh}LgovnUZpo1r4xp3+w;mDKUTmBy$xtCG#DQCA+DlKov3K2&k@n4-H}8Glyq|p# zk1Gh)gJ1tB{2WB+FLCf!)KgBr#0c#m2~lLCJj6_T16Hsc z(nDCphA0LZ-tz)z08TQ+KHk0Ho`;4JM24(LIjToHk;n}DUZugaeDD(0lsAzcqp>xt z$MuAS-bZF+6PZz(+zWL5^%StBd~RLoaH~FQ7Jc$6;PlbH66|M}srY^j-%3N|099xo z?1NOLeS1>Z-)F+;)6zvLEy<5Z$q}P8K0f|@ zK`Yp0s^x5JsjlS;hU?a~664(I~jM7In2e zqqL&=m95r0tR0a80_O^d2708XZeQQJzCZfL!RT~rbh@c#?+^Z_W`F5#so6$WTwmR~ zy6yh(;l4W449=WD6#8Fl{8pTZDcphK(y@tjsg9xJBOd#FcWT6d+_24>MHe9L2m z!LF>pEK=qyWQGN2v0Sy83!G6xJSQ#_VptZ~R6?qTn=4Qg4k^E=Q(YTlVoVhI;%yu06zzHq05lS5f<6Em=*ALa9JArRM_%87*B;hxoMGzi)oIckaf2W9t`KyVi5sag|D^C$@|AtX=qMdlXp&qG;X7YSnz3>D{IEx$ehic;X z?AGk=#fR#-qeyaF-Cevh(Y$o+ey)9C?qOv97&x;SfM2@UQ1{W$V`Z?Jeyy!c94J~# a(Hinme7HGsxgDQ8h|jd*GhZm3A()5(s`KAf2qHz=$S zFp6S4NIZ%s7ypGX4uCJ@6zUrPArP3gvymlRWcz+x^~imS}SG;`Tp-csk`8nd)>fT3gnB|8p40cfloLiLa>!gV}!c{k=#T)4ra z3>sypuEV_oAnqAUoD90qS55~6sHm|XcG@D4nPP);ujxgc_tjVng+CqV8uqo&n-y`M zbH}&Ckn@o8kB;`foigjKhA^F$f3a&ibvuf>=2g+WYWm(qv)zsAt>%G=>V7lyA~D{! zoy+!&2u*>I$QO}069~~XC214wZcxIgv*pB0KZAN3?nL#q2g`+_1^dq7Xor~1rg9^vZB6DRW$8};5;7WywP$yKCO$KU+LI>;*r_n(ppW9@O`YA z1zRW|(R4HzLM=&=3JHXezVPcDd{I<={4<~mwx%El7nu-0B!Ajux++fAE;J|rjel%% zKwpiQh*(VSBUKR4gGhUrlqsp-{=;paNLxT9{;UO!N7#ZrPq76D6Do!&{x(u0bZi5Y z@;Q(qUNE-=*Cgt<~6_Z2RFCt@6((#jv>^&M@GRB_fUK#s- z$D&M4Y6p&uELMrdYI2_U^e8eTy4No9<^g^~Az$9uW=TGZJ1{N(+Uw@{WS#lGr=@8SFELloD)JNL2Wav;*3Z;-h!5xGwJzH{4KR8_s$`DdY zNycpX8;bvnu317SN8l3Z)Xk7gnR0R>2z|r5@4ffE?|X1(t=1#ZarWy^xJm&2Rz)4w zJ-9m5;1mJ~h=7>%2&u3kjF|T50#l7yj}b5c$!MWii`=~d)04*Jz8RQE2S(69HZUga zu1Vl4K`XF!Ujj&5v+bHqps)+O)BINvJEXKSPx&EA4V`yhj$1oBpSMwt7oEnQ6e8-E zE89_=i3c-T`AAcz(ste0ixSuU)M=JBjxkXJm88|osmtzAA|?;E8c{MrUzOHSq!Z@L?=c^)Zzm3jWI zH^iLRwtQjB|G&PQ6*ui; yTl)TCYObCfoU<2GvvoW^XYK1IBTwhcU@l+MrQ`kI^w|Zof4rYlV9(`at#=Flq>I=9 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/SocketServer.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/SocketServer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d83b46072aadab49dfcd2f831d8bc7190536a5fb GIT binary patch literal 561 zcmZ9Jze~eF6vyxKtG{dzhk_u2;L^b!>MHI!6rq#Z(&U+>UXpS-i`gs=I*Lod$yNGa zxOVBNCyF2rZi2Wtd5MOKkK^w1eed1-a@?d|uObfF}1U=m*MJ|0!XV>do3)CYo53`ZjlbU=ocab`F8(1QeW%D%@vayFui&#at(^ zcYqCmiMgNdBv(w6(^zJ}jBIXIlwkl7r@)yi6T&QH%VPl1zkZ=6!Lcxcs3%>8y0^a} zN*;~7Hcf8OhrExzI>e7dAWlrXDRF$8^OQ)30ii*c^i%G~(PnFXgCqfmwZ82f+8!jN za3#4b`TKxYKm96ig|T}?VVf;T5BZlU*_GS7iZOmfORs3xk$!PzNvdu^Vsdt3dTOzLDo{yDPHKsM xdQoa>o_>6MW?p7Ve7s&kVhts zQc-5|*2MS;-`|S9j4^^al!IY_iMR_fGRpX)bTG_E{95Euq^J&Zg^OW)2TZv(QJf&a zCbm*9=O{)HPoIKE5zm~0PlW)P&Yu#A>%q^VI6a78I3+%#D;Q5S#k0U(nFlTsxCLLm zx(lC(w_ulyqfK#yWq2qsFWq=T9&9 z`&q#a7?rxknH#h@UeL5%!LqZ&4OTc~b;Bx;*C^vAh|jUd>>fD7u>v?K1Al!6U%wc4 z0rm-|@Ds-=wYX6+>Lru<6;g_Mq!O>#ws%qHWL>LTHKvs@oBu4Q}WXEGTc!#DSe8kpD6vCpDGF7XQwmOJs> z7s_4nZULF?mB0M-yKDC^fBSAHdak3K<5PGpn{-u5jatLv4yCR_hkbH`l$J~rp)jS5 zl2fiS9XGL2R~LPZyUGZ0gCi(rn>)Ng8{BFz?ikEQ)tJ^s$+TTjH#qnH5-7c<&BLA- zUiBnZ80gg3M3Szq-N-Mk(dCtEOE=fn>H78eR&Lx}cO%0g4#mm?_`N^Kh<8(D$#J-` zvE#56&Moe@#hdIt49E_eb)vjev-BO4UF8cH@+4;ef~GGLLLP?`d)346>n-J}I^9!K zT{YDTx{-V<{0x#up!6Wtg;)>fx-fVD{Ql~}^`Ex8g%3I((!;{X9hiFrr9b58o^&WD zTEefO{tmPK0H{;F&}=s}+X@_|=6b0s-PDy1UWc%n>8hDlurCEj=qUBZ_u4mFM@l}I zhD ZqWLVe({g@6Y`;l7o0ao9v3-%C{Rc6iR3-ob literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/asynchat.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/asynchat.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3da7a8e29dfd523b347e586e81014f0d380effa GIT binary patch literal 439 zcmYLDJx{|h5VaHM18BpBD#U`AI#3IkkPtfy>}5)mYt!O<$T5|&ff!(9<2RuG3)U_j z7)qofBqlacnL2Sv8*!5F-h1|Y=W)HhPO!!=@4+k1&w^BCt-!pC;EDLer-FpEO=*EO zp@n)|FR_l8wHYNE0XirXtB~dN8Qz$qmFOFwcQxMx*40LADN=GtGplKT6|_lnD{++< zfJA7!X%>001jJ?K_@#mEylLe&3?Whil+;nlor<=45+M42o16V;0G`UN zo`^fHNT10Z4P=U7zr&ME1#xuLIymHMU%`IjdRJ}_QeLg$ z|YBwl@8`pJn1pEZ=F&)BRey)HzT_r#JREVom*$hoHw>{cQ!RP b9~%?4JvHhN!wK8^U1PNIQzJ%A*3kV2T5NEu literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/asyncore.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/asyncore.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da887b7f436dfeab5dc7a45fe837d493e731a03a GIT binary patch literal 483 zcmZ9Jy-veG49D-1OB4DL7Q}>v7(37tup$;Fma;IFDNWp_?Io!$M@6*vh1I2`R|i%R;!f>+U)tw*C-($Sy7m^hUG27Bk_nw1qo=2 z(u_5s1$s-*Sx06qmNP>(S_UN=0otz?Vw>z4I5DT|hF1c;qj@GUj20LjZEF0DD7mJ| zR=xb0m35k$Lr1wjNW@YM0$i0-@CPa t?)&e=9-9yLojqsvE34dJ%#GS)^ul&GxiYyLH-9Py-TAhOQI$2+`33R8dH(=+oKOUOeuqS#hE>?uL6t=cL^z~_Q|TeG#4SA#-Z zxAm0I2SvLG`g~y6hJt8`Dw_~T4%_s!CblhVtR5Un#wpXN%?OT0Q9jY(_jk-$4qYe4Y7o-2#)1qa2NT|rb!-jS;ql8IFRruUrto%o2YX4ijru4 zGmIEb3Y;}g(jIRNYdoaNXt8q$m0ET2 zM&NSpI9y_z5E4IJuC<`kHE2M+S8D*iM6Y&=hNo(QU$1p~q7{WNRG&XmpxK zkD$|7cKW>X2nBrJ!!8!mD;=v^#yy(*%4+e^D&zj!c~5b1UjazwmQO4}873SjVt&&P z-5|X;3A9q)0l3U54%i-u#UxNGIaZeZ6p%!N8{G{Amr#Lj4+|mFVv(Hn7OO``(eH&kn{I9r%(vzh1d8ofK z#M!H?3Mz>p{wa{{9LD%hrGVA#DO8?|t>4kUyJ+q%TKEYW@VjSN>+M^{!MHG(m|bth zr9n=+u=vs9K%Ka_bn&Gv^+5dAJv4c7?#seg#le$LUpsz%?$*MwzaZ@Ecw0l*iq#9| qhvwRzUq+65_s8$tS9AI5`Um$BOxq@!ej=V2nB};*qZxQAP5NIVxY=X? literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/ftplib.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/ftplib.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b19e5dddb66df5914a90ed64236c4fbc8c6b6e6 GIT binary patch literal 451 zcmYLFze~eF6n>Ya$)Ps5NySMV>|hTSbWuk)ouv*TOty z3CXeKNxoc{2}ppXO^c!bZp-4O)Sq403d&8XFHb3fQ-Z2p>Z}9AROqxqM<7*GwW@vR z!YawM$mEuTv4Cn4S{>w>+T@YvHey96GHMvU=>~>zT%{SsE(Y8~9_KlkUeH=AMhvC{ zsvES?;+fX+!iMQ3xkF(3Xc(EoG2u@z&Z8$s%n5yDR_HZSW;rJ3DHHBpCZbuCawiP7 zYnxjvaXD^8rqwZ9n6M&|{Ux&LPis*+p?iL4pLuu?FA8_DYDo6QF_H-(AF%R6fvgTe wztnE@E4_Dqw{%~5IC<>8tnIwP?ogQ@sh}+ME?(uzyF#xIM=~hOu`V2b0kzb2FaQ7m literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/httplib.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/httplib.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59da371563d149d9636af37b23e40c3695fa4e1d GIT binary patch literal 959 zcmZWnL1+^}6rIV;ZZ=Jm#-ycIv|_A~6wG2H6rp&i+ES3zi-!heNq48orkm_CJ53u; ztst#hB;cWn7x7dn-aYr)OKkV{@4uOyolvL?@Yvb@ zTKEY8_%1e`kWRo+mNOf`00y)`31%S_*k}1lD2rMcSvZRgOwgP!53^E0@Dk_ctPBB> ziaj04KVL&8@|eD0RoG?r*my=_&F2YIwA5tBE5z0<>zgsv;WuA zH20)fp;9;-hJg3>kZ@=EZSWutKsG`md=^LlMg_WXJ<5BcBupY(zBYe)K8cL+ zBxVHW697HXoUS8TunjzX5Q)_$UIY(;PrAN_>%pGd z;^+Rv$Nt1#FmW(=<;~otu|Jr8GWl$(W;~s4O1w;ncdtF%Ljwm`c|6fX0H6ES><3Eh e5I`8q?bFXi27~V%6g6-6du}huQN_y zBkk^MujO;k+;i^F-20t-zWF>5s6tRazxR*Ki2y?XBo9vUY$#7&XAqi0EK-n+Zh}tA z5F;}(n`MVs+RjzlIcVpz{HSZl#URjKS@)Sj zW~8A!{#H5UA4anGQ)VatbNLhoWbo#OgF@Owg>mM;Rdtgun_y($2WTh+WA52HT3H{e zk^Nw;2J60UthI6gtYJ9_vJQMYw(+T#MX*M|dT<+Sqg*YAK469#CfI0=&FaR#iBq5L zHuTha#f&nxFpL#NQ*}_i!-^SKFxJR}kG7i`tR!T3W7Sk_emtR?wm@V~m2IAQL|NOd4r@AAY{9^=fC(eDagucHE|4Pp zpWFd4hs=u2LMBT+K-}EWjG#&UX^|NVO}fFh(c=>1MW*0bM;r_}4NzNXn6a2oncurr zWU-$yA2I`|6`2%=#H|hC`aEX>8q3AvMuLvMS$~Kw0sivbd)zoPj(*O)gT|RCH;Ky| z(w>&3cRQp}T~?0l$NPz%97`MOq;l?DRO);~N~>8Vt|v#7lv!0JK{>6MV_1_CnnbpX z+NH8xO6i&geu^xax|ArJWW`8fHD~I$Yq9|*Gcx*ER+f^Aq-th|r0J%#7xzUyc9m+V znqelilwxyWv%P~8IpsLUI>uxZZSM(!f>UY^PNuh?8f>9|pser-JZwaHn@!8Ok$AJ` z$8AP22*k;%*L!0beN>61^lWk>mdYedb0T&@(JsWYYBH9aFf+RLov!XzV+H`DGnYu6 zPYf$YOaT`&tC%s0w3u#m29h=WvPTT)T>5b`zBr>!8)F;B%ycKbUKyoIeCR!dyb{AV3=X9;>}suE!bE z@Azzo&2Kbc$Qfa?KHm=Wxt!T1xJw)JZXz7sJ?!|8)H>Q`ZNZIui+6T}L0Th3R*hpH zi8$J34Qu5Xc*6Z{g`vENu9Tld8SE1|ll913+j z|5B`*v8Jsx#6kax)5VW3-WOjf);F&BtDlzyu&tLo91P=yb1vVoZSm$X8Lw3joOQ-i74ip5<_7!Qc69A^$>Llp((n z7YE=*2kZJ%H}VE%#A$J!U1b<$ll6DfK6_> z?W64EF~V`$=@GR9)#-raNSI?rbmNS`D5Z4_34AmM*-q6Vxu@aKQW+I;H4v$kVi;YR z%ms%;NXGBb6OgEUeGmqMiTDxx4RT+X6JmI_dp2?PNP%y_ozS>_{QU(ibI4*$sLN%f zlAD|sgeDnyw! zJXw629rzliCRg$#gRUI}b~UoWDcifcx}p@`P6OS2JxBJ~_}1q!tO6g|?o@^^*8btd z7n2|SFzT`e-JprY7S8nbzunV!!miyKPGzzS0Y$i{?J}~8l7n6xVZg%1!fckxL9wZJYbr3L6L=1pps32$@dBl(E=XPD+wIoOjJ@D#`$gyl$WX6aob&mV=e5Ag_M2oCQ1bjHnc9}(^A5pcki`!mxL8f;3{K=E zpqhkVQ|yr49?Yl)u;xtSf?`Oj475~DC9>HG!aGa|+-x?H)Nul?Uuj%5GnIa3Mv>sV zXum%hnv+byd&o|XX|ya`m?_Qc~_ zVpNI8U!W>vJU|m=9)TC;ARWo)gY8OZ6T^nhldlZ?eb^RCMP5b{`KrQ1DDXFkfLkPj zJpL=kE>AubnwEs7LQCwv5L;=BE`IlRKi986rIn93QJTC{judqel^N^1$@sU~lfNxsy1*Uo_ilVIgU$|lVqD^*HJgx$J z!X~^_j4X*}8iF$0<9rU3KS)B(W0x_OBRwcfqj6Qq}O0a{+rhH$Z&h?ABr^3ox6UnfNIvJBqO H`U?L8r9kWe literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/profile.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/profile.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d40f185939e787bb19dfb238214e3608a47f18ae GIT binary patch literal 10672 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 E25qvsLI3~& literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/selectors.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/selectors.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b287b411f44d943c29e5129573313aa5cddd404 GIT binary patch literal 726 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 YeI(V?n=oz037%=|>KUHX32c!6Hn+a literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/subprocess.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/subprocess.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b522f8c4930f20417945f6d16d94b187aaf8cefd GIT binary patch literal 5657 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 OO0_TMP|dxfh5i?;-^mC7 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/thread.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/thread.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..86224e8ee8e645eb499607392dde0b8aaf8b6ea5 GIT binary patch literal 3843 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 M#%RYNS2is72LqmlRR910 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/urllib2.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/urllib2.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..043018149c2dacc2f19eaa6902c11816a39a18ca GIT binary patch literal 755 zcmZ{i&x_Mg5Xaxkua`}mtn6Z4L=?R(Y#UrJ9z+%eQIO)HdWeUTCex0aR#;Daa*{Umk>l(rH`NMbbHznj( zmJ}zy!Q^a#@`kv?r9SCXi_(k*U+9aLn6rp1SyIjtvTVsY%gBnQQ-5IDG18F%LT8aYk-Jf z3rh#eO})5#PDh)&7ye}FE=^fvbH{@iQEBtY@3u12;{_^7=o|t^Q%n*cgT%Xi$f^gi zkJTw##{F2vhOv@KHgc?HvXO~|Qr|UF6^0OSA9yUHlNv=@>%;&-UiRN6snQL4;P8}n z{IF&FNowSSH`{~2;oUGuwFiwS5ADG9Ax>4?q!~sKyd!7$6gjV(Uf2iI3H{c{bUd5$ zk%_|$P29kYM%)X7TeX|}X42(Qjcn)8?m%J|1Dg4O=4#PeG&)!B^h0;x!`=8Iw#Q$K zBri}FBBgXruFT2xIk}PR)j7F_@Pim*_S$%9%q0DrRC@JjCL2d=D(%cD`JE`owwN9%CC)I=Zw?96c-fMiRJRNIa$@WjF J6mR1M{{W!Ox*z}m literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/zmq.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/__pycache__/zmq.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b45d28c04a2267e18aba705960ee747854214128 GIT binary patch literal 19794 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/_socket_nodns.py b/venv/lib/python3.12/site-packages/eventlet/green/_socket_nodns.py new file mode 100644 index 0000000..7dca20a --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/_socket_nodns.py @@ -0,0 +1,33 @@ +__socket = __import__('socket') + +__all__ = __socket.__all__ +__patched__ = ['fromfd', 'socketpair', 'ssl', 'socket', 'timeout'] + +import eventlet.patcher +eventlet.patcher.slurp_properties(__socket, globals(), ignore=__patched__, srckeys=dir(__socket)) + +os = __import__('os') +import sys +from eventlet import greenio + + +socket = greenio.GreenSocket +_GLOBAL_DEFAULT_TIMEOUT = greenio._GLOBAL_DEFAULT_TIMEOUT +timeout = greenio.socket_timeout + +try: + __original_fromfd__ = __socket.fromfd + + def fromfd(*args): + return socket(__original_fromfd__(*args)) +except AttributeError: + pass + +try: + __original_socketpair__ = __socket.socketpair + + def socketpair(*args): + one, two = __original_socketpair__(*args) + return socket(one), socket(two) +except AttributeError: + pass diff --git a/venv/lib/python3.12/site-packages/eventlet/green/asynchat.py b/venv/lib/python3.12/site-packages/eventlet/green/asynchat.py new file mode 100644 index 0000000..e074749 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/asynchat.py @@ -0,0 +1,11 @@ +from eventlet import patcher +from eventlet.green import asyncore +from eventlet.green import socket + +patcher.inject( + 'asynchat', + globals(), + ('asyncore', asyncore), + ('socket', socket)) + +del patcher diff --git a/venv/lib/python3.12/site-packages/eventlet/green/asyncore.py b/venv/lib/python3.12/site-packages/eventlet/green/asyncore.py new file mode 100644 index 0000000..6a5f797 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/asyncore.py @@ -0,0 +1,13 @@ +from eventlet import patcher +from eventlet.green import select +from eventlet.green import socket +from eventlet.green import time + +patcher.inject( + "asyncore", + globals(), + ('select', select), + ('socket', socket), + ('time', time)) + +del patcher diff --git a/venv/lib/python3.12/site-packages/eventlet/green/builtin.py b/venv/lib/python3.12/site-packages/eventlet/green/builtin.py new file mode 100644 index 0000000..8d0603a --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/builtin.py @@ -0,0 +1,51 @@ +""" +In order to detect a filehandle that's been closed, our only clue may be +the operating system returning the same filehandle in response to some +other operation. + +The builtins 'file' and 'open' are patched to collaborate with the +notify_opened protocol. +""" + +builtins_orig = __builtins__ + +from eventlet import hubs +from eventlet.hubs import hub +from eventlet.patcher import slurp_properties +import sys +import six + +__all__ = dir(builtins_orig) +__patched__ = ['open'] +if six.PY2: + __patched__ += ['file'] + +slurp_properties(builtins_orig, globals(), + ignore=__patched__, srckeys=dir(builtins_orig)) + +hubs.get_hub() + +if six.PY2: + __original_file = file + + class file(__original_file): + def __init__(self, *args, **kwargs): + super(file, self).__init__(*args, **kwargs) + hubs.notify_opened(self.fileno()) + + +__original_open = open +__opening = False + + +def open(*args, **kwargs): + global __opening + result = __original_open(*args, **kwargs) + if not __opening: + # This is incredibly ugly. 'open' is used under the hood by + # the import process. So, ensure we don't wind up in an + # infinite loop. + __opening = True + hubs.notify_opened(result.fileno()) + __opening = False + return result diff --git a/venv/lib/python3.12/site-packages/eventlet/green/ftplib.py b/venv/lib/python3.12/site-packages/eventlet/green/ftplib.py new file mode 100644 index 0000000..b452e1d --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/ftplib.py @@ -0,0 +1,13 @@ +from eventlet import patcher + +# *NOTE: there might be some funny business with the "SOCKS" module +# if it even still exists +from eventlet.green import socket + +patcher.inject('ftplib', globals(), ('socket', socket)) + +del patcher + +# Run test program when run as a script +if __name__ == '__main__': + test() diff --git a/venv/lib/python3.12/site-packages/eventlet/green/http/__init__.py b/venv/lib/python3.12/site-packages/eventlet/green/http/__init__.py new file mode 100644 index 0000000..2e86175 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/http/__init__.py @@ -0,0 +1,191 @@ +# This is part of Python source code with Eventlet-specific modifications. +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +# 2011, 2012, 2013, 2014, 2015, 2016 Python Software Foundation; All Rights +# Reserved +# +# 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, +# 2011, 2012, 2013, 2014, 2015, 2016 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. +import six +assert six.PY3, 'This is a Python 3 module' + +from enum import IntEnum + +__all__ = ['HTTPStatus'] + +class HTTPStatus(IntEnum): + """HTTP status codes and reason phrases + + Status codes from the following RFCs are all observed: + + * RFC 7231: Hypertext Transfer Protocol (HTTP/1.1), obsoletes 2616 + * RFC 6585: Additional HTTP Status Codes + * RFC 3229: Delta encoding in HTTP + * RFC 4918: HTTP Extensions for WebDAV, obsoletes 2518 + * RFC 5842: Binding Extensions to WebDAV + * RFC 7238: Permanent Redirect + * RFC 2295: Transparent Content Negotiation in HTTP + * RFC 2774: An HTTP Extension Framework + """ + def __new__(cls, value, phrase, description=''): + obj = int.__new__(cls, value) + obj._value_ = value + + obj.phrase = phrase + obj.description = description + return obj + + # informational + CONTINUE = 100, 'Continue', 'Request received, please continue' + SWITCHING_PROTOCOLS = (101, 'Switching Protocols', + 'Switching to new protocol; obey Upgrade header') + PROCESSING = 102, 'Processing' + + # success + OK = 200, 'OK', 'Request fulfilled, document follows' + CREATED = 201, 'Created', 'Document created, URL follows' + ACCEPTED = (202, 'Accepted', + 'Request accepted, processing continues off-line') + NON_AUTHORITATIVE_INFORMATION = (203, + 'Non-Authoritative Information', 'Request fulfilled from cache') + NO_CONTENT = 204, 'No Content', 'Request fulfilled, nothing follows' + RESET_CONTENT = 205, 'Reset Content', 'Clear input form for further input' + PARTIAL_CONTENT = 206, 'Partial Content', 'Partial content follows' + MULTI_STATUS = 207, 'Multi-Status' + ALREADY_REPORTED = 208, 'Already Reported' + IM_USED = 226, 'IM Used' + + # redirection + MULTIPLE_CHOICES = (300, 'Multiple Choices', + 'Object has several resources -- see URI list') + MOVED_PERMANENTLY = (301, 'Moved Permanently', + 'Object moved permanently -- see URI list') + FOUND = 302, 'Found', 'Object moved temporarily -- see URI list' + SEE_OTHER = 303, 'See Other', 'Object moved -- see Method and URL list' + NOT_MODIFIED = (304, 'Not Modified', + 'Document has not changed since given time') + USE_PROXY = (305, 'Use Proxy', + 'You must use proxy specified in Location to access this resource') + TEMPORARY_REDIRECT = (307, 'Temporary Redirect', + 'Object moved temporarily -- see URI list') + PERMANENT_REDIRECT = (308, 'Permanent Redirect', + 'Object moved temporarily -- see URI list') + + # client error + BAD_REQUEST = (400, 'Bad Request', + 'Bad request syntax or unsupported method') + UNAUTHORIZED = (401, 'Unauthorized', + 'No permission -- see authorization schemes') + PAYMENT_REQUIRED = (402, 'Payment Required', + 'No payment -- see charging schemes') + FORBIDDEN = (403, 'Forbidden', + 'Request forbidden -- authorization will not help') + NOT_FOUND = (404, 'Not Found', + 'Nothing matches the given URI') + METHOD_NOT_ALLOWED = (405, 'Method Not Allowed', + 'Specified method is invalid for this resource') + NOT_ACCEPTABLE = (406, 'Not Acceptable', + 'URI not available in preferred format') + PROXY_AUTHENTICATION_REQUIRED = (407, + 'Proxy Authentication Required', + 'You must authenticate with this proxy before proceeding') + REQUEST_TIMEOUT = (408, 'Request Timeout', + 'Request timed out; try again later') + CONFLICT = 409, 'Conflict', 'Request conflict' + GONE = (410, 'Gone', + 'URI no longer exists and has been permanently removed') + LENGTH_REQUIRED = (411, 'Length Required', + 'Client must specify Content-Length') + PRECONDITION_FAILED = (412, 'Precondition Failed', + 'Precondition in headers is false') + REQUEST_ENTITY_TOO_LARGE = (413, 'Request Entity Too Large', + 'Entity is too large') + REQUEST_URI_TOO_LONG = (414, 'Request-URI Too Long', + 'URI is too long') + UNSUPPORTED_MEDIA_TYPE = (415, 'Unsupported Media Type', + 'Entity body in unsupported format') + REQUESTED_RANGE_NOT_SATISFIABLE = (416, + 'Requested Range Not Satisfiable', + 'Cannot satisfy request range') + EXPECTATION_FAILED = (417, 'Expectation Failed', + 'Expect condition could not be satisfied') + UNPROCESSABLE_ENTITY = 422, 'Unprocessable Entity' + LOCKED = 423, 'Locked' + FAILED_DEPENDENCY = 424, 'Failed Dependency' + UPGRADE_REQUIRED = 426, 'Upgrade Required' + PRECONDITION_REQUIRED = (428, 'Precondition Required', + 'The origin server requires the request to be conditional') + TOO_MANY_REQUESTS = (429, 'Too Many Requests', + 'The user has sent too many requests in ' + 'a given amount of time ("rate limiting")') + REQUEST_HEADER_FIELDS_TOO_LARGE = (431, + 'Request Header Fields Too Large', + 'The server is unwilling to process the request because its header ' + 'fields are too large') + + # server errors + INTERNAL_SERVER_ERROR = (500, 'Internal Server Error', + 'Server got itself in trouble') + NOT_IMPLEMENTED = (501, 'Not Implemented', + 'Server does not support this operation') + BAD_GATEWAY = (502, 'Bad Gateway', + 'Invalid responses from another server/proxy') + SERVICE_UNAVAILABLE = (503, 'Service Unavailable', + 'The server cannot process the request due to a high load') + GATEWAY_TIMEOUT = (504, 'Gateway Timeout', + 'The gateway server did not receive a timely response') + HTTP_VERSION_NOT_SUPPORTED = (505, 'HTTP Version Not Supported', + 'Cannot fulfill request') + VARIANT_ALSO_NEGOTIATES = 506, 'Variant Also Negotiates' + INSUFFICIENT_STORAGE = 507, 'Insufficient Storage' + LOOP_DETECTED = 508, 'Loop Detected' + NOT_EXTENDED = 510, 'Not Extended' + NETWORK_AUTHENTICATION_REQUIRED = (511, + 'Network Authentication Required', + 'The client needs to authenticate to gain network access') diff --git a/venv/lib/python3.12/site-packages/eventlet/green/http/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/http/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6ff7d79b6c47e5e923c9b1b20363939f3ea689a GIT binary patch literal 6247 zcmZ`-S#KLzcIGC@mL+SoruW7+@xAcb1tXFG*%PLH^X}$sB=WEH(*5fTA6{vA_%y;JJZeR>h=_a?>yjLN zdjlUtc{ak{yvX=3=4Q%>*EQnpZ944yk9%S2eiZdkT%EWttYghS-c8y=vuJdc!4Srp_$!}=4_)YCLzop&bx3#!s9WD@Cn=@A!9->>{bq!B=gM$ozK}18MZP%$We^;<= z$FiKKroF?8t0^$Mf*F>@oGnkdPXxac@Dl%fM8R&ZTwT7y)|++V`r?_-%C2F1+rni9 z*YTaIW3fwQ($ezc^4vU$aV+7(s+AkdH>4Q{qHbKjb^Q)Ya&G#jV;dHezJz9`$d|Vm zuCA=yzQfYO@(m_zcuQuRw&Z(@^xEy^TQHM?qdfD4?LmVFXI=J4Y^9SMZ zyx4Jk)1XLvjY}&xZ(c*V0(TF*VymuE6HguYvD8&1WOV=Nnd

FXQ9Wh~Hh0d=yq^ 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 R#Z^+>zU}vWzv`0+{{{UdPapsQ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/http/__pycache__/cookiejar.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/http/__pycache__/cookiejar.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72c92cac11d71c58f21154d4d6c3fc79d700a15d GIT binary patch literal 80452 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/http/__pycache__/cookies.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/http/__pycache__/cookies.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fa4483343bb057d532e87c8a9cfd04632969704 GIT binary patch literal 22730 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/http/__pycache__/server.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/http/__pycache__/server.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e0a72ffc7aca1345f29a64942ac895c0148268f GIT binary patch literal 50065 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 I0k$~#4S@3`6#xJL literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/http/client.py b/venv/lib/python3.12/site-packages/eventlet/green/http/client.py new file mode 100644 index 0000000..e4bd2ad --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/http/client.py @@ -0,0 +1,1567 @@ +# This is part of Python source code with Eventlet-specific modifications. +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +# 2011, 2012, 2013, 2014, 2015, 2016 Python Software Foundation; All Rights +# Reserved +# +# 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, +# 2011, 2012, 2013, 2014, 2015, 2016 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. +"""HTTP/1.1 client library + + + + +HTTPConnection goes through a number of "states", which define when a client +may legally make another request or fetch the response for a particular +request. This diagram details these state transitions: + + (null) + | + | HTTPConnection() + v + Idle + | + | putrequest() + v + Request-started + | + | ( putheader() )* endheaders() + v + Request-sent + |\_____________________________ + | | getresponse() raises + | response = getresponse() | ConnectionError + v v + Unread-response Idle + [Response-headers-read] + |\____________________ + | | + | response.read() | putrequest() + v v + Idle Req-started-unread-response + ______/| + / | + response.read() | | ( putheader() )* endheaders() + v v + Request-started Req-sent-unread-response + | + | response.read() + v + Request-sent + +This diagram presents the following rules: + -- a second request may not be started until {response-headers-read} + -- a response [object] cannot be retrieved until {request-sent} + -- there is no differentiation between an unread response body and a + partially read response body + +Note: this enforcement is applied by the HTTPConnection class. The + HTTPResponse class does not enforce this state machine, which + implies sophisticated clients may accelerate the request/response + pipeline. Caution should be taken, though: accelerating the states + beyond the above pattern may imply knowledge of the server's + connection-close behavior for certain requests. For example, it + is impossible to tell whether the server will close the connection + UNTIL the response headers have been read; this means that further + requests cannot be placed into the pipeline until it is known that + the server will NOT be closing the connection. + +Logical State __state __response +------------- ------- ---------- +Idle _CS_IDLE None +Request-started _CS_REQ_STARTED None +Request-sent _CS_REQ_SENT None +Unread-response _CS_IDLE +Req-started-unread-response _CS_REQ_STARTED +Req-sent-unread-response _CS_REQ_SENT +""" + +import email.parser +import email.message +import io +import re +import collections +from urllib.parse import urlsplit + +from eventlet.green import http, os, socket + +# HTTPMessage, parse_headers(), and the HTTP status code constants are +# intentionally omitted for simplicity +__all__ = ["HTTPResponse", "HTTPConnection", + "HTTPException", "NotConnected", "UnknownProtocol", + "UnknownTransferEncoding", "UnimplementedFileMode", + "IncompleteRead", "InvalidURL", "ImproperConnectionState", + "CannotSendRequest", "CannotSendHeader", "ResponseNotReady", + "BadStatusLine", "LineTooLong", "RemoteDisconnected", "error", + "responses"] + +HTTP_PORT = 80 +HTTPS_PORT = 443 + +_UNKNOWN = 'UNKNOWN' + +# connection states +_CS_IDLE = 'Idle' +_CS_REQ_STARTED = 'Request-started' +_CS_REQ_SENT = 'Request-sent' + + +# hack to maintain backwards compatibility +globals().update(http.HTTPStatus.__members__) + +# another hack to maintain backwards compatibility +# Mapping status codes to official W3C names +responses = {v: v.phrase for v in http.HTTPStatus.__members__.values()} + +# maximal amount of data to read at one time in _safe_read +MAXAMOUNT = 1048576 + +# maximal line length when calling readline(). +_MAXLINE = 65536 +_MAXHEADERS = 100 + +# Header name/value ABNF (http://tools.ietf.org/html/rfc7230#section-3.2) +# +# VCHAR = %x21-7E +# obs-text = %x80-FF +# header-field = field-name ":" OWS field-value OWS +# field-name = token +# field-value = *( field-content / obs-fold ) +# field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] +# field-vchar = VCHAR / obs-text +# +# obs-fold = CRLF 1*( SP / HTAB ) +# ; obsolete line folding +# ; see Section 3.2.4 + +# token = 1*tchar +# +# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" +# / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" +# / DIGIT / ALPHA +# ; any VCHAR, except delimiters +# +# VCHAR defined in http://tools.ietf.org/html/rfc5234#appendix-B.1 + +# the patterns for both name and value are more leniant than RFC +# definitions to allow for backwards compatibility +# Eventlet change: match used instead of fullmatch for Python 3.3 compatibility +_is_legal_header_name = re.compile(rb'[^:\s][^:\r\n]*\Z').match +_is_illegal_header_value = re.compile(rb'\n(?![ \t])|\r(?![ \t\n])').search + +# We always set the Content-Length header for these methods because some +# servers will otherwise respond with a 411 +_METHODS_EXPECTING_BODY = {'PATCH', 'POST', 'PUT'} + + +def _encode(data, name='data'): + """Call data.encode("latin-1") but show a better error message.""" + try: + return data.encode("latin-1") + except UnicodeEncodeError as err: + raise UnicodeEncodeError( + err.encoding, + err.object, + err.start, + err.end, + "%s (%.20r) is not valid Latin-1. Use %s.encode('utf-8') " + "if you want to send it encoded in UTF-8." % + (name.title(), data[err.start:err.end], name)) from None + + +class HTTPMessage(email.message.Message): + # XXX The only usage of this method is in + # http.server.CGIHTTPRequestHandler. Maybe move the code there so + # that it doesn't need to be part of the public API. The API has + # never been defined so this could cause backwards compatibility + # issues. + + def getallmatchingheaders(self, name): + """Find all header lines matching a given header name. + + Look through the list of headers and find all lines matching a given + header name (and their continuation lines). A list of the lines is + returned, without interpretation. If the header does not occur, an + empty list is returned. If the header occurs multiple times, all + occurrences are returned. Case is not important in the header name. + + """ + name = name.lower() + ':' + n = len(name) + lst = [] + hit = 0 + for line in self.keys(): + if line[:n].lower() == name: + hit = 1 + elif not line[:1].isspace(): + hit = 0 + if hit: + lst.append(line) + return lst + +def parse_headers(fp, _class=HTTPMessage): + """Parses only RFC2822 headers from a file pointer. + + email Parser wants to see strings rather than bytes. + But a TextIOWrapper around self.rfile would buffer too many bytes + from the stream, bytes which we later need to read as bytes. + So we read the correct bytes here, as bytes, for email Parser + to parse. + + """ + headers = [] + while True: + line = fp.readline(_MAXLINE + 1) + if len(line) > _MAXLINE: + raise LineTooLong("header line") + headers.append(line) + if len(headers) > _MAXHEADERS: + raise HTTPException("got more than %d headers" % _MAXHEADERS) + if line in (b'\r\n', b'\n', b''): + break + hstring = b''.join(headers).decode('iso-8859-1') + return email.parser.Parser(_class=_class).parsestr(hstring) + + +class HTTPResponse(io.BufferedIOBase): + + # See RFC 2616 sec 19.6 and RFC 1945 sec 6 for details. + + # The bytes from the socket object are iso-8859-1 strings. + # See RFC 2616 sec 2.2 which notes an exception for MIME-encoded + # text following RFC 2047. The basic status line parsing only + # accepts iso-8859-1. + + def __init__(self, sock, debuglevel=0, method=None, url=None): + # If the response includes a content-length header, we need to + # make sure that the client doesn't read more than the + # specified number of bytes. If it does, it will block until + # the server times out and closes the connection. This will + # happen if a self.fp.read() is done (without a size) whether + # self.fp is buffered or not. So, no self.fp.read() by + # clients unless they know what they are doing. + self.fp = sock.makefile("rb") + self.debuglevel = debuglevel + self._method = method + + # The HTTPResponse object is returned via urllib. The clients + # of http and urllib expect different attributes for the + # headers. headers is used here and supports urllib. msg is + # provided as a backwards compatibility layer for http + # clients. + + self.headers = self.msg = None + + # from the Status-Line of the response + self.version = _UNKNOWN # HTTP-Version + self.status = _UNKNOWN # Status-Code + self.reason = _UNKNOWN # Reason-Phrase + + self.chunked = _UNKNOWN # is "chunked" being used? + self.chunk_left = _UNKNOWN # bytes left to read in current chunk + self.length = _UNKNOWN # number of bytes left in response + self.will_close = _UNKNOWN # conn will close at end of response + + def _read_status(self): + line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1") + if len(line) > _MAXLINE: + raise LineTooLong("status line") + if self.debuglevel > 0: + print("reply:", repr(line)) + if not line: + # Presumably, the server closed the connection before + # sending a valid response. + raise RemoteDisconnected("Remote end closed connection without" + " response") + try: + version, status, reason = line.split(None, 2) + except ValueError: + try: + version, status = line.split(None, 1) + reason = "" + except ValueError: + # empty version will cause next test to fail. + version = "" + if not version.startswith("HTTP/"): + self._close_conn() + raise BadStatusLine(line) + + # The status code is a three-digit number + try: + status = int(status) + if status < 100 or status > 999: + raise BadStatusLine(line) + except ValueError: + raise BadStatusLine(line) + return version, status, reason + + def begin(self): + if self.headers is not None: + # we've already started reading the response + return + + # read until we get a non-100 response + while True: + version, status, reason = self._read_status() + if status != CONTINUE: + break + # skip the header from the 100 response + while True: + skip = self.fp.readline(_MAXLINE + 1) + if len(skip) > _MAXLINE: + raise LineTooLong("header line") + skip = skip.strip() + if not skip: + break + if self.debuglevel > 0: + print("header:", skip) + + self.code = self.status = status + self.reason = reason.strip() + if version in ("HTTP/1.0", "HTTP/0.9"): + # Some servers might still return "0.9", treat it as 1.0 anyway + self.version = 10 + elif version.startswith("HTTP/1."): + self.version = 11 # use HTTP/1.1 code for HTTP/1.x where x>=1 + else: + raise UnknownProtocol(version) + + self.headers = self.msg = parse_headers(self.fp) + + if self.debuglevel > 0: + for hdr in self.headers: + print("header:", hdr, end=" ") + + # are we using the chunked-style of transfer encoding? + tr_enc = self.headers.get("transfer-encoding") + if tr_enc and tr_enc.lower() == "chunked": + self.chunked = True + self.chunk_left = None + else: + self.chunked = False + + # will the connection close at the end of the response? + self.will_close = self._check_close() + + # do we have a Content-Length? + # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked" + self.length = None + length = self.headers.get("content-length") + + # are we using the chunked-style of transfer encoding? + tr_enc = self.headers.get("transfer-encoding") + if length and not self.chunked: + try: + self.length = int(length) + except ValueError: + self.length = None + else: + if self.length < 0: # ignore nonsensical negative lengths + self.length = None + else: + self.length = None + + # does the body have a fixed length? (of zero) + if (status == NO_CONTENT or status == NOT_MODIFIED or + 100 <= status < 200 or # 1xx codes + self._method == "HEAD"): + self.length = 0 + + # if the connection remains open, and we aren't using chunked, and + # a content-length was not provided, then assume that the connection + # WILL close. + if (not self.will_close and + not self.chunked and + self.length is None): + self.will_close = True + + def _check_close(self): + conn = self.headers.get("connection") + if self.version == 11: + # An HTTP/1.1 proxy is assumed to stay open unless + # explicitly closed. + conn = self.headers.get("connection") + if conn and "close" in conn.lower(): + return True + return False + + # Some HTTP/1.0 implementations have support for persistent + # connections, using rules different than HTTP/1.1. + + # For older HTTP, Keep-Alive indicates persistent connection. + if self.headers.get("keep-alive"): + return False + + # At least Akamai returns a "Connection: Keep-Alive" header, + # which was supposed to be sent by the client. + if conn and "keep-alive" in conn.lower(): + return False + + # Proxy-Connection is a netscape hack. + pconn = self.headers.get("proxy-connection") + if pconn and "keep-alive" in pconn.lower(): + return False + + # otherwise, assume it will close + return True + + def _close_conn(self): + fp = self.fp + self.fp = None + fp.close() + + def close(self): + try: + super().close() # set "closed" flag + finally: + if self.fp: + self._close_conn() + + # These implementations are for the benefit of io.BufferedReader. + + # XXX This class should probably be revised to act more like + # the "raw stream" that BufferedReader expects. + + def flush(self): + super().flush() + if self.fp: + self.fp.flush() + + def readable(self): + """Always returns True""" + return True + + # End of "raw stream" methods + + def isclosed(self): + """True if the connection is closed.""" + # NOTE: it is possible that we will not ever call self.close(). This + # case occurs when will_close is TRUE, length is None, and we + # read up to the last byte, but NOT past it. + # + # IMPLIES: if will_close is FALSE, then self.close() will ALWAYS be + # called, meaning self.isclosed() is meaningful. + return self.fp is None + + def read(self, amt=None): + if self.fp is None: + return b"" + + if self._method == "HEAD": + self._close_conn() + return b"" + + if amt is not None: + # Amount is given, implement using readinto + b = bytearray(amt) + n = self.readinto(b) + return memoryview(b)[:n].tobytes() + else: + # Amount is not given (unbounded read) so we must check self.length + # and self.chunked + + if self.chunked: + return self._readall_chunked() + + if self.length is None: + s = self.fp.read() + else: + try: + s = self._safe_read(self.length) + except IncompleteRead: + self._close_conn() + raise + self.length = 0 + self._close_conn() # we read everything + return s + + def readinto(self, b): + """Read up to len(b) bytes into bytearray b and return the number + of bytes read. + """ + + if self.fp is None: + return 0 + + if self._method == "HEAD": + self._close_conn() + return 0 + + if self.chunked: + return self._readinto_chunked(b) + + if self.length is not None: + if len(b) > self.length: + # clip the read to the "end of response" + b = memoryview(b)[0:self.length] + + # we do not use _safe_read() here because this may be a .will_close + # connection, and the user is reading more bytes than will be provided + # (for example, reading in 1k chunks) + n = self.fp.readinto(b) + if not n and b: + # Ideally, we would raise IncompleteRead if the content-length + # wasn't satisfied, but it might break compatibility. + self._close_conn() + elif self.length is not None: + self.length -= n + if not self.length: + self._close_conn() + return n + + def _read_next_chunk_size(self): + # Read the next chunk size from the file + line = self.fp.readline(_MAXLINE + 1) + if len(line) > _MAXLINE: + raise LineTooLong("chunk size") + i = line.find(b";") + if i >= 0: + line = line[:i] # strip chunk-extensions + try: + return int(line, 16) + except ValueError: + # close the connection as protocol synchronisation is + # probably lost + self._close_conn() + raise + + def _read_and_discard_trailer(self): + # read and discard trailer up to the CRLF terminator + ### note: we shouldn't have any trailers! + while True: + line = self.fp.readline(_MAXLINE + 1) + if len(line) > _MAXLINE: + raise LineTooLong("trailer line") + if not line: + # a vanishingly small number of sites EOF without + # sending the trailer + break + if line in (b'\r\n', b'\n', b''): + break + + def _get_chunk_left(self): + # return self.chunk_left, reading a new chunk if necessary. + # chunk_left == 0: at the end of the current chunk, need to close it + # chunk_left == None: No current chunk, should read next. + # This function returns non-zero or None if the last chunk has + # been read. + chunk_left = self.chunk_left + if not chunk_left: # Can be 0 or None + if chunk_left is not None: + # We are at the end of chunk. dicard chunk end + self._safe_read(2) # toss the CRLF at the end of the chunk + try: + chunk_left = self._read_next_chunk_size() + except ValueError: + raise IncompleteRead(b'') + if chunk_left == 0: + # last chunk: 1*("0") [ chunk-extension ] CRLF + self._read_and_discard_trailer() + # we read everything; close the "file" + self._close_conn() + chunk_left = None + self.chunk_left = chunk_left + return chunk_left + + def _readall_chunked(self): + assert self.chunked != _UNKNOWN + value = [] + try: + while True: + chunk_left = self._get_chunk_left() + if chunk_left is None: + break + value.append(self._safe_read(chunk_left)) + self.chunk_left = 0 + return b''.join(value) + except IncompleteRead: + raise IncompleteRead(b''.join(value)) + + def _readinto_chunked(self, b): + assert self.chunked != _UNKNOWN + total_bytes = 0 + mvb = memoryview(b) + try: + while True: + chunk_left = self._get_chunk_left() + if chunk_left is None: + return total_bytes + + if len(mvb) <= chunk_left: + n = self._safe_readinto(mvb) + self.chunk_left = chunk_left - n + return total_bytes + n + + temp_mvb = mvb[:chunk_left] + n = self._safe_readinto(temp_mvb) + mvb = mvb[n:] + total_bytes += n + self.chunk_left = 0 + + except IncompleteRead: + raise IncompleteRead(bytes(b[0:total_bytes])) + + def _safe_read(self, amt): + """Read the number of bytes requested, compensating for partial reads. + + Normally, we have a blocking socket, but a read() can be interrupted + by a signal (resulting in a partial read). + + Note that we cannot distinguish between EOF and an interrupt when zero + bytes have been read. IncompleteRead() will be raised in this + situation. + + This function should be used when bytes "should" be present for + reading. If the bytes are truly not available (due to EOF), then the + IncompleteRead exception can be used to detect the problem. + """ + s = [] + while amt > 0: + chunk = self.fp.read(min(amt, MAXAMOUNT)) + if not chunk: + raise IncompleteRead(b''.join(s), amt) + s.append(chunk) + amt -= len(chunk) + return b"".join(s) + + def _safe_readinto(self, b): + """Same as _safe_read, but for reading into a buffer.""" + total_bytes = 0 + mvb = memoryview(b) + while total_bytes < len(b): + if MAXAMOUNT < len(mvb): + temp_mvb = mvb[0:MAXAMOUNT] + n = self.fp.readinto(temp_mvb) + else: + n = self.fp.readinto(mvb) + if not n: + raise IncompleteRead(bytes(mvb[0:total_bytes]), len(b)) + mvb = mvb[n:] + total_bytes += n + return total_bytes + + def read1(self, n=-1): + """Read with at most one underlying system call. If at least one + byte is buffered, return that instead. + """ + if self.fp is None or self._method == "HEAD": + return b"" + if self.chunked: + return self._read1_chunked(n) + if self.length is not None and (n < 0 or n > self.length): + n = self.length + try: + result = self.fp.read1(n) + except ValueError: + if n >= 0: + raise + # some implementations, like BufferedReader, don't support -1 + # Read an arbitrarily selected largeish chunk. + result = self.fp.read1(16*1024) + if not result and n: + self._close_conn() + elif self.length is not None: + self.length -= len(result) + return result + + def peek(self, n=-1): + # Having this enables IOBase.readline() to read more than one + # byte at a time + if self.fp is None or self._method == "HEAD": + return b"" + if self.chunked: + return self._peek_chunked(n) + return self.fp.peek(n) + + def readline(self, limit=-1): + if self.fp is None or self._method == "HEAD": + return b"" + if self.chunked: + # Fallback to IOBase readline which uses peek() and read() + return super().readline(limit) + if self.length is not None and (limit < 0 or limit > self.length): + limit = self.length + result = self.fp.readline(limit) + if not result and limit: + self._close_conn() + elif self.length is not None: + self.length -= len(result) + return result + + def _read1_chunked(self, n): + # Strictly speaking, _get_chunk_left() may cause more than one read, + # but that is ok, since that is to satisfy the chunked protocol. + chunk_left = self._get_chunk_left() + if chunk_left is None or n == 0: + return b'' + if not (0 <= n <= chunk_left): + n = chunk_left # if n is negative or larger than chunk_left + read = self.fp.read1(n) + self.chunk_left -= len(read) + if not read: + raise IncompleteRead(b"") + return read + + def _peek_chunked(self, n): + # Strictly speaking, _get_chunk_left() may cause more than one read, + # but that is ok, since that is to satisfy the chunked protocol. + try: + chunk_left = self._get_chunk_left() + except IncompleteRead: + return b'' # peek doesn't worry about protocol + if chunk_left is None: + return b'' # eof + # peek is allowed to return more than requested. Just request the + # entire chunk, and truncate what we get. + return self.fp.peek(chunk_left)[:chunk_left] + + def fileno(self): + return self.fp.fileno() + + def getheader(self, name, default=None): + '''Returns the value of the header matching *name*. + + If there are multiple matching headers, the values are + combined into a single string separated by commas and spaces. + + If no matching header is found, returns *default* or None if + the *default* is not specified. + + If the headers are unknown, raises http.client.ResponseNotReady. + + ''' + if self.headers is None: + raise ResponseNotReady() + headers = self.headers.get_all(name) or default + if isinstance(headers, str) or not hasattr(headers, '__iter__'): + return headers + else: + return ', '.join(headers) + + def getheaders(self): + """Return list of (header, value) tuples.""" + if self.headers is None: + raise ResponseNotReady() + return list(self.headers.items()) + + # We override IOBase.__iter__ so that it doesn't check for closed-ness + + def __iter__(self): + return self + + # For compatibility with old-style urllib responses. + + def info(self): + '''Returns an instance of the class mimetools.Message containing + meta-information associated with the URL. + + When the method is HTTP, these headers are those returned by + the server at the head of the retrieved HTML page (including + Content-Length and Content-Type). + + When the method is FTP, a Content-Length header will be + present if (as is now usual) the server passed back a file + length in response to the FTP retrieval request. A + Content-Type header will be present if the MIME type can be + guessed. + + When the method is local-file, returned headers will include + a Date representing the file's last-modified time, a + Content-Length giving file size, and a Content-Type + containing a guess at the file's type. See also the + description of the mimetools module. + + ''' + return self.headers + + def geturl(self): + '''Return the real URL of the page. + + In some cases, the HTTP server redirects a client to another + URL. The urlopen() function handles this transparently, but in + some cases the caller needs to know which URL the client was + redirected to. The geturl() method can be used to get at this + redirected URL. + + ''' + return self.url + + def getcode(self): + '''Return the HTTP status code that was sent with the response, + or None if the URL is not an HTTP URL. + + ''' + return self.status + +class HTTPConnection: + + _http_vsn = 11 + _http_vsn_str = 'HTTP/1.1' + + response_class = HTTPResponse + default_port = HTTP_PORT + auto_open = 1 + debuglevel = 0 + + @staticmethod + def _is_textIO(stream): + """Test whether a file-like object is a text or a binary stream. + """ + return isinstance(stream, io.TextIOBase) + + @staticmethod + def _get_content_length(body, method): + """Get the content-length based on the body. + + If the body is None, we set Content-Length: 0 for methods that expect + a body (RFC 7230, Section 3.3.2). We also set the Content-Length for + any method if the body is a str or bytes-like object and not a file. + """ + if body is None: + # do an explicit check for not None here to distinguish + # between unset and set but empty + if method.upper() in _METHODS_EXPECTING_BODY: + return 0 + else: + return None + + if hasattr(body, 'read'): + # file-like object. + return None + + try: + # does it implement the buffer protocol (bytes, bytearray, array)? + mv = memoryview(body) + return mv.nbytes + except TypeError: + pass + + if isinstance(body, str): + return len(body) + + return None + + def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None): + self.timeout = timeout + self.source_address = source_address + self.sock = None + self._buffer = [] + self.__response = None + self.__state = _CS_IDLE + self._method = None + self._tunnel_host = None + self._tunnel_port = None + self._tunnel_headers = {} + + (self.host, self.port) = self._get_hostport(host, port) + + # This is stored as an instance variable to allow unit + # tests to replace it with a suitable mockup + self._create_connection = socket.create_connection + + def set_tunnel(self, host, port=None, headers=None): + """Set up host and port for HTTP CONNECT tunnelling. + + In a connection that uses HTTP CONNECT tunneling, the host passed to the + constructor is used as a proxy server that relays all communication to + the endpoint passed to `set_tunnel`. This done by sending an HTTP + CONNECT request to the proxy server when the connection is established. + + This method must be called before the HTML connection has been + established. + + The headers argument should be a mapping of extra HTTP headers to send + with the CONNECT request. + """ + + if self.sock: + raise RuntimeError("Can't set up tunnel for established connection") + + self._tunnel_host, self._tunnel_port = self._get_hostport(host, port) + if headers: + self._tunnel_headers = headers + else: + self._tunnel_headers.clear() + + def _get_hostport(self, host, port): + if port is None: + i = host.rfind(':') + j = host.rfind(']') # ipv6 addresses have [...] + if i > j: + try: + port = int(host[i+1:]) + except ValueError: + if host[i+1:] == "": # http://foo.com:/ == http://foo.com/ + port = self.default_port + else: + raise InvalidURL("nonnumeric port: '%s'" % host[i+1:]) + host = host[:i] + else: + port = self.default_port + if host and host[0] == '[' and host[-1] == ']': + host = host[1:-1] + + return (host, port) + + def set_debuglevel(self, level): + self.debuglevel = level + + def _tunnel(self): + connect_str = "CONNECT %s:%d HTTP/1.0\r\n" % (self._tunnel_host, + self._tunnel_port) + connect_bytes = connect_str.encode("ascii") + self.send(connect_bytes) + for header, value in self._tunnel_headers.items(): + header_str = "%s: %s\r\n" % (header, value) + header_bytes = header_str.encode("latin-1") + self.send(header_bytes) + self.send(b'\r\n') + + response = self.response_class(self.sock, method=self._method) + (version, code, message) = response._read_status() + + if code != http.HTTPStatus.OK: + self.close() + raise OSError("Tunnel connection failed: %d %s" % (code, + message.strip())) + while True: + line = response.fp.readline(_MAXLINE + 1) + if len(line) > _MAXLINE: + raise 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()) + + def connect(self): + """Connect to the host and port specified in __init__.""" + self.sock = self._create_connection( + (self.host,self.port), self.timeout, self.source_address) + self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + + if self._tunnel_host: + self._tunnel() + + def close(self): + """Close the connection to the HTTP server.""" + self.__state = _CS_IDLE + try: + sock = self.sock + if sock: + self.sock = None + sock.close() # close it manually... there may be other refs + finally: + response = self.__response + if response: + self.__response = None + response.close() + + def send(self, data): + """Send `data' to the server. + ``data`` can be a string object, a bytes object, an array object, a + file-like object that supports a .read() method, or an iterable object. + """ + + if self.sock is None: + if self.auto_open: + self.connect() + else: + raise NotConnected() + + if self.debuglevel > 0: + print("send:", repr(data)) + blocksize = 8192 + if hasattr(data, "read") : + if self.debuglevel > 0: + print("sendIng a read()able") + encode = False + try: + mode = data.mode + except AttributeError: + # io.BytesIO and other file-like objects don't have a `mode` + # attribute. + pass + else: + if "b" not in mode: + encode = True + if self.debuglevel > 0: + print("encoding file using iso-8859-1") + while 1: + datablock = data.read(blocksize) + if not datablock: + break + if encode: + datablock = datablock.encode("iso-8859-1") + self.sock.sendall(datablock) + return + try: + self.sock.sendall(data) + except TypeError: + if isinstance(data, collections.Iterable): + for d in data: + self.sock.sendall(d) + else: + raise TypeError("data should be a bytes-like object " + "or an iterable, got %r" % type(data)) + + def _output(self, s): + """Add a line of output to the current request buffer. + + Assumes that the line does *not* end with \\r\\n. + """ + self._buffer.append(s) + + def _read_readable(self, readable): + blocksize = 8192 + if self.debuglevel > 0: + print("sendIng a read()able") + encode = self._is_textIO(readable) + if encode and self.debuglevel > 0: + print("encoding file using iso-8859-1") + while True: + datablock = readable.read(blocksize) + if not datablock: + break + if encode: + datablock = datablock.encode("iso-8859-1") + yield datablock + + def _send_output(self, message_body=None, encode_chunked=False): + """Send the currently buffered request and clear the buffer. + + Appends an extra \\r\\n to the buffer. + A message_body may be specified, to be appended to the request. + """ + self._buffer.extend((b"", b"")) + msg = b"\r\n".join(self._buffer) + del self._buffer[:] + self.send(msg) + + if message_body is not None: + + # create a consistent interface to message_body + if hasattr(message_body, 'read'): + # Let file-like take precedence over byte-like. This + # is needed to allow the current position of mmap'ed + # files to be taken into account. + chunks = self._read_readable(message_body) + else: + try: + # this is solely to check to see if message_body + # implements the buffer API. it /would/ be easier + # to capture if PyObject_CheckBuffer was exposed + # to Python. + memoryview(message_body) + except TypeError: + try: + chunks = iter(message_body) + except TypeError: + raise TypeError("message_body should be a bytes-like " + "object or an iterable, got %r" + % type(message_body)) + else: + # the object implements the buffer interface and + # can be passed directly into socket methods + chunks = (message_body,) + + for chunk in chunks: + if not chunk: + if self.debuglevel > 0: + print('Zero length chunk ignored') + continue + + if encode_chunked and self._http_vsn == 11: + # chunked encoding + chunk = '{0:X}\r\n'.format(len(chunk)).encode('ascii') + chunk + b'\r\n' + self.send(chunk) + + if encode_chunked and self._http_vsn == 11: + # end chunked transfer + self.send(b'0\r\n\r\n') + + def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0): + """Send a request to the server. + + `method' specifies an HTTP request method, e.g. 'GET'. + `url' specifies the object being requested, e.g. '/index.html'. + `skip_host' if True does not add automatically a 'Host:' header + `skip_accept_encoding' if True does not add automatically an + 'Accept-Encoding:' header + """ + + # if a prior response has been completed, then forget about it. + if self.__response and self.__response.isclosed(): + self.__response = None + + + # in certain cases, we cannot issue another request on this connection. + # this occurs when: + # 1) we are in the process of sending a request. (_CS_REQ_STARTED) + # 2) a response to a previous request has signalled that it is going + # to close the connection upon completion. + # 3) the headers for the previous response have not been read, thus + # we cannot determine whether point (2) is true. (_CS_REQ_SENT) + # + # if there is no prior response, then we can request at will. + # + # if point (2) is true, then we will have passed the socket to the + # response (effectively meaning, "there is no prior response"), and + # will open a new one when a new request is made. + # + # Note: if a prior response exists, then we *can* start a new request. + # We are not allowed to begin fetching the response to this new + # request, however, until that prior response is complete. + # + if self.__state == _CS_IDLE: + self.__state = _CS_REQ_STARTED + else: + raise CannotSendRequest(self.__state) + + # Save the method we use, we need it later in the response phase + self._method = method + if not url: + url = '/' + request = '%s %s %s' % (method, url, self._http_vsn_str) + + # Non-ASCII characters should have been eliminated earlier + self._output(request.encode('ascii')) + + if self._http_vsn == 11: + # Issue some standard headers for better HTTP/1.1 compliance + + if not skip_host: + # this header is issued *only* for HTTP/1.1 + # connections. more specifically, this means it is + # only issued when the client uses the new + # HTTPConnection() class. backwards-compat clients + # will be using HTTP/1.0 and those clients may be + # issuing this header themselves. we should NOT issue + # it twice; some web servers (such as Apache) barf + # when they see two Host: headers + + # If we need a non-standard port,include it in the + # header. If the request is going through a proxy, + # but the host of the actual URL, not the host of the + # proxy. + + netloc = '' + if url.startswith('http'): + nil, netloc, nil, nil, nil = urlsplit(url) + + if netloc: + try: + netloc_enc = netloc.encode("ascii") + except UnicodeEncodeError: + netloc_enc = netloc.encode("idna") + self.putheader('Host', netloc_enc) + else: + if self._tunnel_host: + host = self._tunnel_host + port = self._tunnel_port + else: + host = self.host + port = self.port + + try: + host_enc = host.encode("ascii") + except UnicodeEncodeError: + host_enc = host.encode("idna") + + # As per RFC 273, IPv6 address should be wrapped with [] + # when used as Host header + + if host.find(':') >= 0: + host_enc = b'[' + host_enc + b']' + + if port == self.default_port: + self.putheader('Host', host_enc) + else: + host_enc = host_enc.decode("ascii") + self.putheader('Host', "%s:%s" % (host_enc, port)) + + # note: we are assuming that clients will not attempt to set these + # headers since *this* library must deal with the + # consequences. this also means that when the supporting + # libraries are updated to recognize other forms, then this + # code should be changed (removed or updated). + + # we only want a Content-Encoding of "identity" since we don't + # support encodings such as x-gzip or x-deflate. + if not skip_accept_encoding: + self.putheader('Accept-Encoding', 'identity') + + # we can accept "chunked" Transfer-Encodings, but no others + # NOTE: no TE header implies *only* "chunked" + #self.putheader('TE', 'chunked') + + # if TE is supplied in the header, then it must appear in a + # Connection header. + #self.putheader('Connection', 'TE') + + else: + # For HTTP/1.0, the server will assume "not chunked" + pass + + def putheader(self, header, *values): + """Send a request header line to the server. + + For example: h.putheader('Accept', 'text/html') + """ + if self.__state != _CS_REQ_STARTED: + raise CannotSendHeader() + + if hasattr(header, 'encode'): + header = header.encode('ascii') + + if not _is_legal_header_name(header): + raise ValueError('Invalid header name %r' % (header,)) + + values = list(values) + for i, one_value in enumerate(values): + if hasattr(one_value, 'encode'): + values[i] = one_value.encode('latin-1') + elif isinstance(one_value, int): + values[i] = str(one_value).encode('ascii') + + if _is_illegal_header_value(values[i]): + raise ValueError('Invalid header value %r' % (values[i],)) + + value = b'\r\n\t'.join(values) + header = header + b': ' + value + self._output(header) + + def endheaders(self, message_body=None, **kwds): + """Indicate that the last header line has been sent to the server. + + This method sends the request to the server. The optional message_body + argument can be used to pass a message body associated with the + request. + """ + encode_chunked = kwds.pop('encode_chunked', False) + if kwds: + # mimic interpreter error for unrecognized keyword + raise TypeError("endheaders() got an unexpected keyword argument '{0}'" + .format(kwds.popitem()[0])) + + if self.__state == _CS_REQ_STARTED: + self.__state = _CS_REQ_SENT + else: + raise CannotSendHeader() + self._send_output(message_body, encode_chunked=encode_chunked) + + def request(self, method, url, body=None, headers={}, **kwds): + """Send a complete request to the server.""" + encode_chunked = kwds.pop('encode_chunked', False) + if kwds: + # mimic interpreter error for unrecognized keyword + raise TypeError("request() got an unexpected keyword argument '{0}'" + .format(kwds.popitem()[0])) + self._send_request(method, url, body, headers, encode_chunked) + + def _set_content_length(self, body, method): + # Set the content-length based on the body. If the body is "empty", we + # set Content-Length: 0 for methods that expect a body (RFC 7230, + # Section 3.3.2). If the body is set for other methods, we set the + # header provided we can figure out what the length is. + thelen = None + method_expects_body = method.upper() in _METHODS_EXPECTING_BODY + if body is None and method_expects_body: + thelen = '0' + elif body is not None: + try: + thelen = str(len(body)) + except TypeError: + # If this is a file-like object, try to + # fstat its file descriptor + try: + thelen = str(os.fstat(body.fileno()).st_size) + except (AttributeError, OSError): + # Don't send a length if this failed + if self.debuglevel > 0: print("Cannot stat!!") + + if thelen is not None: + self.putheader('Content-Length', thelen) + + def _send_request(self, method, url, body, headers, encode_chunked): + # Honor explicitly requested Host: and Accept-Encoding: headers. + header_names = frozenset(k.lower() for k in headers) + skips = {} + if 'host' in header_names: + skips['skip_host'] = 1 + if 'accept-encoding' in header_names: + skips['skip_accept_encoding'] = 1 + + self.putrequest(method, url, **skips) + + # chunked encoding will happen if HTTP/1.1 is used and either + # the caller passes encode_chunked=True or the following + # conditions hold: + # 1. content-length has not been explicitly set + # 2. the body is a file or iterable, but not a str or bytes-like + # 3. Transfer-Encoding has NOT been explicitly set by the caller + + if 'content-length' not in header_names: + # only chunk body if not explicitly set for backwards + # compatibility, assuming the client code is already handling the + # chunking + if 'transfer-encoding' not in header_names: + # if content-length cannot be automatically determined, fall + # back to chunked encoding + encode_chunked = False + content_length = self._get_content_length(body, method) + if content_length is None: + if body is not None: + if self.debuglevel > 0: + print('Unable to determine size of %r' % body) + encode_chunked = True + self.putheader('Transfer-Encoding', 'chunked') + else: + self.putheader('Content-Length', str(content_length)) + else: + encode_chunked = False + + for hdr, value in headers.items(): + self.putheader(hdr, value) + if isinstance(body, str): + # RFC 2616 Section 3.7.1 says that text default has a + # default charset of iso-8859-1. + body = _encode(body, 'body') + self.endheaders(body, encode_chunked=encode_chunked) + + def getresponse(self): + """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. + """ + + # if a prior response has been completed, then forget about it. + if self.__response and self.__response.isclosed(): + self.__response = None + + # if a prior response exists, then it must be completed (otherwise, we + # cannot read this response's header to determine the connection-close + # behavior) + # + # note: if a prior response existed, but was connection-close, then the + # socket and response were made independent of this HTTPConnection + # object since a new request requires that we open a whole new + # connection + # + # this means the prior response had one of two states: + # 1) will_close: this connection was reset and the prior socket and + # response operate independently + # 2) persistent: the response was retained and we await its + # isclosed() status to become true. + # + if self.__state != _CS_REQ_SENT or self.__response: + raise ResponseNotReady(self.__state) + + if self.debuglevel > 0: + response = self.response_class(self.sock, self.debuglevel, + method=self._method) + else: + response = self.response_class(self.sock, method=self._method) + + try: + try: + response.begin() + except ConnectionError: + self.close() + raise + assert response.will_close != _UNKNOWN + self.__state = _CS_IDLE + + if response.will_close: + # this effectively passes the connection to the response + self.close() + else: + # remember this, so we can tell when it is complete + self.__response = response + + return response + except: + response.close() + raise + +try: + import ssl +except ImportError: + pass +else: + class HTTPSConnection(HTTPConnection): + "This class allows communication via SSL." + + default_port = HTTPS_PORT + + # XXX Should key_file and cert_file be deprecated in favour of context? + + def __init__(self, host, port=None, key_file=None, cert_file=None, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, *, context=None, + check_hostname=None): + super(HTTPSConnection, self).__init__(host, port, timeout, + source_address) + self.key_file = key_file + self.cert_file = cert_file + if context is None: + context = ssl._create_default_https_context() + will_verify = context.verify_mode != ssl.CERT_NONE + if check_hostname is None: + check_hostname = context.check_hostname + if check_hostname and not will_verify: + raise ValueError("check_hostname needs a SSL context with " + "either CERT_OPTIONAL or CERT_REQUIRED") + if key_file or cert_file: + context.load_cert_chain(cert_file, key_file) + self._context = context + self._check_hostname = check_hostname + + def connect(self): + "Connect to a host on a given (SSL) port." + + super().connect() + + if self._tunnel_host: + server_hostname = self._tunnel_host + else: + server_hostname = self.host + + self.sock = self._context.wrap_socket(self.sock, + server_hostname=server_hostname) + if not self._context.check_hostname and self._check_hostname: + try: + ssl.match_hostname(self.sock.getpeercert(), server_hostname) + except Exception: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + raise + + __all__.append("HTTPSConnection") + +class HTTPException(Exception): + # Subclasses that define an __init__ must call Exception.__init__ + # or define self.args. Otherwise, str() will fail. + pass + +class NotConnected(HTTPException): + pass + +class InvalidURL(HTTPException): + pass + +class UnknownProtocol(HTTPException): + def __init__(self, version): + self.args = version, + self.version = version + +class UnknownTransferEncoding(HTTPException): + pass + +class UnimplementedFileMode(HTTPException): + pass + +class IncompleteRead(HTTPException): + def __init__(self, partial, expected=None): + self.args = partial, + self.partial = partial + self.expected = expected + def __repr__(self): + if self.expected is not None: + e = ', %i more expected' % self.expected + else: + e = '' + return '%s(%i bytes read%s)' % (self.__class__.__name__, + len(self.partial), e) + def __str__(self): + return repr(self) + +class ImproperConnectionState(HTTPException): + pass + +class CannotSendRequest(ImproperConnectionState): + pass + +class CannotSendHeader(ImproperConnectionState): + pass + +class ResponseNotReady(ImproperConnectionState): + pass + +class BadStatusLine(HTTPException): + def __init__(self, line): + if not line: + line = repr(line) + self.args = line, + self.line = line + +class LineTooLong(HTTPException): + def __init__(self, line_type): + HTTPException.__init__(self, "got more than %d bytes when reading %s" + % (_MAXLINE, line_type)) + +class RemoteDisconnected(ConnectionResetError, BadStatusLine): + def __init__(self, *pos, **kw): + BadStatusLine.__init__(self, "") + ConnectionResetError.__init__(self, *pos, **kw) + +# for backwards compatibility +error = HTTPException diff --git a/venv/lib/python3.12/site-packages/eventlet/green/http/cookiejar.py b/venv/lib/python3.12/site-packages/eventlet/green/http/cookiejar.py new file mode 100644 index 0000000..9c884e9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/http/cookiejar.py @@ -0,0 +1,2152 @@ +# This is part of Python source code with Eventlet-specific modifications. +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +# 2011, 2012, 2013, 2014, 2015, 2016 Python Software Foundation; All Rights +# Reserved +# +# 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, +# 2011, 2012, 2013, 2014, 2015, 2016 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. +r"""HTTP cookie handling for web clients. + +This module has (now fairly distant) origins in Gisle Aas' Perl module +HTTP::Cookies, from the libwww-perl library. + +Docstrings, comments and debug strings in this code refer to the +attributes of the HTTP cookie system as cookie-attributes, to distinguish +them clearly from Python attributes. + +Class diagram (note that BSDDBCookieJar and the MSIE* classes are not +distributed with the Python standard library, but are available from +http://wwwsearch.sf.net/): + + CookieJar____ + / \ \ + FileCookieJar \ \ + / | \ \ \ + MozillaCookieJar | LWPCookieJar \ \ + | | \ + | ---MSIEBase | \ + | / | | \ + | / MSIEDBCookieJar BSDDBCookieJar + |/ + MSIECookieJar + +""" + +__all__ = ['Cookie', 'CookieJar', 'CookiePolicy', 'DefaultCookiePolicy', + 'FileCookieJar', 'LWPCookieJar', 'LoadError', 'MozillaCookieJar'] + +import copy +import datetime +import re +import time +# Eventlet change: urllib.request used to be imported here but it's not used, +# removed for clarity +import urllib.parse +from calendar import timegm + +from eventlet.green import threading as _threading, time +from eventlet.green.http import client as http_client # only for the default HTTP port + +debug = False # set to True to enable debugging via the logging module +logger = None + +def _debug(*args): + if not debug: + return + global logger + if not logger: + import logging + logger = logging.getLogger("http.cookiejar") + return logger.debug(*args) + + +DEFAULT_HTTP_PORT = str(http_client.HTTP_PORT) +MISSING_FILENAME_TEXT = ("a filename was not supplied (nor was the CookieJar " + "instance initialised with one)") + +def _warn_unhandled_exception(): + # There are a few catch-all except: statements in this module, for + # catching input that's bad in unexpected ways. Warn if any + # exceptions are caught there. + import io, warnings, traceback + f = io.StringIO() + traceback.print_exc(None, f) + msg = f.getvalue() + warnings.warn("http.cookiejar bug!\n%s" % msg, stacklevel=2) + + +# Date/time conversion +# ----------------------------------------------------------------------------- + +EPOCH_YEAR = 1970 +def _timegm(tt): + year, month, mday, hour, min, sec = tt[:6] + if ((year >= EPOCH_YEAR) and (1 <= month <= 12) and (1 <= mday <= 31) and + (0 <= hour <= 24) and (0 <= min <= 59) and (0 <= sec <= 61)): + return timegm(tt) + else: + return None + +DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] +MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] +MONTHS_LOWER = [] +for month in MONTHS: MONTHS_LOWER.append(month.lower()) + +def time2isoz(t=None): + """Return a string representing time in seconds since epoch, t. + + If the function is called without an argument, it will use the current + time. + + The format of the returned string is like "YYYY-MM-DD hh:mm:ssZ", + representing Universal Time (UTC, aka GMT). An example of this format is: + + 1994-11-24 08:49:37Z + + """ + if t is None: + dt = datetime.datetime.utcnow() + else: + dt = datetime.datetime.utcfromtimestamp(t) + return "%04d-%02d-%02d %02d:%02d:%02dZ" % ( + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) + +def time2netscape(t=None): + """Return a string representing time in seconds since epoch, t. + + If the function is called without an argument, it will use the current + time. + + The format of the returned string is like this: + + Wed, DD-Mon-YYYY HH:MM:SS GMT + + """ + if t is None: + dt = datetime.datetime.utcnow() + else: + dt = datetime.datetime.utcfromtimestamp(t) + return "%s %02d-%s-%04d %02d:%02d:%02d GMT" % ( + DAYS[dt.weekday()], dt.day, MONTHS[dt.month-1], + dt.year, dt.hour, dt.minute, dt.second) + + +UTC_ZONES = {"GMT": None, "UTC": None, "UT": None, "Z": None} + +TIMEZONE_RE = re.compile(r"^([-+])?(\d\d?):?(\d\d)?$", re.ASCII) +def offset_from_tz_string(tz): + offset = None + if tz in UTC_ZONES: + offset = 0 + else: + m = TIMEZONE_RE.search(tz) + if m: + offset = 3600 * int(m.group(2)) + if m.group(3): + offset = offset + 60 * int(m.group(3)) + if m.group(1) == '-': + offset = -offset + return offset + +def _str2time(day, mon, yr, hr, min, sec, tz): + yr = int(yr) + if yr > datetime.MAXYEAR: + return None + + # translate month name to number + # month numbers start with 1 (January) + try: + mon = MONTHS_LOWER.index(mon.lower())+1 + except ValueError: + # maybe it's already a number + try: + imon = int(mon) + except ValueError: + return None + if 1 <= imon <= 12: + mon = imon + else: + return None + + # make sure clock elements are defined + if hr is None: hr = 0 + if min is None: min = 0 + if sec is None: sec = 0 + + day = int(day) + hr = int(hr) + min = int(min) + sec = int(sec) + + if yr < 1000: + # find "obvious" year + cur_yr = time.localtime(time.time())[0] + m = cur_yr % 100 + tmp = yr + yr = yr + cur_yr - m + m = m - tmp + if abs(m) > 50: + if m > 0: yr = yr + 100 + else: yr = yr - 100 + + # convert UTC time tuple to seconds since epoch (not timezone-adjusted) + t = _timegm((yr, mon, day, hr, min, sec, tz)) + + if t is not None: + # adjust time using timezone string, to get absolute time since epoch + if tz is None: + tz = "UTC" + tz = tz.upper() + offset = offset_from_tz_string(tz) + if offset is None: + return None + t = t - offset + + return t + +STRICT_DATE_RE = re.compile( + r"^[SMTWF][a-z][a-z], (\d\d) ([JFMASOND][a-z][a-z]) " + "(\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT$", re.ASCII) +WEEKDAY_RE = re.compile( + r"^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)[a-z]*,?\s*", re.I | re.ASCII) +LOOSE_HTTP_DATE_RE = re.compile( + r"""^ + (\d\d?) # day + (?:\s+|[-\/]) + (\w+) # month + (?:\s+|[-\/]) + (\d+) # year + (?: + (?:\s+|:) # separator before clock + (\d\d?):(\d\d) # hour:min + (?::(\d\d))? # optional seconds + )? # optional clock + \s* + ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+)? # timezone + \s* + (?:\(\w+\))? # ASCII representation of timezone in parens. + \s*$""", re.X | re.ASCII) +def http2time(text): + """Returns time in seconds since epoch of time represented by a string. + + Return value is an integer. + + None is returned if the format of str is unrecognized, the time is outside + the representable range, or the timezone string is not recognized. If the + string contains no timezone, UTC is assumed. + + The timezone in the string may be numerical (like "-0800" or "+0100") or a + string timezone (like "UTC", "GMT", "BST" or "EST"). Currently, only the + timezone strings equivalent to UTC (zero offset) are known to the function. + + The function loosely parses the following formats: + + Wed, 09 Feb 1994 22:23:32 GMT -- HTTP format + Tuesday, 08-Feb-94 14:15:29 GMT -- old rfc850 HTTP format + Tuesday, 08-Feb-1994 14:15:29 GMT -- broken rfc850 HTTP format + 09 Feb 1994 22:23:32 GMT -- HTTP format (no weekday) + 08-Feb-94 14:15:29 GMT -- rfc850 format (no weekday) + 08-Feb-1994 14:15:29 GMT -- broken rfc850 format (no weekday) + + The parser ignores leading and trailing whitespace. The time may be + absent. + + If the year is given with only 2 digits, the function will select the + century that makes the year closest to the current date. + + """ + # fast exit for strictly conforming string + m = STRICT_DATE_RE.search(text) + if m: + g = m.groups() + mon = MONTHS_LOWER.index(g[1].lower()) + 1 + tt = (int(g[2]), mon, int(g[0]), + int(g[3]), int(g[4]), float(g[5])) + return _timegm(tt) + + # No, we need some messy parsing... + + # clean up + text = text.lstrip() + text = WEEKDAY_RE.sub("", text, 1) # Useless weekday + + # tz is time zone specifier string + day, mon, yr, hr, min, sec, tz = [None]*7 + + # loose regexp parse + m = LOOSE_HTTP_DATE_RE.search(text) + if m is not None: + day, mon, yr, hr, min, sec, tz = m.groups() + else: + return None # bad format + + return _str2time(day, mon, yr, hr, min, sec, tz) + +ISO_DATE_RE = re.compile( + """^ + (\d{4}) # year + [-\/]? + (\d\d?) # numerical month + [-\/]? + (\d\d?) # day + (?: + (?:\s+|[-:Tt]) # separator before clock + (\d\d?):?(\d\d) # hour:min + (?::?(\d\d(?:\.\d*)?))? # optional seconds (and fractional) + )? # optional clock + \s* + ([-+]?\d\d?:?(:?\d\d)? + |Z|z)? # timezone (Z is "zero meridian", i.e. GMT) + \s*$""", re.X | re. ASCII) +def iso2time(text): + """ + As for http2time, but parses the ISO 8601 formats: + + 1994-02-03 14:15:29 -0100 -- ISO 8601 format + 1994-02-03 14:15:29 -- zone is optional + 1994-02-03 -- only date + 1994-02-03T14:15:29 -- Use T as separator + 19940203T141529Z -- ISO 8601 compact format + 19940203 -- only date + + """ + # clean up + text = text.lstrip() + + # tz is time zone specifier string + day, mon, yr, hr, min, sec, tz = [None]*7 + + # loose regexp parse + m = ISO_DATE_RE.search(text) + if m is not None: + # XXX there's an extra bit of the timezone I'm ignoring here: is + # this the right thing to do? + yr, mon, day, hr, min, sec, tz, _ = m.groups() + else: + return None # bad format + + return _str2time(day, mon, yr, hr, min, sec, tz) + + +# Header parsing +# ----------------------------------------------------------------------------- + +def unmatched(match): + """Return unmatched part of re.Match object.""" + start, end = match.span(0) + return match.string[:start]+match.string[end:] + +HEADER_TOKEN_RE = re.compile(r"^\s*([^=\s;,]+)") +HEADER_QUOTED_VALUE_RE = re.compile(r"^\s*=\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"") +HEADER_VALUE_RE = re.compile(r"^\s*=\s*([^\s;,]*)") +HEADER_ESCAPE_RE = re.compile(r"\\(.)") +def split_header_words(header_values): + r"""Parse header values into a list of lists containing key,value pairs. + + The function knows how to deal with ",", ";" and "=" as well as quoted + values after "=". A list of space separated tokens are parsed as if they + were separated by ";". + + If the header_values passed as argument contains multiple values, then they + are treated as if they were a single value separated by comma ",". + + This means that this function is useful for parsing header fields that + follow this syntax (BNF as from the HTTP/1.1 specification, but we relax + the requirement for tokens). + + headers = #header + header = (token | parameter) *( [";"] (token | parameter)) + + token = 1* + separators = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + + quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + qdtext = > + quoted-pair = "\" CHAR + + parameter = attribute "=" value + attribute = token + value = token | quoted-string + + Each header is represented by a list of key/value pairs. The value for a + simple token (not part of a parameter) is None. Syntactically incorrect + headers will not necessarily be parsed as you would want. + + This is easier to describe with some examples: + + >>> split_header_words(['foo="bar"; port="80,81"; discard, bar=baz']) + [[('foo', 'bar'), ('port', '80,81'), ('discard', None)], [('bar', 'baz')]] + >>> split_header_words(['text/html; charset="iso-8859-1"']) + [[('text/html', None), ('charset', 'iso-8859-1')]] + >>> split_header_words([r'Basic realm="\"foo\bar\""']) + [[('Basic', None), ('realm', '"foobar"')]] + + """ + assert not isinstance(header_values, str) + result = [] + for text in header_values: + orig_text = text + pairs = [] + while text: + m = HEADER_TOKEN_RE.search(text) + if m: + text = unmatched(m) + name = m.group(1) + m = HEADER_QUOTED_VALUE_RE.search(text) + if m: # quoted value + text = unmatched(m) + value = m.group(1) + value = HEADER_ESCAPE_RE.sub(r"\1", value) + else: + m = HEADER_VALUE_RE.search(text) + if m: # unquoted value + text = unmatched(m) + value = m.group(1) + value = value.rstrip() + else: + # no value, a lone token + value = None + pairs.append((name, value)) + elif text.lstrip().startswith(","): + # concatenated headers, as per RFC 2616 section 4.2 + text = text.lstrip()[1:] + if pairs: result.append(pairs) + pairs = [] + else: + # skip junk + non_junk, nr_junk_chars = re.subn("^[=\s;]*", "", text) + assert nr_junk_chars > 0, ( + "split_header_words bug: '%s', '%s', %s" % + (orig_text, text, pairs)) + text = non_junk + if pairs: result.append(pairs) + return result + +HEADER_JOIN_ESCAPE_RE = re.compile(r"([\"\\])") +def join_header_words(lists): + """Do the inverse (almost) of the conversion done by split_header_words. + + Takes a list of lists of (key, value) pairs and produces a single header + value. Attribute values are quoted if needed. + + >>> join_header_words([[("text/plain", None), ("charset", "iso-8859-1")]]) + 'text/plain; charset="iso-8859-1"' + >>> join_header_words([[("text/plain", None)], [("charset", "iso-8859-1")]]) + 'text/plain, charset="iso-8859-1"' + + """ + headers = [] + for pairs in lists: + attr = [] + for k, v in pairs: + if v is not None: + if not re.search(r"^\w+$", v): + v = HEADER_JOIN_ESCAPE_RE.sub(r"\\\1", v) # escape " and \ + v = '"%s"' % v + k = "%s=%s" % (k, v) + attr.append(k) + if attr: headers.append("; ".join(attr)) + return ", ".join(headers) + +def strip_quotes(text): + if text.startswith('"'): + text = text[1:] + if text.endswith('"'): + text = text[:-1] + return text + +def parse_ns_headers(ns_headers): + """Ad-hoc parser for Netscape protocol cookie-attributes. + + The old Netscape cookie format for Set-Cookie can for instance contain + an unquoted "," in the expires field, so we have to use this ad-hoc + parser instead of split_header_words. + + XXX This may not make the best possible effort to parse all the crap + that Netscape Cookie headers contain. Ronald Tschalar's HTTPClient + parser is probably better, so could do worse than following that if + this ever gives any trouble. + + Currently, this is also used for parsing RFC 2109 cookies. + + """ + known_attrs = ("expires", "domain", "path", "secure", + # RFC 2109 attrs (may turn up in Netscape cookies, too) + "version", "port", "max-age") + + result = [] + for ns_header in ns_headers: + pairs = [] + version_set = False + + # XXX: The following does not strictly adhere to RFCs in that empty + # names and values are legal (the former will only appear once and will + # be overwritten if multiple occurrences are present). This is + # mostly to deal with backwards compatibility. + for ii, param in enumerate(ns_header.split(';')): + param = param.strip() + + key, sep, val = param.partition('=') + key = key.strip() + + if not key: + if ii == 0: + break + else: + continue + + # allow for a distinction between present and empty and missing + # altogether + val = val.strip() if sep else None + + if ii != 0: + lc = key.lower() + if lc in known_attrs: + key = lc + + if key == "version": + # This is an RFC 2109 cookie. + if val is not None: + val = strip_quotes(val) + version_set = True + elif key == "expires": + # convert expires date to seconds since epoch + if val is not None: + val = http2time(strip_quotes(val)) # None if invalid + pairs.append((key, val)) + + if pairs: + if not version_set: + pairs.append(("version", "0")) + result.append(pairs) + + return result + + +IPV4_RE = re.compile(r"\.\d+$", re.ASCII) +def is_HDN(text): + """Return True if text is a host domain name.""" + # XXX + # This may well be wrong. Which RFC is HDN defined in, if any (for + # the purposes of RFC 2965)? + # For the current implementation, what about IPv6? Remember to look + # at other uses of IPV4_RE also, if change this. + if IPV4_RE.search(text): + return False + if text == "": + return False + if text[0] == "." or text[-1] == ".": + return False + return True + +def domain_match(A, B): + """Return True if domain A domain-matches domain B, according to RFC 2965. + + A and B may be host domain names or IP addresses. + + RFC 2965, section 1: + + Host names can be specified either as an IP address or a HDN string. + Sometimes we compare one host name with another. (Such comparisons SHALL + be case-insensitive.) Host A's name domain-matches host B's if + + * their host name strings string-compare equal; or + + * A is a HDN string and has the form NB, where N is a non-empty + name string, B has the form .B', and B' is a HDN string. (So, + x.y.com domain-matches .Y.com but not Y.com.) + + Note that domain-match is not a commutative operation: a.b.c.com + domain-matches .c.com, but not the reverse. + + """ + # Note that, if A or B are IP addresses, the only relevant part of the + # definition of the domain-match algorithm is the direct string-compare. + A = A.lower() + B = B.lower() + if A == B: + return True + if not is_HDN(A): + return False + i = A.rfind(B) + if i == -1 or i == 0: + # A does not have form NB, or N is the empty string + return False + if not B.startswith("."): + return False + if not is_HDN(B[1:]): + return False + return True + +def liberal_is_HDN(text): + """Return True if text is a sort-of-like a host domain name. + + For accepting/blocking domains. + + """ + if IPV4_RE.search(text): + return False + return True + +def user_domain_match(A, B): + """For blocking/accepting domains. + + A and B may be host domain names or IP addresses. + + """ + A = A.lower() + B = B.lower() + if not (liberal_is_HDN(A) and liberal_is_HDN(B)): + if A == B: + # equal IP addresses + return True + return False + initial_dot = B.startswith(".") + if initial_dot and A.endswith(B): + return True + if not initial_dot and A == B: + return True + return False + +cut_port_re = re.compile(r":\d+$", re.ASCII) +def request_host(request): + """Return request-host, as defined by RFC 2965. + + Variation from RFC: returned value is lowercased, for convenient + comparison. + + """ + url = request.get_full_url() + host = urllib.parse.urlparse(url)[1] + if host == "": + host = request.get_header("Host", "") + + # remove port, if present + host = cut_port_re.sub("", host, 1) + return host.lower() + +def eff_request_host(request): + """Return a tuple (request-host, effective request-host name). + + As defined by RFC 2965, except both are lowercased. + + """ + erhn = req_host = request_host(request) + if req_host.find(".") == -1 and not IPV4_RE.search(req_host): + erhn = req_host + ".local" + return req_host, erhn + +def request_path(request): + """Path component of request-URI, as defined by RFC 2965.""" + url = request.get_full_url() + parts = urllib.parse.urlsplit(url) + path = escape_path(parts.path) + if not path.startswith("/"): + # fix bad RFC 2396 absoluteURI + path = "/" + path + return path + +def request_port(request): + host = request.host + i = host.find(':') + if i >= 0: + port = host[i+1:] + try: + int(port) + except ValueError: + _debug("nonnumeric port: '%s'", port) + return None + else: + port = DEFAULT_HTTP_PORT + return port + +# Characters in addition to A-Z, a-z, 0-9, '_', '.', and '-' that don't +# need to be escaped to form a valid HTTP URL (RFCs 2396 and 1738). +HTTP_PATH_SAFE = "%/;:@&=+$,!~*'()" +ESCAPED_CHAR_RE = re.compile(r"%([0-9a-fA-F][0-9a-fA-F])") +def uppercase_escaped_char(match): + return "%%%s" % match.group(1).upper() +def escape_path(path): + """Escape any invalid characters in HTTP URL, and uppercase all escapes.""" + # There's no knowing what character encoding was used to create URLs + # containing %-escapes, but since we have to pick one to escape invalid + # path characters, we pick UTF-8, as recommended in the HTML 4.0 + # specification: + # http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.1 + # And here, kind of: draft-fielding-uri-rfc2396bis-03 + # (And in draft IRI specification: draft-duerst-iri-05) + # (And here, for new URI schemes: RFC 2718) + path = urllib.parse.quote(path, HTTP_PATH_SAFE) + path = ESCAPED_CHAR_RE.sub(uppercase_escaped_char, path) + return path + +def reach(h): + """Return reach of host h, as defined by RFC 2965, section 1. + + The reach R of a host name H is defined as follows: + + * If + + - H is the host domain name of a host; and, + + - H has the form A.B; and + + - A has no embedded (that is, interior) dots; and + + - B has at least one embedded dot, or B is the string "local". + then the reach of H is .B. + + * Otherwise, the reach of H is H. + + >>> reach("www.acme.com") + '.acme.com' + >>> reach("acme.com") + 'acme.com' + >>> reach("acme.local") + '.local' + + """ + i = h.find(".") + if i >= 0: + #a = h[:i] # this line is only here to show what a is + b = h[i+1:] + i = b.find(".") + if is_HDN(h) and (i >= 0 or b == "local"): + return "."+b + return h + +def is_third_party(request): + """ + + RFC 2965, section 3.3.6: + + An unverifiable transaction is to a third-party host if its request- + host U does not domain-match the reach R of the request-host O in the + origin transaction. + + """ + req_host = request_host(request) + if not domain_match(req_host, reach(request.origin_req_host)): + return True + else: + return False + + +class Cookie: + """HTTP Cookie. + + This class represents both Netscape and RFC 2965 cookies. + + This is deliberately a very simple class. It just holds attributes. It's + possible to construct Cookie instances that don't comply with the cookie + standards. CookieJar.make_cookies is the factory function for Cookie + objects -- it deals with cookie parsing, supplying defaults, and + normalising to the representation used in this class. CookiePolicy is + responsible for checking them to see whether they should be accepted from + and returned to the server. + + Note that the port may be present in the headers, but unspecified ("Port" + rather than"Port=80", for example); if this is the case, port is None. + + """ + + def __init__(self, version, name, value, + port, port_specified, + domain, domain_specified, domain_initial_dot, + path, path_specified, + secure, + expires, + discard, + comment, + comment_url, + rest, + rfc2109=False, + ): + + if version is not None: version = int(version) + if expires is not None: expires = int(float(expires)) + if port is None and port_specified is True: + raise ValueError("if port is None, port_specified must be false") + + self.version = version + self.name = name + self.value = value + self.port = port + self.port_specified = port_specified + # normalise case, as per RFC 2965 section 3.3.3 + self.domain = domain.lower() + self.domain_specified = domain_specified + # Sigh. We need to know whether the domain given in the + # cookie-attribute had an initial dot, in order to follow RFC 2965 + # (as clarified in draft errata). Needed for the returned $Domain + # value. + self.domain_initial_dot = domain_initial_dot + self.path = path + self.path_specified = path_specified + self.secure = secure + self.expires = expires + self.discard = discard + self.comment = comment + self.comment_url = comment_url + self.rfc2109 = rfc2109 + + self._rest = copy.copy(rest) + + def has_nonstandard_attr(self, name): + return name in self._rest + def get_nonstandard_attr(self, name, default=None): + return self._rest.get(name, default) + def set_nonstandard_attr(self, name, value): + self._rest[name] = value + + def is_expired(self, now=None): + if now is None: now = time.time() + if (self.expires is not None) and (self.expires <= now): + return True + return False + + def __str__(self): + if self.port is None: p = "" + else: p = ":"+self.port + limit = self.domain + p + self.path + if self.value is not None: + namevalue = "%s=%s" % (self.name, self.value) + else: + namevalue = self.name + return "" % (namevalue, limit) + + def __repr__(self): + args = [] + for name in ("version", "name", "value", + "port", "port_specified", + "domain", "domain_specified", "domain_initial_dot", + "path", "path_specified", + "secure", "expires", "discard", "comment", "comment_url", + ): + attr = getattr(self, name) + args.append("%s=%s" % (name, repr(attr))) + args.append("rest=%s" % repr(self._rest)) + args.append("rfc2109=%s" % repr(self.rfc2109)) + return "%s(%s)" % (self.__class__.__name__, ", ".join(args)) + + +class CookiePolicy: + """Defines which cookies get accepted from and returned to server. + + May also modify cookies, though this is probably a bad idea. + + The subclass DefaultCookiePolicy defines the standard rules for Netscape + and RFC 2965 cookies -- override that if you want a customised policy. + + """ + def set_ok(self, cookie, request): + """Return true if (and only if) cookie should be accepted from server. + + Currently, pre-expired cookies never get this far -- the CookieJar + class deletes such cookies itself. + + """ + raise NotImplementedError() + + def return_ok(self, cookie, request): + """Return true if (and only if) cookie should be returned to server.""" + raise NotImplementedError() + + def domain_return_ok(self, domain, request): + """Return false if cookies should not be returned, given cookie domain. + """ + return True + + def path_return_ok(self, path, request): + """Return false if cookies should not be returned, given cookie path. + """ + return True + + +class DefaultCookiePolicy(CookiePolicy): + """Implements the standard rules for accepting and returning cookies.""" + + DomainStrictNoDots = 1 + DomainStrictNonDomain = 2 + DomainRFC2965Match = 4 + + DomainLiberal = 0 + DomainStrict = DomainStrictNoDots|DomainStrictNonDomain + + def __init__(self, + blocked_domains=None, allowed_domains=None, + netscape=True, rfc2965=False, + rfc2109_as_netscape=None, + hide_cookie2=False, + strict_domain=False, + strict_rfc2965_unverifiable=True, + strict_ns_unverifiable=False, + strict_ns_domain=DomainLiberal, + strict_ns_set_initial_dollar=False, + strict_ns_set_path=False, + ): + """Constructor arguments should be passed as keyword arguments only.""" + self.netscape = netscape + self.rfc2965 = rfc2965 + self.rfc2109_as_netscape = rfc2109_as_netscape + self.hide_cookie2 = hide_cookie2 + self.strict_domain = strict_domain + self.strict_rfc2965_unverifiable = strict_rfc2965_unverifiable + self.strict_ns_unverifiable = strict_ns_unverifiable + self.strict_ns_domain = strict_ns_domain + self.strict_ns_set_initial_dollar = strict_ns_set_initial_dollar + self.strict_ns_set_path = strict_ns_set_path + + if blocked_domains is not None: + self._blocked_domains = tuple(blocked_domains) + else: + self._blocked_domains = () + + if allowed_domains is not None: + allowed_domains = tuple(allowed_domains) + self._allowed_domains = allowed_domains + + def blocked_domains(self): + """Return the sequence of blocked domains (as a tuple).""" + return self._blocked_domains + def set_blocked_domains(self, blocked_domains): + """Set the sequence of blocked domains.""" + self._blocked_domains = tuple(blocked_domains) + + def is_blocked(self, domain): + for blocked_domain in self._blocked_domains: + if user_domain_match(domain, blocked_domain): + return True + return False + + def allowed_domains(self): + """Return None, or the sequence of allowed domains (as a tuple).""" + return self._allowed_domains + def set_allowed_domains(self, allowed_domains): + """Set the sequence of allowed domains, or None.""" + if allowed_domains is not None: + allowed_domains = tuple(allowed_domains) + self._allowed_domains = allowed_domains + + def is_not_allowed(self, domain): + if self._allowed_domains is None: + return False + for allowed_domain in self._allowed_domains: + if user_domain_match(domain, allowed_domain): + return False + return True + + def set_ok(self, cookie, request): + """ + If you override .set_ok(), be sure to call this method. If it returns + false, so should your subclass (assuming your subclass wants to be more + strict about which cookies to accept). + + """ + _debug(" - checking cookie %s=%s", cookie.name, cookie.value) + + assert cookie.name is not None + + for n in "version", "verifiability", "name", "path", "domain", "port": + fn_name = "set_ok_"+n + fn = getattr(self, fn_name) + if not fn(cookie, request): + return False + + return True + + def set_ok_version(self, cookie, request): + if cookie.version is None: + # Version is always set to 0 by parse_ns_headers if it's a Netscape + # cookie, so this must be an invalid RFC 2965 cookie. + _debug(" Set-Cookie2 without version attribute (%s=%s)", + cookie.name, cookie.value) + return False + if cookie.version > 0 and not self.rfc2965: + _debug(" RFC 2965 cookies are switched off") + return False + elif cookie.version == 0 and not self.netscape: + _debug(" Netscape cookies are switched off") + return False + return True + + def set_ok_verifiability(self, cookie, request): + if request.unverifiable and is_third_party(request): + if cookie.version > 0 and self.strict_rfc2965_unverifiable: + _debug(" third-party RFC 2965 cookie during " + "unverifiable transaction") + return False + elif cookie.version == 0 and self.strict_ns_unverifiable: + _debug(" third-party Netscape cookie during " + "unverifiable transaction") + return False + return True + + def set_ok_name(self, cookie, request): + # Try and stop servers setting V0 cookies designed to hack other + # servers that know both V0 and V1 protocols. + if (cookie.version == 0 and self.strict_ns_set_initial_dollar and + cookie.name.startswith("$")): + _debug(" illegal name (starts with '$'): '%s'", cookie.name) + return False + return True + + def set_ok_path(self, cookie, request): + if cookie.path_specified: + req_path = request_path(request) + if ((cookie.version > 0 or + (cookie.version == 0 and self.strict_ns_set_path)) and + not req_path.startswith(cookie.path)): + _debug(" path attribute %s is not a prefix of request " + "path %s", cookie.path, req_path) + return False + return True + + def set_ok_domain(self, cookie, request): + if self.is_blocked(cookie.domain): + _debug(" domain %s is in user block-list", cookie.domain) + return False + if self.is_not_allowed(cookie.domain): + _debug(" domain %s is not in user allow-list", cookie.domain) + return False + if cookie.domain_specified: + req_host, erhn = eff_request_host(request) + domain = cookie.domain + if self.strict_domain and (domain.count(".") >= 2): + # XXX This should probably be compared with the Konqueror + # (kcookiejar.cpp) and Mozilla implementations, but it's a + # losing battle. + i = domain.rfind(".") + j = domain.rfind(".", 0, i) + if j == 0: # domain like .foo.bar + tld = domain[i+1:] + sld = domain[j+1:i] + if sld.lower() in ("co", "ac", "com", "edu", "org", "net", + "gov", "mil", "int", "aero", "biz", "cat", "coop", + "info", "jobs", "mobi", "museum", "name", "pro", + "travel", "eu") and len(tld) == 2: + # domain like .co.uk + _debug(" country-code second level domain %s", domain) + return False + if domain.startswith("."): + undotted_domain = domain[1:] + else: + undotted_domain = domain + embedded_dots = (undotted_domain.find(".") >= 0) + if not embedded_dots and domain != ".local": + _debug(" non-local domain %s contains no embedded dot", + domain) + return False + if cookie.version == 0: + if (not erhn.endswith(domain) and + (not erhn.startswith(".") and + not ("."+erhn).endswith(domain))): + _debug(" effective request-host %s (even with added " + "initial dot) does not end with %s", + erhn, domain) + return False + if (cookie.version > 0 or + (self.strict_ns_domain & self.DomainRFC2965Match)): + if not domain_match(erhn, domain): + _debug(" effective request-host %s does not domain-match " + "%s", erhn, domain) + return False + if (cookie.version > 0 or + (self.strict_ns_domain & self.DomainStrictNoDots)): + host_prefix = req_host[:-len(domain)] + if (host_prefix.find(".") >= 0 and + not IPV4_RE.search(req_host)): + _debug(" host prefix %s for domain %s contains a dot", + host_prefix, domain) + return False + return True + + def set_ok_port(self, cookie, request): + if cookie.port_specified: + req_port = request_port(request) + if req_port is None: + req_port = "80" + else: + req_port = str(req_port) + for p in cookie.port.split(","): + try: + int(p) + except ValueError: + _debug(" bad port %s (not numeric)", p) + return False + if p == req_port: + break + else: + _debug(" request port (%s) not found in %s", + req_port, cookie.port) + return False + return True + + def return_ok(self, cookie, request): + """ + If you override .return_ok(), be sure to call this method. If it + returns false, so should your subclass (assuming your subclass wants to + be more strict about which cookies to return). + + """ + # Path has already been checked by .path_return_ok(), and domain + # blocking done by .domain_return_ok(). + _debug(" - checking cookie %s=%s", cookie.name, cookie.value) + + for n in "version", "verifiability", "secure", "expires", "port", "domain": + fn_name = "return_ok_"+n + fn = getattr(self, fn_name) + if not fn(cookie, request): + return False + return True + + def return_ok_version(self, cookie, request): + if cookie.version > 0 and not self.rfc2965: + _debug(" RFC 2965 cookies are switched off") + return False + elif cookie.version == 0 and not self.netscape: + _debug(" Netscape cookies are switched off") + return False + return True + + def return_ok_verifiability(self, cookie, request): + if request.unverifiable and is_third_party(request): + if cookie.version > 0 and self.strict_rfc2965_unverifiable: + _debug(" third-party RFC 2965 cookie during unverifiable " + "transaction") + return False + elif cookie.version == 0 and self.strict_ns_unverifiable: + _debug(" third-party Netscape cookie during unverifiable " + "transaction") + return False + return True + + def return_ok_secure(self, cookie, request): + if cookie.secure and request.type != "https": + _debug(" secure cookie with non-secure request") + return False + return True + + def return_ok_expires(self, cookie, request): + if cookie.is_expired(self._now): + _debug(" cookie expired") + return False + return True + + def return_ok_port(self, cookie, request): + if cookie.port: + req_port = request_port(request) + if req_port is None: + req_port = "80" + for p in cookie.port.split(","): + if p == req_port: + break + else: + _debug(" request port %s does not match cookie port %s", + req_port, cookie.port) + return False + return True + + def return_ok_domain(self, cookie, request): + req_host, erhn = eff_request_host(request) + domain = cookie.domain + + # strict check of non-domain cookies: Mozilla does this, MSIE5 doesn't + if (cookie.version == 0 and + (self.strict_ns_domain & self.DomainStrictNonDomain) and + not cookie.domain_specified and domain != erhn): + _debug(" cookie with unspecified domain does not string-compare " + "equal to request domain") + return False + + if cookie.version > 0 and not domain_match(erhn, domain): + _debug(" effective request-host name %s does not domain-match " + "RFC 2965 cookie domain %s", erhn, domain) + return False + if cookie.version == 0 and not ("."+erhn).endswith(domain): + _debug(" request-host %s does not match Netscape cookie domain " + "%s", req_host, domain) + return False + return True + + def domain_return_ok(self, domain, request): + # Liberal check of. This is here as an optimization to avoid + # having to load lots of MSIE cookie files unless necessary. + req_host, erhn = eff_request_host(request) + if not req_host.startswith("."): + req_host = "."+req_host + if not erhn.startswith("."): + erhn = "."+erhn + if not (req_host.endswith(domain) or erhn.endswith(domain)): + #_debug(" request domain %s does not match cookie domain %s", + # req_host, domain) + return False + + if self.is_blocked(domain): + _debug(" domain %s is in user block-list", domain) + return False + if self.is_not_allowed(domain): + _debug(" domain %s is not in user allow-list", domain) + return False + + return True + + def path_return_ok(self, path, request): + _debug("- checking cookie path=%s", path) + req_path = request_path(request) + if not req_path.startswith(path): + _debug(" %s does not path-match %s", req_path, path) + return False + return True + + +def vals_sorted_by_key(adict): + keys = sorted(adict.keys()) + return map(adict.get, keys) + +def deepvalues(mapping): + """Iterates over nested mapping, depth-first, in sorted order by key.""" + values = vals_sorted_by_key(mapping) + for obj in values: + mapping = False + try: + obj.items + except AttributeError: + pass + else: + mapping = True + yield from deepvalues(obj) + if not mapping: + yield obj + + +# Used as second parameter to dict.get() method, to distinguish absent +# dict key from one with a None value. +class Absent: pass + +class CookieJar: + """Collection of HTTP cookies. + + You may not need to know about this class: try + urllib.request.build_opener(HTTPCookieProcessor).open(url). + """ + + non_word_re = re.compile(r"\W") + quote_re = re.compile(r"([\"\\])") + strict_domain_re = re.compile(r"\.?[^.]*") + domain_re = re.compile(r"[^.]*") + dots_re = re.compile(r"^\.+") + + magic_re = re.compile(r"^\#LWP-Cookies-(\d+\.\d+)", re.ASCII) + + def __init__(self, policy=None): + if policy is None: + policy = DefaultCookiePolicy() + self._policy = policy + + self._cookies_lock = _threading.RLock() + self._cookies = {} + + def set_policy(self, policy): + self._policy = policy + + def _cookies_for_domain(self, domain, request): + cookies = [] + if not self._policy.domain_return_ok(domain, request): + return [] + _debug("Checking %s for cookies to return", domain) + cookies_by_path = self._cookies[domain] + for path in cookies_by_path.keys(): + if not self._policy.path_return_ok(path, request): + continue + cookies_by_name = cookies_by_path[path] + for cookie in cookies_by_name.values(): + if not self._policy.return_ok(cookie, request): + _debug(" not returning cookie") + continue + _debug(" it's a match") + cookies.append(cookie) + return cookies + + def _cookies_for_request(self, request): + """Return a list of cookies to be returned to server.""" + cookies = [] + for domain in self._cookies.keys(): + cookies.extend(self._cookies_for_domain(domain, request)) + return cookies + + def _cookie_attrs(self, cookies): + """Return a list of cookie-attributes to be returned to server. + + like ['foo="bar"; $Path="/"', ...] + + The $Version attribute is also added when appropriate (currently only + once per request). + + """ + # add cookies in order of most specific (ie. longest) path first + cookies.sort(key=lambda a: len(a.path), reverse=True) + + version_set = False + + attrs = [] + for cookie in cookies: + # set version of Cookie header + # XXX + # What should it be if multiple matching Set-Cookie headers have + # different versions themselves? + # Answer: there is no answer; was supposed to be settled by + # RFC 2965 errata, but that may never appear... + version = cookie.version + if not version_set: + version_set = True + if version > 0: + attrs.append("$Version=%s" % version) + + # quote cookie value if necessary + # (not for Netscape protocol, which already has any quotes + # intact, due to the poorly-specified Netscape Cookie: syntax) + if ((cookie.value is not None) and + self.non_word_re.search(cookie.value) and version > 0): + value = self.quote_re.sub(r"\\\1", cookie.value) + else: + value = cookie.value + + # add cookie-attributes to be returned in Cookie header + if cookie.value is None: + attrs.append(cookie.name) + else: + attrs.append("%s=%s" % (cookie.name, value)) + if version > 0: + if cookie.path_specified: + attrs.append('$Path="%s"' % cookie.path) + if cookie.domain.startswith("."): + domain = cookie.domain + if (not cookie.domain_initial_dot and + domain.startswith(".")): + domain = domain[1:] + attrs.append('$Domain="%s"' % domain) + if cookie.port is not None: + p = "$Port" + if cookie.port_specified: + p = p + ('="%s"' % cookie.port) + attrs.append(p) + + return attrs + + def add_cookie_header(self, request): + """Add correct Cookie: header to request (urllib.request.Request object). + + The Cookie2 header is also added unless policy.hide_cookie2 is true. + + """ + _debug("add_cookie_header") + self._cookies_lock.acquire() + try: + + self._policy._now = self._now = int(time.time()) + + cookies = self._cookies_for_request(request) + + attrs = self._cookie_attrs(cookies) + if attrs: + if not request.has_header("Cookie"): + request.add_unredirected_header( + "Cookie", "; ".join(attrs)) + + # if necessary, advertise that we know RFC 2965 + if (self._policy.rfc2965 and not self._policy.hide_cookie2 and + not request.has_header("Cookie2")): + for cookie in cookies: + if cookie.version != 1: + request.add_unredirected_header("Cookie2", '$Version="1"') + break + + finally: + self._cookies_lock.release() + + self.clear_expired_cookies() + + def _normalized_cookie_tuples(self, attrs_set): + """Return list of tuples containing normalised cookie information. + + attrs_set is the list of lists of key,value pairs extracted from + the Set-Cookie or Set-Cookie2 headers. + + Tuples are name, value, standard, rest, where name and value are the + cookie name and value, standard is a dictionary containing the standard + cookie-attributes (discard, secure, version, expires or max-age, + domain, path and port) and rest is a dictionary containing the rest of + the cookie-attributes. + + """ + cookie_tuples = [] + + boolean_attrs = "discard", "secure" + value_attrs = ("version", + "expires", "max-age", + "domain", "path", "port", + "comment", "commenturl") + + for cookie_attrs in attrs_set: + name, value = cookie_attrs[0] + + # Build dictionary of standard cookie-attributes (standard) and + # dictionary of other cookie-attributes (rest). + + # Note: expiry time is normalised to seconds since epoch. V0 + # cookies should have the Expires cookie-attribute, and V1 cookies + # should have Max-Age, but since V1 includes RFC 2109 cookies (and + # since V0 cookies may be a mish-mash of Netscape and RFC 2109), we + # accept either (but prefer Max-Age). + max_age_set = False + + bad_cookie = False + + standard = {} + rest = {} + for k, v in cookie_attrs[1:]: + lc = k.lower() + # don't lose case distinction for unknown fields + if lc in value_attrs or lc in boolean_attrs: + k = lc + if k in boolean_attrs and v is None: + # boolean cookie-attribute is present, but has no value + # (like "discard", rather than "port=80") + v = True + if k in standard: + # only first value is significant + continue + if k == "domain": + if v is None: + _debug(" missing value for domain attribute") + bad_cookie = True + break + # RFC 2965 section 3.3.3 + v = v.lower() + if k == "expires": + if max_age_set: + # Prefer max-age to expires (like Mozilla) + continue + if v is None: + _debug(" missing or invalid value for expires " + "attribute: treating as session cookie") + continue + if k == "max-age": + max_age_set = True + try: + v = int(v) + except ValueError: + _debug(" missing or invalid (non-numeric) value for " + "max-age attribute") + bad_cookie = True + break + # convert RFC 2965 Max-Age to seconds since epoch + # XXX Strictly you're supposed to follow RFC 2616 + # age-calculation rules. Remember that zero Max-Age + # is a request to discard (old and new) cookie, though. + k = "expires" + v = self._now + v + if (k in value_attrs) or (k in boolean_attrs): + if (v is None and + k not in ("port", "comment", "commenturl")): + _debug(" missing value for %s attribute" % k) + bad_cookie = True + break + standard[k] = v + else: + rest[k] = v + + if bad_cookie: + continue + + cookie_tuples.append((name, value, standard, rest)) + + return cookie_tuples + + def _cookie_from_cookie_tuple(self, tup, request): + # standard is dict of standard cookie-attributes, rest is dict of the + # rest of them + name, value, standard, rest = tup + + domain = standard.get("domain", Absent) + path = standard.get("path", Absent) + port = standard.get("port", Absent) + expires = standard.get("expires", Absent) + + # set the easy defaults + version = standard.get("version", None) + if version is not None: + try: + version = int(version) + except ValueError: + return None # invalid version, ignore cookie + secure = standard.get("secure", False) + # (discard is also set if expires is Absent) + discard = standard.get("discard", False) + comment = standard.get("comment", None) + comment_url = standard.get("commenturl", None) + + # set default path + if path is not Absent and path != "": + path_specified = True + path = escape_path(path) + else: + path_specified = False + path = request_path(request) + i = path.rfind("/") + if i != -1: + if version == 0: + # Netscape spec parts company from reality here + path = path[:i] + else: + path = path[:i+1] + if len(path) == 0: path = "/" + + # set default domain + domain_specified = domain is not Absent + # but first we have to remember whether it starts with a dot + domain_initial_dot = False + if domain_specified: + domain_initial_dot = bool(domain.startswith(".")) + if domain is Absent: + req_host, erhn = eff_request_host(request) + domain = erhn + elif not domain.startswith("."): + domain = "."+domain + + # set default port + port_specified = False + if port is not Absent: + if port is None: + # Port attr present, but has no value: default to request port. + # Cookie should then only be sent back on that port. + port = request_port(request) + else: + port_specified = True + port = re.sub(r"\s+", "", port) + else: + # No port attr present. Cookie can be sent back on any port. + port = None + + # set default expires and discard + if expires is Absent: + expires = None + discard = True + elif expires <= self._now: + # Expiry date in past is request to delete cookie. This can't be + # in DefaultCookiePolicy, because can't delete cookies there. + try: + self.clear(domain, path, name) + except KeyError: + pass + _debug("Expiring cookie, domain='%s', path='%s', name='%s'", + domain, path, name) + return None + + return Cookie(version, + name, value, + port, port_specified, + domain, domain_specified, domain_initial_dot, + path, path_specified, + secure, + expires, + discard, + comment, + comment_url, + rest) + + def _cookies_from_attrs_set(self, attrs_set, request): + cookie_tuples = self._normalized_cookie_tuples(attrs_set) + + cookies = [] + for tup in cookie_tuples: + cookie = self._cookie_from_cookie_tuple(tup, request) + if cookie: cookies.append(cookie) + return cookies + + def _process_rfc2109_cookies(self, cookies): + rfc2109_as_ns = getattr(self._policy, 'rfc2109_as_netscape', None) + if rfc2109_as_ns is None: + rfc2109_as_ns = not self._policy.rfc2965 + for cookie in cookies: + if cookie.version == 1: + cookie.rfc2109 = True + if rfc2109_as_ns: + # treat 2109 cookies as Netscape cookies rather than + # as RFC2965 cookies + cookie.version = 0 + + def make_cookies(self, response, request): + """Return sequence of Cookie objects extracted from response object.""" + # get cookie-attributes for RFC 2965 and Netscape protocols + headers = response.info() + rfc2965_hdrs = headers.get_all("Set-Cookie2", []) + ns_hdrs = headers.get_all("Set-Cookie", []) + + rfc2965 = self._policy.rfc2965 + netscape = self._policy.netscape + + if ((not rfc2965_hdrs and not ns_hdrs) or + (not ns_hdrs and not rfc2965) or + (not rfc2965_hdrs and not netscape) or + (not netscape and not rfc2965)): + return [] # no relevant cookie headers: quick exit + + try: + cookies = self._cookies_from_attrs_set( + split_header_words(rfc2965_hdrs), request) + except Exception: + _warn_unhandled_exception() + cookies = [] + + if ns_hdrs and netscape: + try: + # RFC 2109 and Netscape cookies + ns_cookies = self._cookies_from_attrs_set( + parse_ns_headers(ns_hdrs), request) + except Exception: + _warn_unhandled_exception() + ns_cookies = [] + self._process_rfc2109_cookies(ns_cookies) + + # Look for Netscape cookies (from Set-Cookie headers) that match + # corresponding RFC 2965 cookies (from Set-Cookie2 headers). + # For each match, keep the RFC 2965 cookie and ignore the Netscape + # cookie (RFC 2965 section 9.1). Actually, RFC 2109 cookies are + # bundled in with the Netscape cookies for this purpose, which is + # reasonable behaviour. + if rfc2965: + lookup = {} + for cookie in cookies: + lookup[(cookie.domain, cookie.path, cookie.name)] = None + + def no_matching_rfc2965(ns_cookie, lookup=lookup): + key = ns_cookie.domain, ns_cookie.path, ns_cookie.name + return key not in lookup + ns_cookies = filter(no_matching_rfc2965, ns_cookies) + + if ns_cookies: + cookies.extend(ns_cookies) + + return cookies + + def set_cookie_if_ok(self, cookie, request): + """Set a cookie if policy says it's OK to do so.""" + self._cookies_lock.acquire() + try: + self._policy._now = self._now = int(time.time()) + + if self._policy.set_ok(cookie, request): + self.set_cookie(cookie) + + + finally: + self._cookies_lock.release() + + def set_cookie(self, cookie): + """Set a cookie, without checking whether or not it should be set.""" + c = self._cookies + self._cookies_lock.acquire() + try: + if cookie.domain not in c: c[cookie.domain] = {} + c2 = c[cookie.domain] + if cookie.path not in c2: c2[cookie.path] = {} + c3 = c2[cookie.path] + c3[cookie.name] = cookie + finally: + self._cookies_lock.release() + + def extract_cookies(self, response, request): + """Extract cookies from response, where allowable given the request.""" + _debug("extract_cookies: %s", response.info()) + self._cookies_lock.acquire() + try: + self._policy._now = self._now = int(time.time()) + + for cookie in self.make_cookies(response, request): + if self._policy.set_ok(cookie, request): + _debug(" setting cookie: %s", cookie) + self.set_cookie(cookie) + finally: + self._cookies_lock.release() + + def clear(self, domain=None, path=None, name=None): + """Clear some cookies. + + Invoking this method without arguments will clear all cookies. If + given a single argument, only cookies belonging to that domain will be + removed. If given two arguments, cookies belonging to the specified + path within that domain are removed. If given three arguments, then + the cookie with the specified name, path and domain is removed. + + Raises KeyError if no matching cookie exists. + + """ + if name is not None: + if (domain is None) or (path is None): + raise ValueError( + "domain and path must be given to remove a cookie by name") + del self._cookies[domain][path][name] + elif path is not None: + if domain is None: + raise ValueError( + "domain must be given to remove cookies by path") + del self._cookies[domain][path] + elif domain is not None: + del self._cookies[domain] + else: + self._cookies = {} + + def clear_session_cookies(self): + """Discard all session cookies. + + Note that the .save() method won't save session cookies anyway, unless + you ask otherwise by passing a true ignore_discard argument. + + """ + self._cookies_lock.acquire() + try: + for cookie in self: + if cookie.discard: + self.clear(cookie.domain, cookie.path, cookie.name) + finally: + self._cookies_lock.release() + + def clear_expired_cookies(self): + """Discard all expired cookies. + + You probably don't need to call this method: expired cookies are never + sent back to the server (provided you're using DefaultCookiePolicy), + this method is called by CookieJar itself every so often, and the + .save() method won't save expired cookies anyway (unless you ask + otherwise by passing a true ignore_expires argument). + + """ + self._cookies_lock.acquire() + try: + now = time.time() + for cookie in self: + if cookie.is_expired(now): + self.clear(cookie.domain, cookie.path, cookie.name) + finally: + self._cookies_lock.release() + + def __iter__(self): + return deepvalues(self._cookies) + + def __len__(self): + """Return number of contained cookies.""" + i = 0 + for cookie in self: i = i + 1 + return i + + def __repr__(self): + r = [] + for cookie in self: r.append(repr(cookie)) + return "<%s[%s]>" % (self.__class__.__name__, ", ".join(r)) + + def __str__(self): + r = [] + for cookie in self: r.append(str(cookie)) + return "<%s[%s]>" % (self.__class__.__name__, ", ".join(r)) + + +# derives from OSError for backwards-compatibility with Python 2.4.0 +class LoadError(OSError): pass + +class FileCookieJar(CookieJar): + """CookieJar that can be loaded from and saved to a file.""" + + def __init__(self, filename=None, delayload=False, policy=None): + """ + Cookies are NOT loaded from the named file until either the .load() or + .revert() method is called. + + """ + CookieJar.__init__(self, policy) + if filename is not None: + try: + filename+"" + except: + raise ValueError("filename must be string-like") + self.filename = filename + self.delayload = bool(delayload) + + def save(self, filename=None, ignore_discard=False, ignore_expires=False): + """Save cookies to a file.""" + raise NotImplementedError() + + def load(self, filename=None, ignore_discard=False, ignore_expires=False): + """Load cookies from a file.""" + if filename is None: + if self.filename is not None: filename = self.filename + else: raise ValueError(MISSING_FILENAME_TEXT) + + with open(filename) as f: + self._really_load(f, filename, ignore_discard, ignore_expires) + + def revert(self, filename=None, + ignore_discard=False, ignore_expires=False): + """Clear all cookies and reload cookies from a saved file. + + Raises LoadError (or OSError) if reversion is not successful; the + object's state will not be altered if this happens. + + """ + if filename is None: + if self.filename is not None: filename = self.filename + else: raise ValueError(MISSING_FILENAME_TEXT) + + self._cookies_lock.acquire() + try: + + old_state = copy.deepcopy(self._cookies) + self._cookies = {} + try: + self.load(filename, ignore_discard, ignore_expires) + except OSError: + self._cookies = old_state + raise + + finally: + self._cookies_lock.release() + + +def lwp_cookie_str(cookie): + """Return string representation of Cookie in the LWP cookie file format. + + Actually, the format is extended a bit -- see module docstring. + + """ + h = [(cookie.name, cookie.value), + ("path", cookie.path), + ("domain", cookie.domain)] + if cookie.port is not None: h.append(("port", cookie.port)) + if cookie.path_specified: h.append(("path_spec", None)) + if cookie.port_specified: h.append(("port_spec", None)) + if cookie.domain_initial_dot: h.append(("domain_dot", None)) + if cookie.secure: h.append(("secure", None)) + if cookie.expires: h.append(("expires", + time2isoz(float(cookie.expires)))) + if cookie.discard: h.append(("discard", None)) + if cookie.comment: h.append(("comment", cookie.comment)) + if cookie.comment_url: h.append(("commenturl", cookie.comment_url)) + + keys = sorted(cookie._rest.keys()) + for k in keys: + h.append((k, str(cookie._rest[k]))) + + h.append(("version", str(cookie.version))) + + return join_header_words([h]) + +class LWPCookieJar(FileCookieJar): + """ + The LWPCookieJar saves a sequence of "Set-Cookie3" lines. + "Set-Cookie3" is the format used by the libwww-perl library, not known + to be compatible with any browser, but which is easy to read and + doesn't lose information about RFC 2965 cookies. + + Additional methods + + as_lwp_str(ignore_discard=True, ignore_expired=True) + + """ + + def as_lwp_str(self, ignore_discard=True, ignore_expires=True): + """Return cookies as a string of "\\n"-separated "Set-Cookie3" headers. + + ignore_discard and ignore_expires: see docstring for FileCookieJar.save + + """ + now = time.time() + r = [] + for cookie in self: + if not ignore_discard and cookie.discard: + continue + if not ignore_expires and cookie.is_expired(now): + continue + r.append("Set-Cookie3: %s" % lwp_cookie_str(cookie)) + return "\n".join(r+[""]) + + def save(self, filename=None, ignore_discard=False, ignore_expires=False): + if filename is None: + if self.filename is not None: filename = self.filename + else: raise ValueError(MISSING_FILENAME_TEXT) + + with open(filename, "w") as f: + # There really isn't an LWP Cookies 2.0 format, but this indicates + # that there is extra information in here (domain_dot and + # port_spec) while still being compatible with libwww-perl, I hope. + f.write("#LWP-Cookies-2.0\n") + f.write(self.as_lwp_str(ignore_discard, ignore_expires)) + + def _really_load(self, f, filename, ignore_discard, ignore_expires): + magic = f.readline() + if not self.magic_re.search(magic): + msg = ("%r does not look like a Set-Cookie3 (LWP) format " + "file" % filename) + raise LoadError(msg) + + now = time.time() + + header = "Set-Cookie3:" + boolean_attrs = ("port_spec", "path_spec", "domain_dot", + "secure", "discard") + value_attrs = ("version", + "port", "path", "domain", + "expires", + "comment", "commenturl") + + try: + while 1: + line = f.readline() + if line == "": break + if not line.startswith(header): + continue + line = line[len(header):].strip() + + for data in split_header_words([line]): + name, value = data[0] + standard = {} + rest = {} + for k in boolean_attrs: + standard[k] = False + for k, v in data[1:]: + if k is not None: + lc = k.lower() + else: + lc = None + # don't lose case distinction for unknown fields + if (lc in value_attrs) or (lc in boolean_attrs): + k = lc + if k in boolean_attrs: + if v is None: v = True + standard[k] = v + elif k in value_attrs: + standard[k] = v + else: + rest[k] = v + + h = standard.get + expires = h("expires") + discard = h("discard") + if expires is not None: + expires = iso2time(expires) + if expires is None: + discard = True + domain = h("domain") + domain_specified = domain.startswith(".") + c = Cookie(h("version"), name, value, + h("port"), h("port_spec"), + domain, domain_specified, h("domain_dot"), + h("path"), h("path_spec"), + h("secure"), + expires, + discard, + h("comment"), + h("commenturl"), + rest) + if not ignore_discard and c.discard: + continue + if not ignore_expires and c.is_expired(now): + continue + self.set_cookie(c) + except OSError: + raise + except Exception: + _warn_unhandled_exception() + raise LoadError("invalid Set-Cookie3 format file %r: %r" % + (filename, line)) + + +class MozillaCookieJar(FileCookieJar): + """ + + WARNING: you may want to backup your browser's cookies file if you use + this class to save cookies. I *think* it works, but there have been + bugs in the past! + + This class differs from CookieJar only in the format it uses to save and + load cookies to and from a file. This class uses the Mozilla/Netscape + `cookies.txt' format. lynx uses this file format, too. + + Don't expect cookies saved while the browser is running to be noticed by + the browser (in fact, Mozilla on unix will overwrite your saved cookies if + you change them on disk while it's running; on Windows, you probably can't + save at all while the browser is running). + + Note that the Mozilla/Netscape format will downgrade RFC2965 cookies to + Netscape cookies on saving. + + In particular, the cookie version and port number information is lost, + together with information about whether or not Path, Port and Discard were + specified by the Set-Cookie2 (or Set-Cookie) header, and whether or not the + domain as set in the HTTP header started with a dot (yes, I'm aware some + domains in Netscape files start with a dot and some don't -- trust me, you + really don't want to know any more about this). + + Note that though Mozilla and Netscape use the same format, they use + slightly different headers. The class saves cookies using the Netscape + header by default (Mozilla can cope with that). + + """ + magic_re = re.compile("#( Netscape)? HTTP Cookie File") + header = """\ +# Netscape HTTP Cookie File +# http://curl.haxx.se/rfc/cookie_spec.html +# This is a generated file! Do not edit. + +""" + + def _really_load(self, f, filename, ignore_discard, ignore_expires): + now = time.time() + + magic = f.readline() + if not self.magic_re.search(magic): + raise LoadError( + "%r does not look like a Netscape format cookies file" % + filename) + + try: + while 1: + line = f.readline() + if line == "": break + + # last field may be absent, so keep any trailing tab + if line.endswith("\n"): line = line[:-1] + + # skip comments and blank lines XXX what is $ for? + if (line.strip().startswith(("#", "$")) or + line.strip() == ""): + continue + + domain, domain_specified, path, secure, expires, name, value = \ + line.split("\t") + secure = (secure == "TRUE") + domain_specified = (domain_specified == "TRUE") + if name == "": + # cookies.txt regards 'Set-Cookie: foo' as a cookie + # with no name, whereas http.cookiejar regards it as a + # cookie with no value. + name = value + value = None + + initial_dot = domain.startswith(".") + assert domain_specified == initial_dot + + discard = False + if expires == "": + expires = None + discard = True + + # assume path_specified is false + c = Cookie(0, name, value, + None, False, + domain, domain_specified, initial_dot, + path, False, + secure, + expires, + discard, + None, + None, + {}) + if not ignore_discard and c.discard: + continue + if not ignore_expires and c.is_expired(now): + continue + self.set_cookie(c) + + except OSError: + raise + except Exception: + _warn_unhandled_exception() + raise LoadError("invalid Netscape format cookies file %r: %r" % + (filename, line)) + + def save(self, filename=None, ignore_discard=False, ignore_expires=False): + if filename is None: + if self.filename is not None: filename = self.filename + else: raise ValueError(MISSING_FILENAME_TEXT) + + with open(filename, "w") as f: + f.write(self.header) + now = time.time() + for cookie in self: + if not ignore_discard and cookie.discard: + continue + if not ignore_expires and cookie.is_expired(now): + continue + if cookie.secure: secure = "TRUE" + else: secure = "FALSE" + if cookie.domain.startswith("."): initial_dot = "TRUE" + else: initial_dot = "FALSE" + if cookie.expires is not None: + expires = str(cookie.expires) + else: + expires = "" + if cookie.value is None: + # cookies.txt regards 'Set-Cookie: foo' as a cookie + # with no name, whereas http.cookiejar regards it as a + # cookie with no value. + name = "" + value = cookie.name + else: + name = cookie.name + value = cookie.value + f.write( + "\t".join([cookie.domain, initial_dot, cookie.path, + secure, expires, name, value])+ + "\n") diff --git a/venv/lib/python3.12/site-packages/eventlet/green/http/cookies.py b/venv/lib/python3.12/site-packages/eventlet/green/http/cookies.py new file mode 100644 index 0000000..0a0a150 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/http/cookies.py @@ -0,0 +1,691 @@ +# This is part of Python source code with Eventlet-specific modifications. +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +# 2011, 2012, 2013, 2014, 2015, 2016 Python Software Foundation; All Rights +# Reserved +# +# 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, +# 2011, 2012, 2013, 2014, 2015, 2016 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. +#### +# Copyright 2000 by Timothy O'Malley +# +# 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 +# Timothy O'Malley not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# Timothy O'Malley DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS, IN NO EVENT SHALL Timothy O'Malley 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. +# +#### +# +# Id: Cookie.py,v 2.29 2000/08/23 05:28:49 timo Exp +# by Timothy O'Malley +# +# Cookie.py is a Python module for the handling of HTTP +# cookies as a Python dictionary. See RFC 2109 for more +# information on cookies. +# +# The original idea to treat Cookies as a dictionary came from +# Dave Mitchell (davem@magnet.com) in 1995, when he released the +# first version of nscookie.py. +# +#### + +r""" +Here's a sample session to show how to use this module. +At the moment, this is the only documentation. + +The Basics +---------- + +Importing is easy... + + >>> from http import cookies + +Most of the time you start by creating a cookie. + + >>> C = cookies.SimpleCookie() + +Once you've created your Cookie, you can add values just as if it were +a dictionary. + + >>> C = cookies.SimpleCookie() + >>> C["fig"] = "newton" + >>> C["sugar"] = "wafer" + >>> C.output() + 'Set-Cookie: fig=newton\r\nSet-Cookie: sugar=wafer' + +Notice that the printable representation of a Cookie is the +appropriate format for a Set-Cookie: header. This is the +default behavior. You can change the header and printed +attributes by using the .output() function + + >>> C = cookies.SimpleCookie() + >>> C["rocky"] = "road" + >>> C["rocky"]["path"] = "/cookie" + >>> print(C.output(header="Cookie:")) + Cookie: rocky=road; Path=/cookie + >>> print(C.output(attrs=[], header="Cookie:")) + Cookie: rocky=road + +The load() method of a Cookie extracts cookies from a string. In a +CGI script, you would use this method to extract the cookies from the +HTTP_COOKIE environment variable. + + >>> C = cookies.SimpleCookie() + >>> C.load("chips=ahoy; vienna=finger") + >>> C.output() + 'Set-Cookie: chips=ahoy\r\nSet-Cookie: vienna=finger' + +The load() method is darn-tootin smart about identifying cookies +within a string. Escaped quotation marks, nested semicolons, and other +such trickeries do not confuse it. + + >>> C = cookies.SimpleCookie() + >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";') + >>> print(C) + Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;" + +Each element of the Cookie also supports all of the RFC 2109 +Cookie attributes. Here's an example which sets the Path +attribute. + + >>> C = cookies.SimpleCookie() + >>> C["oreo"] = "doublestuff" + >>> C["oreo"]["path"] = "/" + >>> print(C) + Set-Cookie: oreo=doublestuff; Path=/ + +Each dictionary element has a 'value' attribute, which gives you +back the value associated with the key. + + >>> C = cookies.SimpleCookie() + >>> C["twix"] = "none for you" + >>> C["twix"].value + 'none for you' + +The SimpleCookie expects that all values should be standard strings. +Just to be sure, SimpleCookie invokes the str() builtin to convert +the value to a string, when the values are set dictionary-style. + + >>> C = cookies.SimpleCookie() + >>> C["number"] = 7 + >>> C["string"] = "seven" + >>> C["number"].value + '7' + >>> C["string"].value + 'seven' + >>> C.output() + 'Set-Cookie: number=7\r\nSet-Cookie: string=seven' + +Finis. +""" + +# +# Import our required modules +# +import re +import string + +__all__ = ["CookieError", "BaseCookie", "SimpleCookie"] + +_nulljoin = ''.join +_semispacejoin = '; '.join +_spacejoin = ' '.join + +def _warn_deprecated_setter(setter): + import warnings + msg = ('The .%s setter is deprecated. The attribute will be read-only in ' + 'future releases. Please use the set() method instead.' % setter) + warnings.warn(msg, DeprecationWarning, stacklevel=3) + +# +# Define an exception visible to External modules +# +class CookieError(Exception): + pass + + +# These quoting routines conform to the RFC2109 specification, which in +# turn references the character definitions from RFC2068. They provide +# a two-way quoting algorithm. Any non-text character is translated +# into a 4 character sequence: a forward-slash followed by the +# three-digit octal equivalent of the character. Any '\' or '"' is +# quoted with a preceding '\' slash. +# Because of the way browsers really handle cookies (as opposed to what +# the RFC says) we also encode "," and ";". +# +# These are taken from RFC2068 and RFC2109. +# _LegalChars is the list of chars which don't require "'s +# _Translator hash-table for fast quoting +# +_LegalChars = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~:" +_UnescapedChars = _LegalChars + ' ()/<=>?@[]{}' + +_Translator = {n: '\\%03o' % n + for n in set(range(256)) - set(map(ord, _UnescapedChars))} +_Translator.update({ + ord('"'): '\\"', + ord('\\'): '\\\\', +}) + +# Eventlet change: match used instead of fullmatch for Python 3.3 compatibility +_is_legal_key = re.compile(r'[%s]+\Z' % re.escape(_LegalChars)).match + +def _quote(str): + r"""Quote a string for use in a cookie header. + + If the string does not need to be double-quoted, then just return the + string. Otherwise, surround the string in doublequotes and quote + (with a \) special characters. + """ + if str is None or _is_legal_key(str): + return str + else: + return '"' + str.translate(_Translator) + '"' + + +_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") +_QuotePatt = re.compile(r"[\\].") + +def _unquote(str): + # If there aren't any doublequotes, + # then there can't be any special characters. See RFC 2109. + if str is None or len(str) < 2: + return str + if str[0] != '"' or str[-1] != '"': + return str + + # We have to assume that we must decode this string. + # Down to work. + + # Remove the "s + str = str[1:-1] + + # Check for special sequences. Examples: + # \012 --> \n + # \" --> " + # + i = 0 + n = len(str) + res = [] + while 0 <= i < n: + o_match = _OctalPatt.search(str, i) + q_match = _QuotePatt.search(str, i) + if not o_match and not q_match: # Neither matched + res.append(str[i:]) + break + # else: + j = k = -1 + if o_match: + j = o_match.start(0) + if q_match: + k = q_match.start(0) + if q_match and (not o_match or k < j): # QuotePatt matched + res.append(str[i:k]) + res.append(str[k+1]) + i = k + 2 + else: # OctalPatt matched + res.append(str[i:j]) + res.append(chr(int(str[j+1:j+4], 8))) + i = j + 4 + return _nulljoin(res) + +# The _getdate() routine is used to set the expiration time in the cookie's HTTP +# header. By default, _getdate() returns the current time in the appropriate +# "expires" format for a Set-Cookie header. The one optional argument is an +# offset from now, in seconds. For example, an offset of -3600 means "one hour +# ago". The offset may be a floating point number. +# + +_weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + +_monthname = [None, + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + +def _getdate(future=0, weekdayname=_weekdayname, monthname=_monthname): + from eventlet.green.time import gmtime, time + now = time() + year, month, day, hh, mm, ss, wd, y, z = gmtime(now + future) + return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % \ + (weekdayname[wd], day, monthname[month], year, hh, mm, ss) + + +class Morsel(dict): + """A class to hold ONE (key, value) pair. + + In a cookie, each such pair may have several attributes, so this class is + used to keep the attributes associated with the appropriate key,value pair. + This class also includes a coded_value attribute, which is used to hold + the network representation of the value. This is most useful when Python + objects are pickled for network transit. + """ + # RFC 2109 lists these attributes as reserved: + # path comment domain + # max-age secure version + # + # For historical reasons, these attributes are also reserved: + # expires + # + # This is an extension from Microsoft: + # httponly + # + # This dictionary provides a mapping from the lowercase + # variant on the left to the appropriate traditional + # formatting on the right. + _reserved = { + "expires" : "expires", + "path" : "Path", + "comment" : "Comment", + "domain" : "Domain", + "max-age" : "Max-Age", + "secure" : "Secure", + "httponly" : "HttpOnly", + "version" : "Version", + } + + _flags = {'secure', 'httponly'} + + def __init__(self): + # Set defaults + self._key = self._value = self._coded_value = None + + # Set default attributes + for key in self._reserved: + dict.__setitem__(self, key, "") + + @property + def key(self): + return self._key + + @key.setter + def key(self, key): + _warn_deprecated_setter('key') + self._key = key + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + _warn_deprecated_setter('value') + self._value = value + + @property + def coded_value(self): + return self._coded_value + + @coded_value.setter + def coded_value(self, coded_value): + _warn_deprecated_setter('coded_value') + self._coded_value = coded_value + + def __setitem__(self, K, V): + K = K.lower() + if not K in self._reserved: + raise CookieError("Invalid attribute %r" % (K,)) + dict.__setitem__(self, K, V) + + def setdefault(self, key, val=None): + key = key.lower() + if key not in self._reserved: + raise CookieError("Invalid attribute %r" % (key,)) + return dict.setdefault(self, key, val) + + def __eq__(self, morsel): + if not isinstance(morsel, Morsel): + return NotImplemented + return (dict.__eq__(self, morsel) and + self._value == morsel._value and + self._key == morsel._key and + self._coded_value == morsel._coded_value) + + __ne__ = object.__ne__ + + def copy(self): + morsel = Morsel() + dict.update(morsel, self) + morsel.__dict__.update(self.__dict__) + return morsel + + def update(self, values): + data = {} + for key, val in dict(values).items(): + key = key.lower() + if key not in self._reserved: + raise CookieError("Invalid attribute %r" % (key,)) + data[key] = val + dict.update(self, data) + + def isReservedKey(self, K): + return K.lower() in self._reserved + + def set(self, key, val, coded_val, LegalChars=_LegalChars): + if LegalChars != _LegalChars: + import warnings + warnings.warn( + 'LegalChars parameter is deprecated, ignored and will ' + 'be removed in future versions.', DeprecationWarning, + stacklevel=2) + + if key.lower() in self._reserved: + raise CookieError('Attempt to set a reserved key %r' % (key,)) + if not _is_legal_key(key): + raise CookieError('Illegal key %r' % (key,)) + + # It's a good key, so save it. + self._key = key + self._value = val + self._coded_value = coded_val + + def __getstate__(self): + return { + 'key': self._key, + 'value': self._value, + 'coded_value': self._coded_value, + } + + def __setstate__(self, state): + self._key = state['key'] + self._value = state['value'] + self._coded_value = state['coded_value'] + + def output(self, attrs=None, header="Set-Cookie:"): + return "%s %s" % (header, self.OutputString(attrs)) + + __str__ = output + + def __repr__(self): + return '<%s: %s>' % (self.__class__.__name__, self.OutputString()) + + def js_output(self, attrs=None): + # Print javascript + return """ + + """ % (self.OutputString(attrs).replace('"', r'\"')) + + def OutputString(self, attrs=None): + # Build up our result + # + result = [] + append = result.append + + # First, the key=value pair + append("%s=%s" % (self.key, self.coded_value)) + + # Now add any defined attributes + if attrs is None: + attrs = self._reserved + items = sorted(self.items()) + for key, value in items: + if value == "": + continue + if key not in attrs: + continue + if key == "expires" and isinstance(value, int): + append("%s=%s" % (self._reserved[key], _getdate(value))) + elif key == "max-age" and isinstance(value, int): + append("%s=%d" % (self._reserved[key], value)) + elif key in self._flags: + if value: + append(str(self._reserved[key])) + else: + append("%s=%s" % (self._reserved[key], value)) + + # Return the result + return _semispacejoin(result) + + +# +# Pattern for finding cookie +# +# This used to be strict parsing based on the RFC2109 and RFC2068 +# specifications. I have since discovered that MSIE 3.0x doesn't +# follow the character rules outlined in those specs. As a +# result, the parsing rules here are less strict. +# + +_LegalKeyChars = r"\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=" +_LegalValueChars = _LegalKeyChars + '\[\]' +_CookiePattern = re.compile(r""" + (?x) # This is a verbose pattern + \s* # Optional whitespace at start of cookie + (?P # Start of group 'key' + [""" + _LegalKeyChars + r"""]+? # Any word of at least one letter + ) # End of group 'key' + ( # Optional group: there may not be a value. + \s*=\s* # Equal Sign + (?P # Start of group 'val' + "(?:[^\\"]|\\.)*" # Any doublequoted string + | # or + \w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT # Special case for "expires" attr + | # or + [""" + _LegalValueChars + r"""]* # Any word or empty string + ) # End of group 'val' + )? # End of optional value group + \s* # Any number of spaces. + (\s+|;|$) # Ending either at space, semicolon, or EOS. + """, re.ASCII) # May be removed if safe. + + +# At long last, here is the cookie class. Using this class is almost just like +# using a dictionary. See this module's docstring for example usage. +# +class BaseCookie(dict): + """A container class for a set of Morsels.""" + + def value_decode(self, val): + """real_value, coded_value = value_decode(STRING) + Called prior to setting a cookie's value from the network + representation. The VALUE is the value read from HTTP + header. + Override this function to modify the behavior of cookies. + """ + return val, val + + def value_encode(self, val): + """real_value, coded_value = value_encode(VALUE) + Called prior to setting a cookie's value from the dictionary + representation. The VALUE is the value being assigned. + Override this function to modify the behavior of cookies. + """ + strval = str(val) + return strval, strval + + def __init__(self, input=None): + if input: + self.load(input) + + def __set(self, key, real_value, coded_value): + """Private method for setting a cookie's value""" + M = self.get(key, Morsel()) + M.set(key, real_value, coded_value) + dict.__setitem__(self, key, M) + + def __setitem__(self, key, value): + """Dictionary style assignment.""" + if isinstance(value, Morsel): + # allow assignment of constructed Morsels (e.g. for pickling) + dict.__setitem__(self, key, value) + else: + rval, cval = self.value_encode(value) + self.__set(key, rval, cval) + + def output(self, attrs=None, header="Set-Cookie:", sep="\015\012"): + """Return a string suitable for HTTP.""" + result = [] + items = sorted(self.items()) + for key, value in items: + result.append(value.output(attrs, header)) + return sep.join(result) + + __str__ = output + + def __repr__(self): + l = [] + items = sorted(self.items()) + for key, value in items: + l.append('%s=%s' % (key, repr(value.value))) + return '<%s: %s>' % (self.__class__.__name__, _spacejoin(l)) + + def js_output(self, attrs=None): + """Return a string suitable for JavaScript.""" + result = [] + items = sorted(self.items()) + for key, value in items: + result.append(value.js_output(attrs)) + return _nulljoin(result) + + def load(self, rawdata): + """Load cookies from a string (presumably HTTP_COOKIE) or + from a dictionary. Loading cookies from a dictionary 'd' + is equivalent to calling: + map(Cookie.__setitem__, d.keys(), d.values()) + """ + if isinstance(rawdata, str): + self.__parse_string(rawdata) + else: + # self.update() wouldn't call our custom __setitem__ + for key, value in rawdata.items(): + self[key] = value + return + + def __parse_string(self, str, patt=_CookiePattern): + i = 0 # Our starting point + n = len(str) # Length of string + parsed_items = [] # Parsed (type, key, value) triples + morsel_seen = False # A key=value pair was previously encountered + + TYPE_ATTRIBUTE = 1 + TYPE_KEYVALUE = 2 + + # We first parse the whole cookie string and reject it if it's + # syntactically invalid (this helps avoid some classes of injection + # attacks). + while 0 <= i < n: + # Start looking for a cookie + match = patt.match(str, i) + if not match: + # No more cookies + break + + key, value = match.group("key"), match.group("val") + i = match.end(0) + + if key[0] == "$": + if not morsel_seen: + # We ignore attributes which pertain to the cookie + # mechanism as a whole, such as "$Version". + # See RFC 2965. (Does anyone care?) + continue + parsed_items.append((TYPE_ATTRIBUTE, key[1:], value)) + elif key.lower() in Morsel._reserved: + if not morsel_seen: + # Invalid cookie string + return + if value is None: + if key.lower() in Morsel._flags: + parsed_items.append((TYPE_ATTRIBUTE, key, True)) + else: + # Invalid cookie string + return + else: + parsed_items.append((TYPE_ATTRIBUTE, key, _unquote(value))) + elif value is not None: + parsed_items.append((TYPE_KEYVALUE, key, self.value_decode(value))) + morsel_seen = True + else: + # Invalid cookie string + return + + # The cookie string is valid, apply it. + M = None # current morsel + for tp, key, value in parsed_items: + if tp == TYPE_ATTRIBUTE: + assert M is not None + M[key] = value + else: + assert tp == TYPE_KEYVALUE + rval, cval = value + self.__set(key, rval, cval) + M = self[key] + + +class SimpleCookie(BaseCookie): + """ + SimpleCookie supports strings as cookie values. When setting + the value using the dictionary assignment notation, SimpleCookie + calls the builtin str() to convert the value to a string. Values + received from HTTP are kept as strings. + """ + def value_decode(self, val): + return _unquote(val), val + + def value_encode(self, val): + strval = str(val) + return strval, _quote(strval) diff --git a/venv/lib/python3.12/site-packages/eventlet/green/http/server.py b/venv/lib/python3.12/site-packages/eventlet/green/http/server.py new file mode 100644 index 0000000..190bdb9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/http/server.py @@ -0,0 +1,1266 @@ +# This is part of Python source code with Eventlet-specific modifications. +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +# 2011, 2012, 2013, 2014, 2015, 2016 Python Software Foundation; All Rights +# Reserved +# +# 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, +# 2011, 2012, 2013, 2014, 2015, 2016 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. +"""HTTP server classes. + +Note: BaseHTTPRequestHandler doesn't implement any HTTP request; see +SimpleHTTPRequestHandler for simple implementations of GET, HEAD and POST, +and CGIHTTPRequestHandler for CGI scripts. + +It does, however, optionally implement HTTP/1.1 persistent connections, +as of version 0.3. + +Notes on CGIHTTPRequestHandler +------------------------------ + +This class implements GET and POST requests to cgi-bin scripts. + +If the os.fork() function is not present (e.g. on Windows), +subprocess.Popen() is used as a fallback, with slightly altered semantics. + +In all cases, the implementation is intentionally naive -- all +requests are executed synchronously. + +SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL +-- it may execute arbitrary Python code or external programs. + +Note that status code 200 is sent prior to execution of a CGI script, so +scripts cannot send other status codes such as 302 (redirect). + +XXX To do: + +- log requests even later (to capture byte count) +- log user-agent header and other interesting goodies +- send error log to separate file +""" + + +# See also: +# +# HTTP Working Group T. Berners-Lee +# INTERNET-DRAFT R. T. Fielding +# H. Frystyk Nielsen +# Expires September 8, 1995 March 8, 1995 +# +# URL: http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt +# +# and +# +# Network Working Group R. Fielding +# Request for Comments: 2616 et al +# Obsoletes: 2068 June 1999 +# Category: Standards Track +# +# URL: http://www.faqs.org/rfcs/rfc2616.html + +# Log files +# --------- +# +# Here's a quote from the NCSA httpd docs about log file format. +# +# | The logfile format is as follows. Each line consists of: +# | +# | host rfc931 authuser [DD/Mon/YYYY:hh:mm:ss] "request" ddd bbbb +# | +# | host: Either the DNS name or the IP number of the remote client +# | rfc931: Any information returned by identd for this person, +# | - otherwise. +# | authuser: If user sent a userid for authentication, the user name, +# | - otherwise. +# | DD: Day +# | Mon: Month (calendar name) +# | YYYY: Year +# | hh: hour (24-hour format, the machine's timezone) +# | mm: minutes +# | ss: seconds +# | request: The first line of the HTTP request as sent by the client. +# | ddd: the status code returned by the server, - if not available. +# | bbbb: the total number of bytes sent, +# | *not including the HTTP/1.0 header*, - if not available +# | +# | You can determine the name of the file accessed through request. +# +# (Actually, the latter is only true if you know the server configuration +# at the time the request was made!) + +__version__ = "0.6" + +__all__ = [ + "HTTPServer", "BaseHTTPRequestHandler", + "SimpleHTTPRequestHandler", "CGIHTTPRequestHandler", +] + +import email.utils +import html +import io +import mimetypes +import posixpath +import shutil +import sys +import urllib.parse +import copy +import argparse + +from eventlet.green import ( + os, + time, + select, + socket, + SocketServer as socketserver, + subprocess, +) +from eventlet.green.http import client as http_client, HTTPStatus + + +# Default error message template +DEFAULT_ERROR_MESSAGE = """\ + + + + + Error response + + +

Error response

+

Error code: %(code)d

+

Message: %(message)s.

+

Error code explanation: %(code)s - %(explain)s.

+ + +""" + +DEFAULT_ERROR_CONTENT_TYPE = "text/html;charset=utf-8" + +class HTTPServer(socketserver.TCPServer): + + allow_reuse_address = 1 # Seems to make sense in testing environment + + def server_bind(self): + """Override server_bind to store the server name.""" + socketserver.TCPServer.server_bind(self) + host, port = self.server_address[:2] + self.server_name = socket.getfqdn(host) + self.server_port = port + + +class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): + + """HTTP request handler base class. + + The following explanation of HTTP serves to guide you through the + code as well as to expose any misunderstandings I may have about + HTTP (so you don't need to read the code to figure out I'm wrong + :-). + + HTTP (HyperText Transfer Protocol) is an extensible protocol on + top of a reliable stream transport (e.g. TCP/IP). The protocol + recognizes three parts to a request: + + 1. One line identifying the request type and path + 2. An optional set of RFC-822-style headers + 3. An optional data part + + The headers and data are separated by a blank line. + + The first line of the request has the form + + + + where is a (case-sensitive) keyword such as GET or POST, + is a string containing path information for the request, + and should be the string "HTTP/1.0" or "HTTP/1.1". + is encoded using the URL encoding scheme (using %xx to signify + the ASCII character with hex code xx). + + The specification specifies that lines are separated by CRLF but + for compatibility with the widest range of clients recommends + servers also handle LF. Similarly, whitespace in the request line + is treated sensibly (allowing multiple spaces between components + and allowing trailing whitespace). + + Similarly, for output, lines ought to be separated by CRLF pairs + but most clients grok LF characters just fine. + + If the first line of the request has the form + + + + (i.e. is left out) then this is assumed to be an HTTP + 0.9 request; this form has no optional headers and data part and + the reply consists of just the data. + + The reply form of the HTTP 1.x protocol again has three parts: + + 1. One line giving the response code + 2. An optional set of RFC-822-style headers + 3. The data + + Again, the headers and data are separated by a blank line. + + The response code line has the form + + + + where is the protocol version ("HTTP/1.0" or "HTTP/1.1"), + is a 3-digit response code indicating success or + failure of the request, and is an optional + human-readable string explaining what the response code means. + + This server parses the request and the headers, and then calls a + function specific to the request type (). Specifically, + a request SPAM will be handled by a method do_SPAM(). If no + such method exists the server sends an error response to the + client. If it exists, it is called with no arguments: + + do_SPAM() + + Note that the request name is case sensitive (i.e. SPAM and spam + are different requests). + + The various request details are stored in instance variables: + + - client_address is the client IP address in the form (host, + port); + + - command, path and version are the broken-down request line; + + - headers is an instance of email.message.Message (or a derived + class) containing the header information; + + - rfile is a file object open for reading positioned at the + start of the optional input data part; + + - wfile is a file object open for writing. + + IT IS IMPORTANT TO ADHERE TO THE PROTOCOL FOR WRITING! + + The first thing to be written must be the response line. Then + follow 0 or more header lines, then a blank line, and then the + actual data (if any). The meaning of the header lines depends on + the command executed by the server; in most cases, when data is + returned, there should be at least one header line of the form + + Content-type: / + + where and should be registered MIME types, + e.g. "text/html" or "text/plain". + + """ + + # The Python system version, truncated to its first component. + sys_version = "Python/" + sys.version.split()[0] + + # The server software version. You may want to override this. + # The format is multiple whitespace-separated strings, + # where each string is of the form name[/version]. + server_version = "BaseHTTP/" + __version__ + + error_message_format = DEFAULT_ERROR_MESSAGE + error_content_type = DEFAULT_ERROR_CONTENT_TYPE + + # The default request version. This only affects responses up until + # the point where the request line is parsed, so it mainly decides what + # the client gets back when sending a malformed request line. + # Most web servers default to HTTP 0.9, i.e. don't send a status line. + default_request_version = "HTTP/0.9" + + def parse_request(self): + """Parse a request (internal). + + The request should be stored in self.raw_requestline; the results + are in self.command, self.path, self.request_version and + self.headers. + + Return True for success, False for failure; on failure, an + error is sent back. + + """ + self.command = None # set in case of error on the first line + self.request_version = version = self.default_request_version + self.close_connection = True + requestline = str(self.raw_requestline, 'iso-8859-1') + requestline = requestline.rstrip('\r\n') + self.requestline = requestline + words = requestline.split() + if len(words) == 3: + command, path, version = words + try: + if version[:5] != 'HTTP/': + raise ValueError + base_version_number = version.split('/', 1)[1] + version_number = base_version_number.split(".") + # RFC 2145 section 3.1 says there can be only one "." and + # - major and minor numbers MUST be treated as + # separate integers; + # - HTTP/2.4 is a lower version than HTTP/2.13, which in + # turn is lower than HTTP/12.3; + # - Leading zeros MUST be ignored by recipients. + if len(version_number) != 2: + raise ValueError + version_number = int(version_number[0]), int(version_number[1]) + except (ValueError, IndexError): + self.send_error( + HTTPStatus.BAD_REQUEST, + "Bad request version (%r)" % version) + return False + if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1": + self.close_connection = False + if version_number >= (2, 0): + self.send_error( + HTTPStatus.HTTP_VERSION_NOT_SUPPORTED, + "Invalid HTTP version (%s)" % base_version_number) + return False + elif len(words) == 2: + command, path = words + self.close_connection = True + if command != 'GET': + self.send_error( + HTTPStatus.BAD_REQUEST, + "Bad HTTP/0.9 request type (%r)" % command) + return False + elif not words: + return False + else: + self.send_error( + HTTPStatus.BAD_REQUEST, + "Bad request syntax (%r)" % requestline) + return False + self.command, self.path, self.request_version = command, path, version + + # Examine the headers and look for a Connection directive. + try: + self.headers = http_client.parse_headers(self.rfile, + _class=self.MessageClass) + except http_client.LineTooLong as err: + self.send_error( + HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE, + "Line too long", + str(err)) + return False + except http_client.HTTPException as err: + self.send_error( + HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE, + "Too many headers", + str(err) + ) + return False + + conntype = self.headers.get('Connection', "") + if conntype.lower() == 'close': + self.close_connection = True + elif (conntype.lower() == 'keep-alive' and + self.protocol_version >= "HTTP/1.1"): + self.close_connection = False + # Examine the headers and look for an Expect directive + expect = self.headers.get('Expect', "") + if (expect.lower() == "100-continue" and + self.protocol_version >= "HTTP/1.1" and + self.request_version >= "HTTP/1.1"): + if not self.handle_expect_100(): + return False + return True + + def handle_expect_100(self): + """Decide what to do with an "Expect: 100-continue" header. + + If the client is expecting a 100 Continue response, we must + respond with either a 100 Continue or a final response before + waiting for the request body. The default is to always respond + with a 100 Continue. You can behave differently (for example, + reject unauthorized requests) by overriding this method. + + This method should either return True (possibly after sending + a 100 Continue response) or send an error response and return + False. + + """ + self.send_response_only(HTTPStatus.CONTINUE) + self.end_headers() + return True + + def handle_one_request(self): + """Handle a single HTTP request. + + You normally don't need to override this method; see the class + __doc__ string for information on how to handle specific HTTP + commands such as GET and POST. + + """ + try: + self.raw_requestline = self.rfile.readline(65537) + if len(self.raw_requestline) > 65536: + self.requestline = '' + self.request_version = '' + self.command = '' + self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG) + return + if not self.raw_requestline: + self.close_connection = True + return + if not self.parse_request(): + # An error code has been sent, just exit + return + mname = 'do_' + self.command + if not hasattr(self, mname): + self.send_error( + HTTPStatus.NOT_IMPLEMENTED, + "Unsupported method (%r)" % self.command) + return + method = getattr(self, mname) + method() + self.wfile.flush() #actually send the response if not already done. + except socket.timeout as e: + #a read or a write timed out. Discard this connection + self.log_error("Request timed out: %r", e) + self.close_connection = True + return + + def handle(self): + """Handle multiple requests if necessary.""" + self.close_connection = True + + self.handle_one_request() + while not self.close_connection: + self.handle_one_request() + + def send_error(self, code, message=None, explain=None): + """Send and log an error reply. + + Arguments are + * code: an HTTP error code + 3 digits + * message: a simple optional 1 line reason phrase. + *( HTAB / SP / VCHAR / %x80-FF ) + defaults to short entry matching the response code + * explain: a detailed message defaults to the long entry + matching the response code. + + This sends an error response (so it must be called before any + output has been generated), logs the error, and finally sends + a piece of HTML explaining the error to the user. + + """ + + try: + shortmsg, longmsg = self.responses[code] + except KeyError: + shortmsg, longmsg = '???', '???' + if message is None: + message = shortmsg + if explain is None: + explain = longmsg + self.log_error("code %d, message %s", code, message) + self.send_response(code, message) + self.send_header('Connection', 'close') + + # Message body is omitted for cases described in: + # - RFC7230: 3.3. 1xx, 204(No Content), 304(Not Modified) + # - RFC7231: 6.3.6. 205(Reset Content) + body = None + if (code >= 200 and + code not in (HTTPStatus.NO_CONTENT, + HTTPStatus.RESET_CONTENT, + HTTPStatus.NOT_MODIFIED)): + # HTML encode to prevent Cross Site Scripting attacks + # (see bug #1100201) + content = (self.error_message_format % { + 'code': code, + 'message': html.escape(message, quote=False), + 'explain': html.escape(explain, quote=False) + }) + body = content.encode('UTF-8', 'replace') + self.send_header("Content-Type", self.error_content_type) + self.send_header('Content-Length', int(len(body))) + self.end_headers() + + if self.command != 'HEAD' and body: + self.wfile.write(body) + + def send_response(self, code, message=None): + """Add the response header to the headers buffer and log the + response code. + + Also send two standard headers with the server software + version and the current date. + + """ + self.log_request(code) + self.send_response_only(code, message) + self.send_header('Server', self.version_string()) + self.send_header('Date', self.date_time_string()) + + def send_response_only(self, code, message=None): + """Send the response header only.""" + if self.request_version != 'HTTP/0.9': + if message is None: + if code in self.responses: + message = self.responses[code][0] + else: + message = '' + if not hasattr(self, '_headers_buffer'): + self._headers_buffer = [] + self._headers_buffer.append(("%s %d %s\r\n" % + (self.protocol_version, code, message)).encode( + 'latin-1', 'strict')) + + def send_header(self, keyword, value): + """Send a MIME header to the headers buffer.""" + if self.request_version != 'HTTP/0.9': + if not hasattr(self, '_headers_buffer'): + self._headers_buffer = [] + self._headers_buffer.append( + ("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict')) + + if keyword.lower() == 'connection': + if value.lower() == 'close': + self.close_connection = True + elif value.lower() == 'keep-alive': + self.close_connection = False + + def end_headers(self): + """Send the blank line ending the MIME headers.""" + if self.request_version != 'HTTP/0.9': + self._headers_buffer.append(b"\r\n") + self.flush_headers() + + def flush_headers(self): + if hasattr(self, '_headers_buffer'): + self.wfile.write(b"".join(self._headers_buffer)) + self._headers_buffer = [] + + def log_request(self, code='-', size='-'): + """Log an accepted request. + + This is called by send_response(). + + """ + if isinstance(code, HTTPStatus): + code = code.value + self.log_message('"%s" %s %s', + self.requestline, str(code), str(size)) + + def log_error(self, format, *args): + """Log an error. + + This is called when a request cannot be fulfilled. By + default it passes the message on to log_message(). + + Arguments are the same as for log_message(). + + XXX This should go to the separate error log. + + """ + + self.log_message(format, *args) + + def log_message(self, format, *args): + """Log an arbitrary message. + + This is used by all other logging functions. Override + it if you have specific logging wishes. + + The first argument, FORMAT, is a format string for the + message to be logged. If the format string contains + any % escapes requiring parameters, they should be + specified as subsequent arguments (it's just like + printf!). + + The client ip and current date/time are prefixed to + every message. + + """ + + sys.stderr.write("%s - - [%s] %s\n" % + (self.address_string(), + self.log_date_time_string(), + format%args)) + + def version_string(self): + """Return the server software version string.""" + return self.server_version + ' ' + self.sys_version + + def date_time_string(self, timestamp=None): + """Return the current date and time formatted for a message header.""" + if timestamp is None: + timestamp = time.time() + return email.utils.formatdate(timestamp, usegmt=True) + + def log_date_time_string(self): + """Return the current time formatted for logging.""" + now = time.time() + year, month, day, hh, mm, ss, x, y, z = time.localtime(now) + s = "%02d/%3s/%04d %02d:%02d:%02d" % ( + day, self.monthname[month], year, hh, mm, ss) + return s + + weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + + monthname = [None, + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + + def address_string(self): + """Return the client address.""" + + return self.client_address[0] + + # Essentially static class variables + + # The version of the HTTP protocol we support. + # Set this to HTTP/1.1 to enable automatic keepalive + protocol_version = "HTTP/1.0" + + # MessageClass used to parse headers + MessageClass = http_client.HTTPMessage + + # hack to maintain backwards compatibility + responses = { + v: (v.phrase, v.description) + for v in HTTPStatus.__members__.values() + } + + +class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): + + """Simple HTTP request handler with GET and HEAD commands. + + This serves files from the current directory and any of its + subdirectories. The MIME type for files is determined by + calling the .guess_type() method. + + The GET and HEAD requests are identical except that the HEAD + request omits the actual contents of the file. + + """ + + server_version = "SimpleHTTP/" + __version__ + + def do_GET(self): + """Serve a GET request.""" + f = self.send_head() + if f: + try: + self.copyfile(f, self.wfile) + finally: + f.close() + + def do_HEAD(self): + """Serve a HEAD request.""" + f = self.send_head() + if f: + f.close() + + def send_head(self): + """Common code for GET and HEAD commands. + + This sends the response code and MIME headers. + + Return value is either a file object (which has to be copied + to the outputfile by the caller unless the command was HEAD, + and must be closed by the caller under all circumstances), or + None, in which case the caller has nothing further to do. + + """ + path = self.translate_path(self.path) + f = None + if os.path.isdir(path): + parts = urllib.parse.urlsplit(self.path) + if not parts.path.endswith('/'): + # redirect browser - doing basically what apache does + self.send_response(HTTPStatus.MOVED_PERMANENTLY) + new_parts = (parts[0], parts[1], parts[2] + '/', + parts[3], parts[4]) + new_url = urllib.parse.urlunsplit(new_parts) + self.send_header("Location", new_url) + self.end_headers() + return None + for index in "index.html", "index.htm": + index = os.path.join(path, index) + if os.path.exists(index): + path = index + break + else: + return self.list_directory(path) + ctype = self.guess_type(path) + try: + f = open(path, 'rb') + except OSError: + self.send_error(HTTPStatus.NOT_FOUND, "File not found") + return None + try: + self.send_response(HTTPStatus.OK) + self.send_header("Content-type", ctype) + fs = os.fstat(f.fileno()) + self.send_header("Content-Length", str(fs[6])) + self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) + self.end_headers() + return f + except: + f.close() + raise + + def list_directory(self, path): + """Helper to produce a directory listing (absent index.html). + + Return value is either a file object, or None (indicating an + error). In either case, the headers are sent, making the + interface the same as for send_head(). + + """ + try: + list = os.listdir(path) + except OSError: + self.send_error( + HTTPStatus.NOT_FOUND, + "No permission to list directory") + return None + list.sort(key=lambda a: a.lower()) + r = [] + try: + displaypath = urllib.parse.unquote(self.path, + errors='surrogatepass') + except UnicodeDecodeError: + displaypath = urllib.parse.unquote(path) + displaypath = html.escape(displaypath, quote=False) + enc = sys.getfilesystemencoding() + title = 'Directory listing for %s' % displaypath + r.append('') + r.append('\n') + r.append('' % enc) + r.append('%s\n' % title) + r.append('\n

%s

' % title) + r.append('
\n
\n
\n\n\n') + encoded = '\n'.join(r).encode(enc, 'surrogateescape') + f = io.BytesIO() + f.write(encoded) + f.seek(0) + self.send_response(HTTPStatus.OK) + self.send_header("Content-type", "text/html; charset=%s" % enc) + self.send_header("Content-Length", str(len(encoded))) + self.end_headers() + return f + + def translate_path(self, path): + """Translate a /-separated PATH to the local filename syntax. + + Components that mean special things to the local file system + (e.g. drive or directory names) are ignored. (XXX They should + probably be diagnosed.) + + """ + # abandon query parameters + path = path.split('?',1)[0] + path = path.split('#',1)[0] + # Don't forget explicit trailing slash when normalizing. Issue17324 + trailing_slash = path.rstrip().endswith('/') + try: + path = urllib.parse.unquote(path, errors='surrogatepass') + except UnicodeDecodeError: + path = urllib.parse.unquote(path) + path = posixpath.normpath(path) + words = path.split('/') + words = filter(None, words) + path = os.getcwd() + for word in words: + if os.path.dirname(word) or word in (os.curdir, os.pardir): + # Ignore components that are not a simple file/directory name + continue + path = os.path.join(path, word) + if trailing_slash: + path += '/' + return path + + def copyfile(self, source, outputfile): + """Copy all data between two file objects. + + The SOURCE argument is a file object open for reading + (or anything with a read() method) and the DESTINATION + argument is a file object open for writing (or + anything with a write() method). + + The only reason for overriding this would be to change + the block size or perhaps to replace newlines by CRLF + -- note however that this the default server uses this + to copy binary data as well. + + """ + shutil.copyfileobj(source, outputfile) + + def guess_type(self, path): + """Guess the type of a file. + + Argument is a PATH (a filename). + + Return value is a string of the form type/subtype, + usable for a MIME Content-type header. + + The default implementation looks the file's extension + up in the table self.extensions_map, using application/octet-stream + as a default; however it would be permissible (if + slow) to look inside the data to make a better guess. + + """ + + base, ext = posixpath.splitext(path) + if ext in self.extensions_map: + return self.extensions_map[ext] + ext = ext.lower() + if ext in self.extensions_map: + return self.extensions_map[ext] + else: + return self.extensions_map[''] + + if not mimetypes.inited: + mimetypes.init() # try to read system mime.types + extensions_map = mimetypes.types_map.copy() + extensions_map.update({ + '': 'application/octet-stream', # Default + '.py': 'text/plain', + '.c': 'text/plain', + '.h': 'text/plain', + }) + + +# Utilities for CGIHTTPRequestHandler + +def _url_collapse_path(path): + """ + Given a URL path, remove extra '/'s and '.' path elements and collapse + any '..' references and returns a collapsed path. + + Implements something akin to RFC-2396 5.2 step 6 to parse relative paths. + The utility of this function is limited to is_cgi method and helps + preventing some security attacks. + + Returns: The reconstituted URL, which will always start with a '/'. + + Raises: IndexError if too many '..' occur within the path. + + """ + # Query component should not be involved. + path, _, query = path.partition('?') + path = urllib.parse.unquote(path) + + # Similar to os.path.split(os.path.normpath(path)) but specific to URL + # path semantics rather than local operating system semantics. + path_parts = path.split('/') + head_parts = [] + for part in path_parts[:-1]: + if part == '..': + head_parts.pop() # IndexError if more '..' than prior parts + elif part and part != '.': + head_parts.append( part ) + if path_parts: + tail_part = path_parts.pop() + if tail_part: + if tail_part == '..': + head_parts.pop() + tail_part = '' + elif tail_part == '.': + tail_part = '' + else: + tail_part = '' + + if query: + tail_part = '?'.join((tail_part, query)) + + splitpath = ('/' + '/'.join(head_parts), tail_part) + collapsed_path = "/".join(splitpath) + + return collapsed_path + + + +nobody = None + +def nobody_uid(): + """Internal routine to get nobody's uid""" + global nobody + if nobody: + return nobody + try: + import pwd + except ImportError: + return -1 + try: + nobody = pwd.getpwnam('nobody')[2] + except KeyError: + nobody = 1 + max(x[2] for x in pwd.getpwall()) + return nobody + + +def executable(path): + """Test for executable file.""" + return os.access(path, os.X_OK) + + +class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): + + """Complete HTTP server with GET, HEAD and POST commands. + + GET and HEAD also support running CGI scripts. + + The POST command is *only* implemented for CGI scripts. + + """ + + # Determine platform specifics + have_fork = hasattr(os, 'fork') + + # Make rfile unbuffered -- we need to read one line and then pass + # the rest to a subprocess, so we can't use buffered input. + rbufsize = 0 + + def do_POST(self): + """Serve a POST request. + + This is only implemented for CGI scripts. + + """ + + if self.is_cgi(): + self.run_cgi() + else: + self.send_error( + HTTPStatus.NOT_IMPLEMENTED, + "Can only POST to CGI scripts") + + def send_head(self): + """Version of send_head that support CGI scripts""" + if self.is_cgi(): + return self.run_cgi() + else: + return SimpleHTTPRequestHandler.send_head(self) + + def is_cgi(self): + """Test whether self.path corresponds to a CGI script. + + Returns True and updates the cgi_info attribute to the tuple + (dir, rest) if self.path requires running a CGI script. + Returns False otherwise. + + If any exception is raised, the caller should assume that + self.path was rejected as invalid and act accordingly. + + The default implementation tests whether the normalized url + path begins with one of the strings in self.cgi_directories + (and the next character is a '/' or the end of the string). + + """ + collapsed_path = _url_collapse_path(self.path) + dir_sep = collapsed_path.find('/', 1) + head, tail = collapsed_path[:dir_sep], collapsed_path[dir_sep+1:] + if head in self.cgi_directories: + self.cgi_info = head, tail + return True + return False + + + cgi_directories = ['/cgi-bin', '/htbin'] + + def is_executable(self, path): + """Test whether argument path is an executable file.""" + return executable(path) + + def is_python(self, path): + """Test whether argument path is a Python script.""" + head, tail = os.path.splitext(path) + return tail.lower() in (".py", ".pyw") + + def run_cgi(self): + """Execute a CGI script.""" + dir, rest = self.cgi_info + path = dir + '/' + rest + i = path.find('/', len(dir)+1) + while i >= 0: + nextdir = path[:i] + nextrest = path[i+1:] + + scriptdir = self.translate_path(nextdir) + if os.path.isdir(scriptdir): + dir, rest = nextdir, nextrest + i = path.find('/', len(dir)+1) + else: + break + + # find an explicit query string, if present. + rest, _, query = rest.partition('?') + + # dissect the part after the directory name into a script name & + # a possible additional path, to be stored in PATH_INFO. + i = rest.find('/') + if i >= 0: + script, rest = rest[:i], rest[i:] + else: + script, rest = rest, '' + + scriptname = dir + '/' + script + scriptfile = self.translate_path(scriptname) + if not os.path.exists(scriptfile): + self.send_error( + HTTPStatus.NOT_FOUND, + "No such CGI script (%r)" % scriptname) + return + if not os.path.isfile(scriptfile): + self.send_error( + HTTPStatus.FORBIDDEN, + "CGI script is not a plain file (%r)" % scriptname) + return + ispy = self.is_python(scriptname) + if self.have_fork or not ispy: + if not self.is_executable(scriptfile): + self.send_error( + HTTPStatus.FORBIDDEN, + "CGI script is not executable (%r)" % scriptname) + return + + # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html + # XXX Much of the following could be prepared ahead of time! + env = copy.deepcopy(os.environ) + env['SERVER_SOFTWARE'] = self.version_string() + env['SERVER_NAME'] = self.server.server_name + env['GATEWAY_INTERFACE'] = 'CGI/1.1' + env['SERVER_PROTOCOL'] = self.protocol_version + env['SERVER_PORT'] = str(self.server.server_port) + env['REQUEST_METHOD'] = self.command + uqrest = urllib.parse.unquote(rest) + env['PATH_INFO'] = uqrest + env['PATH_TRANSLATED'] = self.translate_path(uqrest) + env['SCRIPT_NAME'] = scriptname + if query: + env['QUERY_STRING'] = query + env['REMOTE_ADDR'] = self.client_address[0] + authorization = self.headers.get("authorization") + if authorization: + authorization = authorization.split() + if len(authorization) == 2: + import base64, binascii + env['AUTH_TYPE'] = authorization[0] + if authorization[0].lower() == "basic": + try: + authorization = authorization[1].encode('ascii') + authorization = base64.decodebytes(authorization).\ + decode('ascii') + except (binascii.Error, UnicodeError): + pass + else: + authorization = authorization.split(':') + if len(authorization) == 2: + env['REMOTE_USER'] = authorization[0] + # XXX REMOTE_IDENT + if self.headers.get('content-type') is None: + env['CONTENT_TYPE'] = self.headers.get_content_type() + else: + env['CONTENT_TYPE'] = self.headers['content-type'] + length = self.headers.get('content-length') + if length: + env['CONTENT_LENGTH'] = length + referer = self.headers.get('referer') + if referer: + env['HTTP_REFERER'] = referer + accept = [] + for line in self.headers.getallmatchingheaders('accept'): + if line[:1] in "\t\n\r ": + accept.append(line.strip()) + else: + accept = accept + line[7:].split(',') + env['HTTP_ACCEPT'] = ','.join(accept) + ua = self.headers.get('user-agent') + if ua: + env['HTTP_USER_AGENT'] = ua + co = filter(None, self.headers.get_all('cookie', [])) + cookie_str = ', '.join(co) + if cookie_str: + env['HTTP_COOKIE'] = cookie_str + # XXX Other HTTP_* headers + # Since we're setting the env in the parent, provide empty + # values to override previously set values + for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH', + 'HTTP_USER_AGENT', 'HTTP_COOKIE', 'HTTP_REFERER'): + env.setdefault(k, "") + + self.send_response(HTTPStatus.OK, "Script output follows") + self.flush_headers() + + decoded_query = query.replace('+', ' ') + + if self.have_fork: + # Unix -- fork as we should + args = [script] + if '=' not in decoded_query: + args.append(decoded_query) + nobody = nobody_uid() + self.wfile.flush() # Always flush before forking + pid = os.fork() + if pid != 0: + # Parent + pid, sts = os.waitpid(pid, 0) + # throw away additional data [see bug #427345] + while select.select([self.rfile], [], [], 0)[0]: + if not self.rfile.read(1): + break + if sts: + self.log_error("CGI script exit status %#x", sts) + return + # Child + try: + try: + os.setuid(nobody) + except OSError: + pass + os.dup2(self.rfile.fileno(), 0) + os.dup2(self.wfile.fileno(), 1) + os.execve(scriptfile, args, env) + except: + self.server.handle_error(self.request, self.client_address) + os._exit(127) + + else: + # Non-Unix -- use subprocess + cmdline = [scriptfile] + if self.is_python(scriptfile): + interp = sys.executable + if interp.lower().endswith("w.exe"): + # On Windows, use python.exe, not pythonw.exe + interp = interp[:-5] + interp[-4:] + cmdline = [interp, '-u'] + cmdline + if '=' not in query: + cmdline.append(query) + self.log_message("command: %s", subprocess.list2cmdline(cmdline)) + try: + nbytes = int(length) + except (TypeError, ValueError): + nbytes = 0 + p = subprocess.Popen(cmdline, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env = env + ) + if self.command.lower() == "post" and nbytes > 0: + data = self.rfile.read(nbytes) + else: + data = None + # throw away additional data [see bug #427345] + while select.select([self.rfile._sock], [], [], 0)[0]: + if not self.rfile._sock.recv(1): + break + stdout, stderr = p.communicate(data) + self.wfile.write(stdout) + if stderr: + self.log_error('%s', stderr) + p.stderr.close() + p.stdout.close() + status = p.returncode + if status: + self.log_error("CGI script exit status %#x", status) + else: + self.log_message("CGI script exited OK") + + +def test(HandlerClass=BaseHTTPRequestHandler, + ServerClass=HTTPServer, protocol="HTTP/1.0", port=8000, bind=""): + """Test the HTTP request handler class. + + This runs an HTTP server on port 8000 (or the port argument). + + """ + server_address = (bind, port) + + HandlerClass.protocol_version = protocol + with ServerClass(server_address, HandlerClass) as httpd: + sa = httpd.socket.getsockname() + serve_message = "Serving HTTP on {host} port {port} (http://{host}:{port}/) ..." + print(serve_message.format(host=sa[0], port=sa[1])) + try: + httpd.serve_forever() + except KeyboardInterrupt: + print("\nKeyboard interrupt received, exiting.") + sys.exit(0) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--cgi', action='store_true', + help='Run as CGI Server') + parser.add_argument('--bind', '-b', default='', metavar='ADDRESS', + help='Specify alternate bind address ' + '[default: all interfaces]') + parser.add_argument('port', action='store', + default=8000, type=int, + nargs='?', + help='Specify alternate port [default: 8000]') + args = parser.parse_args() + if args.cgi: + handler_class = CGIHTTPRequestHandler + else: + handler_class = SimpleHTTPRequestHandler + test(HandlerClass=handler_class, port=args.port, bind=args.bind) diff --git a/venv/lib/python3.12/site-packages/eventlet/green/httplib.py b/venv/lib/python3.12/site-packages/eventlet/green/httplib.py new file mode 100644 index 0000000..6330efa --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/httplib.py @@ -0,0 +1,22 @@ +from eventlet import patcher +from eventlet.green import socket +import six + +to_patch = [('socket', socket)] + +try: + from eventlet.green import ssl + to_patch.append(('ssl', ssl)) +except ImportError: + pass + +if six.PY2: + patcher.inject('httplib', globals(), *to_patch) +if six.PY3: + from eventlet.green.http import client + for name in dir(client): + if name not in patcher.__exclude: + globals()[name] = getattr(client, name) + +if __name__ == '__main__': + test() diff --git a/venv/lib/python3.12/site-packages/eventlet/green/os.py b/venv/lib/python3.12/site-packages/eventlet/green/os.py new file mode 100644 index 0000000..e13a51e --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/os.py @@ -0,0 +1,120 @@ +os_orig = __import__("os") +import errno +socket = __import__("socket") + +from eventlet import greenio +from eventlet.support import get_errno +from eventlet import greenthread +from eventlet import hubs +from eventlet.patcher import slurp_properties + +__all__ = os_orig.__all__ +__patched__ = ['fdopen', 'read', 'write', 'wait', 'waitpid', 'open'] + +slurp_properties( + os_orig, + globals(), + ignore=__patched__, + srckeys=dir(os_orig)) + + +def fdopen(fd, *args, **kw): + """fdopen(fd [, mode='r' [, bufsize]]) -> file_object + + Return an open file object connected to a file descriptor.""" + if not isinstance(fd, int): + raise TypeError('fd should be int, not %r' % fd) + try: + return greenio.GreenPipe(fd, *args, **kw) + except IOError as e: + raise OSError(*e.args) + +__original_read__ = os_orig.read + + +def read(fd, n): + """read(fd, buffersize) -> string + + Read a file descriptor.""" + while True: + try: + return __original_read__(fd, n) + except (OSError, IOError) as e: + if get_errno(e) != errno.EAGAIN: + raise + except socket.error as e: + if get_errno(e) == errno.EPIPE: + return '' + raise + try: + hubs.trampoline(fd, read=True) + except hubs.IOClosed: + return '' + +__original_write__ = os_orig.write + + +def write(fd, st): + """write(fd, string) -> byteswritten + + Write a string to a file descriptor. + """ + while True: + try: + return __original_write__(fd, st) + except (OSError, IOError) as e: + if get_errno(e) != errno.EAGAIN: + raise + except socket.error as e: + if get_errno(e) != errno.EPIPE: + raise + hubs.trampoline(fd, write=True) + + +def wait(): + """wait() -> (pid, status) + + Wait for completion of a child process.""" + return waitpid(0, 0) + +__original_waitpid__ = os_orig.waitpid + + +def waitpid(pid, options): + """waitpid(...) + waitpid(pid, options) -> (pid, status) + + Wait for completion of a given child process.""" + if options & os_orig.WNOHANG != 0: + return __original_waitpid__(pid, options) + else: + new_options = options | os_orig.WNOHANG + while True: + rpid, status = __original_waitpid__(pid, new_options) + if rpid and status >= 0: + return rpid, status + greenthread.sleep(0.01) + +__original_open__ = os_orig.open + + +def open(file, flags, mode=0o777, dir_fd=None): + """ Wrap os.open + This behaves identically, but collaborates with + the hub's notify_opened protocol. + """ + # pathlib workaround #534 pathlib._NormalAccessor wraps `open` in + # `staticmethod` for py < 3.7 but not 3.7. That means we get here with + # `file` being a pathlib._NormalAccessor object, and the other arguments + # shifted. Fortunately pathlib doesn't use the `dir_fd` argument, so we + # have space in the parameter list. We use some heuristics to detect this + # and adjust the parameters (without importing pathlib) + if type(file).__name__ == '_NormalAccessor': + file, flags, mode, dir_fd = flags, mode, dir_fd, None + + if dir_fd is not None: + fd = __original_open__(file, flags, mode, dir_fd=dir_fd) + else: + fd = __original_open__(file, flags, mode) + hubs.notify_opened(fd) + return fd diff --git a/venv/lib/python3.12/site-packages/eventlet/green/profile.py b/venv/lib/python3.12/site-packages/eventlet/green/profile.py new file mode 100644 index 0000000..9dfd750 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/profile.py @@ -0,0 +1,257 @@ +# Copyright (c) 2010, CCP Games +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of CCP Games 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 CCP GAMES ``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 CCP GAMES 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. + +"""This module is API-equivalent to the standard library :mod:`profile` module +lbut it is greenthread-aware as well as thread-aware. Use this module +to profile Eventlet-based applications in preference to either :mod:`profile` or :mod:`cProfile`. +FIXME: No testcases for this module. +""" + +profile_orig = __import__('profile') +__all__ = profile_orig.__all__ + +from eventlet.patcher import slurp_properties +slurp_properties(profile_orig, globals(), srckeys=dir(profile_orig)) + +import sys +import functools + +from eventlet import greenthread +from eventlet import patcher +import six + +thread = patcher.original(six.moves._thread.__name__) # non-monkeypatched module needed + + +# This class provides the start() and stop() functions +class Profile(profile_orig.Profile): + base = profile_orig.Profile + + def __init__(self, timer=None, bias=None): + self.current_tasklet = greenthread.getcurrent() + self.thread_id = thread.get_ident() + self.base.__init__(self, timer, bias) + self.sleeping = {} + + def __call__(self, *args): + """make callable, allowing an instance to be the profiler""" + self.dispatcher(*args) + + def _setup(self): + self._has_setup = True + self.cur = None + self.timings = {} + self.current_tasklet = greenthread.getcurrent() + self.thread_id = thread.get_ident() + self.simulate_call("profiler") + + def start(self, name="start"): + if getattr(self, "running", False): + return + self._setup() + self.simulate_call("start") + self.running = True + sys.setprofile(self.dispatcher) + + def stop(self): + sys.setprofile(None) + self.running = False + self.TallyTimings() + + # special cases for the original run commands, makin sure to + # clear the timer context. + def runctx(self, cmd, globals, locals): + if not getattr(self, "_has_setup", False): + self._setup() + try: + return profile_orig.Profile.runctx(self, cmd, globals, locals) + finally: + self.TallyTimings() + + def runcall(self, func, *args, **kw): + if not getattr(self, "_has_setup", False): + self._setup() + try: + return profile_orig.Profile.runcall(self, func, *args, **kw) + finally: + self.TallyTimings() + + def trace_dispatch_return_extend_back(self, frame, t): + """A hack function to override error checking in parent class. It + allows invalid returns (where frames weren't preveiously entered into + the profiler) which can happen for all the tasklets that suddenly start + to get monitored. This means that the time will eventually be attributed + to a call high in the chain, when there is a tasklet switch + """ + if isinstance(self.cur[-2], Profile.fake_frame): + return False + self.trace_dispatch_call(frame, 0) + return self.trace_dispatch_return(frame, t) + + def trace_dispatch_c_return_extend_back(self, frame, t): + # same for c return + if isinstance(self.cur[-2], Profile.fake_frame): + return False # ignore bogus returns + self.trace_dispatch_c_call(frame, 0) + return self.trace_dispatch_return(frame, t) + + def SwitchTasklet(self, t0, t1, t): + # tally the time spent in the old tasklet + pt, it, et, fn, frame, rcur = self.cur + cur = (pt, it + t, et, fn, frame, rcur) + + # we are switching to a new tasklet, store the old + self.sleeping[t0] = cur, self.timings + self.current_tasklet = t1 + + # find the new one + try: + self.cur, self.timings = self.sleeping.pop(t1) + except KeyError: + self.cur, self.timings = None, {} + self.simulate_call("profiler") + self.simulate_call("new_tasklet") + + def TallyTimings(self): + oldtimings = self.sleeping + self.sleeping = {} + + # first, unwind the main "cur" + self.cur = self.Unwind(self.cur, self.timings) + + # we must keep the timings dicts separate for each tasklet, since it contains + # the 'ns' item, recursion count of each function in that tasklet. This is + # used in the Unwind dude. + for tasklet, (cur, timings) in six.iteritems(oldtimings): + self.Unwind(cur, timings) + + for k, v in six.iteritems(timings): + if k not in self.timings: + self.timings[k] = v + else: + # accumulate all to the self.timings + cc, ns, tt, ct, callers = self.timings[k] + # ns should be 0 after unwinding + cc += v[0] + tt += v[2] + ct += v[3] + for k1, v1 in six.iteritems(v[4]): + callers[k1] = callers.get(k1, 0) + v1 + self.timings[k] = cc, ns, tt, ct, callers + + def Unwind(self, cur, timings): + "A function to unwind a 'cur' frame and tally the results" + "see profile.trace_dispatch_return() for details" + # also see simulate_cmd_complete() + while(cur[-1]): + rpt, rit, ret, rfn, frame, rcur = cur + frame_total = rit + ret + + if rfn in timings: + cc, ns, tt, ct, callers = timings[rfn] + else: + cc, ns, tt, ct, callers = 0, 0, 0, 0, {} + + if not ns: + ct = ct + frame_total + cc = cc + 1 + + if rcur: + ppt, pit, pet, pfn, pframe, pcur = rcur + else: + pfn = None + + if pfn in callers: + callers[pfn] = callers[pfn] + 1 # hack: gather more + elif pfn: + callers[pfn] = 1 + + timings[rfn] = cc, ns - 1, tt + rit, ct, callers + + ppt, pit, pet, pfn, pframe, pcur = rcur + rcur = ppt, pit + rpt, pet + frame_total, pfn, pframe, pcur + cur = rcur + return cur + + +def ContextWrap(f): + @functools.wraps(f) + def ContextWrapper(self, arg, t): + current = greenthread.getcurrent() + if current != self.current_tasklet: + self.SwitchTasklet(self.current_tasklet, current, t) + t = 0.0 # the time was billed to the previous tasklet + return f(self, arg, t) + return ContextWrapper + + +# Add "return safety" to the dispatchers +Profile.dispatch = dict(profile_orig.Profile.dispatch, **{ + 'return': Profile.trace_dispatch_return_extend_back, + 'c_return': Profile.trace_dispatch_c_return_extend_back, +}) +# Add automatic tasklet detection to the callbacks. +Profile.dispatch = dict((k, ContextWrap(v)) for k, v in six.viewitems(Profile.dispatch)) + + +# run statements shamelessly stolen from profile.py +def run(statement, filename=None, sort=-1): + """Run statement under profiler optionally saving results in filename + + This function takes a single argument that can be passed to the + "exec" statement, and an optional file name. In all cases this + routine attempts to "exec" its first argument and gather profiling + statistics from the execution. If no file name is present, then this + function automatically prints a simple profiling report, sorted by the + standard name string (file/line/function-name) that is presented in + each line. + """ + prof = Profile() + try: + prof = prof.run(statement) + except SystemExit: + pass + if filename is not None: + prof.dump_stats(filename) + else: + return prof.print_stats(sort) + + +def runctx(statement, globals, locals, filename=None): + """Run statement under profiler, supplying your own globals and locals, + optionally saving results in filename. + + statement and filename have the same semantics as profile.run + """ + prof = Profile() + try: + prof = prof.runctx(statement, globals, locals) + except SystemExit: + pass + + if filename is not None: + prof.dump_stats(filename) + else: + return prof.print_stats() diff --git a/venv/lib/python3.12/site-packages/eventlet/green/select.py b/venv/lib/python3.12/site-packages/eventlet/green/select.py new file mode 100644 index 0000000..e293f36 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/select.py @@ -0,0 +1,86 @@ +import eventlet +from eventlet.hubs import get_hub +import six +__select = eventlet.patcher.original('select') +error = __select.error + + +__patched__ = ['select'] +__deleted__ = ['devpoll', 'poll', 'epoll', 'kqueue', 'kevent'] + + +def get_fileno(obj): + # The purpose of this function is to exactly replicate + # the behavior of the select module when confronted with + # abnormal filenos; the details are extensively tested in + # the stdlib test/test_select.py. + try: + f = obj.fileno + except AttributeError: + if not isinstance(obj, six.integer_types): + raise TypeError("Expected int or long, got %s" % type(obj)) + return obj + else: + rv = f() + if not isinstance(rv, six.integer_types): + raise TypeError("Expected int or long, got %s" % type(rv)) + return rv + + +def select(read_list, write_list, error_list, timeout=None): + # error checking like this is required by the stdlib unit tests + if timeout is not None: + try: + timeout = float(timeout) + except ValueError: + raise TypeError("Expected number for timeout") + hub = get_hub() + timers = [] + current = eventlet.getcurrent() + assert hub.greenlet is not current, 'do not call blocking functions from the mainloop' + ds = {} + for r in read_list: + ds[get_fileno(r)] = {'read': r} + for w in write_list: + ds.setdefault(get_fileno(w), {})['write'] = w + for e in error_list: + ds.setdefault(get_fileno(e), {})['error'] = e + + listeners = [] + + def on_read(d): + original = ds[get_fileno(d)]['read'] + current.switch(([original], [], [])) + + def on_write(d): + original = ds[get_fileno(d)]['write'] + current.switch(([], [original], [])) + + def on_timeout2(): + current.switch(([], [], [])) + + def on_timeout(): + # ensure that BaseHub.run() has a chance to call self.wait() + # at least once before timed out. otherwise the following code + # can time out erroneously. + # + # s1, s2 = socket.socketpair() + # print(select.select([], [s1], [], 0)) + timers.append(hub.schedule_call_global(0, on_timeout2)) + + if timeout is not None: + timers.append(hub.schedule_call_global(timeout, on_timeout)) + try: + for k, v in six.iteritems(ds): + if v.get('read'): + listeners.append(hub.add(hub.READ, k, on_read, current.throw, lambda: None)) + if v.get('write'): + listeners.append(hub.add(hub.WRITE, k, on_write, current.throw, lambda: None)) + try: + return hub.switch() + finally: + for l in listeners: + hub.remove(l) + finally: + for t in timers: + t.cancel() diff --git a/venv/lib/python3.12/site-packages/eventlet/green/selectors.py b/venv/lib/python3.12/site-packages/eventlet/green/selectors.py new file mode 100644 index 0000000..81fc862 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/selectors.py @@ -0,0 +1,34 @@ +import sys + +from eventlet import patcher +from eventlet.green import select + +__patched__ = [ + 'DefaultSelector', + 'SelectSelector', +] + +# We only have green select so the options are: +# * leave it be and have selectors that block +# * try to pretend the "bad" selectors don't exist +# * replace all with SelectSelector for the price of possibly different +# performance characteristic and missing fileno() method (if someone +# uses it it'll result in a crash, we may want to implement it in the future) +# +# This module used to follow the third approach but just removing the offending +# selectors is less error prone and less confusing approach. +__deleted__ = [ + 'PollSelector', + 'EpollSelector', + 'DevpollSelector', + 'KqueueSelector', +] + +patcher.inject('selectors', globals(), ('select', select)) + +del patcher + +if sys.platform != 'win32': + SelectSelector._select = staticmethod(select.select) + +DefaultSelector = SelectSelector diff --git a/venv/lib/python3.12/site-packages/eventlet/green/socket.py b/venv/lib/python3.12/site-packages/eventlet/green/socket.py new file mode 100644 index 0000000..6a39caf --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/socket.py @@ -0,0 +1,63 @@ +import os +import sys + +__import__('eventlet.green._socket_nodns') +__socket = sys.modules['eventlet.green._socket_nodns'] + +__all__ = __socket.__all__ +__patched__ = __socket.__patched__ + [ + 'create_connection', + 'getaddrinfo', + 'gethostbyname', + 'gethostbyname_ex', + 'getnameinfo', +] + +from eventlet.patcher import slurp_properties +slurp_properties(__socket, globals(), srckeys=dir(__socket)) + + +if os.environ.get("EVENTLET_NO_GREENDNS", '').lower() != 'yes': + from eventlet.support import greendns + gethostbyname = greendns.gethostbyname + getaddrinfo = greendns.getaddrinfo + gethostbyname_ex = greendns.gethostbyname_ex + getnameinfo = greendns.getnameinfo + del greendns + + +def create_connection(address, + timeout=_GLOBAL_DEFAULT_TIMEOUT, + source_address=None): + """Connect to *address* and return the socket object. + + Convenience function. Connect to *address* (a 2-tuple ``(host, + port)``) and return the socket object. Passing the optional + *timeout* parameter will set the timeout on the socket instance + before attempting to connect. If no *timeout* is supplied, the + global default timeout setting returned by :func:`getdefaulttimeout` + is used. + """ + + err = "getaddrinfo returns an empty list" + host, port = address + for res in getaddrinfo(host, port, 0, SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket(af, socktype, proto) + if timeout is not _GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + return sock + + except error as e: + err = e + if sock is not None: + sock.close() + + if not isinstance(err, error): + err = error(err) + raise err diff --git a/venv/lib/python3.12/site-packages/eventlet/green/ssl.py b/venv/lib/python3.12/site-packages/eventlet/green/ssl.py new file mode 100644 index 0000000..b42ddaa --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/ssl.py @@ -0,0 +1,493 @@ +__ssl = __import__('ssl') + +from eventlet.patcher import slurp_properties +slurp_properties(__ssl, globals(), srckeys=dir(__ssl)) + +import sys +from eventlet import greenio, hubs +from eventlet.greenio import ( + set_nonblocking, GreenSocket, CONNECT_ERR, CONNECT_SUCCESS, +) +from eventlet.hubs import trampoline, IOClosed +from eventlet.support import get_errno, PY33 +import six +from contextlib import contextmanager + +orig_socket = __import__('socket') +socket = orig_socket.socket +timeout_exc = SSLError + +__patched__ = [ + 'SSLSocket', 'SSLContext', 'wrap_socket', 'sslwrap_simple', + 'create_default_context', '_create_default_https_context'] + +_original_sslsocket = __ssl.SSLSocket +_original_wrap_socket = __ssl.wrap_socket +_original_sslcontext = getattr(__ssl, 'SSLContext', None) +_is_under_py_3_7 = sys.version_info < (3, 7) + + +@contextmanager +def _original_ssl_context(*args, **kwargs): + tmp_sslcontext = _original_wrap_socket.__globals__.get('SSLContext', None) + tmp_sslsocket = _original_sslsocket._create.__globals__.get('SSLSocket', None) + _original_sslsocket._create.__globals__['SSLSocket'] = _original_sslsocket + _original_wrap_socket.__globals__['SSLContext'] = _original_sslcontext + try: + yield + finally: + _original_wrap_socket.__globals__['SSLContext'] = tmp_sslcontext + _original_sslsocket._create.__globals__['SSLSocket'] = tmp_sslsocket + + +class GreenSSLSocket(_original_sslsocket): + """ This is a green version of the SSLSocket class from the ssl module added + in 2.6. For documentation on it, please see the Python standard + documentation. + + Python nonblocking ssl objects don't give errors when the other end + of the socket is closed (they do notice when the other end is shutdown, + though). Any write/read operations will simply hang if the socket is + closed from the other end. There is no obvious fix for this problem; + it appears to be a limitation of Python's ssl object implementation. + A workaround is to set a reasonable timeout on the socket using + settimeout(), and to close/reopen the connection when a timeout + occurs at an unexpected juncture in the code. + """ + def __new__(cls, sock=None, keyfile=None, certfile=None, + server_side=False, cert_reqs=CERT_NONE, + ssl_version=PROTOCOL_SSLv23, ca_certs=None, + do_handshake_on_connect=True, *args, **kw): + if _is_under_py_3_7: + return super(GreenSSLSocket, cls).__new__(cls) + else: + if not isinstance(sock, GreenSocket): + sock = GreenSocket(sock) + with _original_ssl_context(): + context = kw.get('_context') + if context: + ret = _original_sslsocket._create( + sock=sock.fd, + server_side=server_side, + do_handshake_on_connect=False, + suppress_ragged_eofs=kw.get('suppress_ragged_eofs', True), + server_hostname=kw.get('server_hostname'), + context=context, + session=kw.get('session'), + ) + else: + ret = _original_wrap_socket( + sock=sock.fd, + keyfile=keyfile, + certfile=certfile, + server_side=server_side, + cert_reqs=cert_reqs, + ssl_version=ssl_version, + ca_certs=ca_certs, + do_handshake_on_connect=False, + ciphers=kw.get('ciphers'), + ) + ret.keyfile = keyfile + ret.certfile = certfile + ret.cert_reqs = cert_reqs + ret.ssl_version = ssl_version + ret.ca_certs = ca_certs + ret.__class__ = GreenSSLSocket + return ret + + # we are inheriting from SSLSocket because its constructor calls + # do_handshake whose behavior we wish to override + def __init__(self, sock, keyfile=None, certfile=None, + server_side=False, cert_reqs=CERT_NONE, + ssl_version=PROTOCOL_SSLv23, ca_certs=None, + do_handshake_on_connect=True, *args, **kw): + if not isinstance(sock, GreenSocket): + sock = GreenSocket(sock) + self.act_non_blocking = sock.act_non_blocking + + if six.PY2: + # On Python 2 SSLSocket constructor queries the timeout, it'd break without + # this assignment + self._timeout = sock.gettimeout() + + if _is_under_py_3_7: + # nonblocking socket handshaking on connect got disabled so let's pretend it's disabled + # even when it's on + super(GreenSSLSocket, self).__init__( + sock.fd, keyfile, certfile, server_side, cert_reqs, ssl_version, + ca_certs, do_handshake_on_connect and six.PY2, *args, **kw) + # the superclass initializer trashes the methods so we remove + # the local-object versions of them and let the actual class + # methods shine through + # Note: This for Python 2 + try: + for fn in orig_socket._delegate_methods: + delattr(self, fn) + except AttributeError: + pass + + if six.PY3: + # Python 3 SSLSocket construction process overwrites the timeout so restore it + self._timeout = sock.gettimeout() + + # it also sets timeout to None internally apparently (tested with 3.4.2) + _original_sslsocket.settimeout(self, 0.0) + assert _original_sslsocket.gettimeout(self) == 0.0 + + # see note above about handshaking + self.do_handshake_on_connect = do_handshake_on_connect + if do_handshake_on_connect and self._connected: + self.do_handshake() + + def settimeout(self, timeout): + self._timeout = timeout + + def gettimeout(self): + return self._timeout + + def setblocking(self, flag): + if flag: + self.act_non_blocking = False + self._timeout = None + else: + self.act_non_blocking = True + self._timeout = 0.0 + + def _call_trampolining(self, func, *a, **kw): + if self.act_non_blocking: + return func(*a, **kw) + else: + while True: + try: + return func(*a, **kw) + except SSLError as exc: + if get_errno(exc) == SSL_ERROR_WANT_READ: + trampoline(self, + read=True, + timeout=self.gettimeout(), + timeout_exc=timeout_exc('timed out')) + elif get_errno(exc) == SSL_ERROR_WANT_WRITE: + trampoline(self, + write=True, + timeout=self.gettimeout(), + timeout_exc=timeout_exc('timed out')) + else: + raise + + def write(self, data): + """Write DATA to the underlying SSL channel. Returns + number of bytes of DATA actually transmitted.""" + return self._call_trampolining( + super(GreenSSLSocket, self).write, data) + + def read(self, *args, **kwargs): + """Read up to LEN bytes and return them. + Return zero-length string on EOF.""" + try: + return self._call_trampolining( + super(GreenSSLSocket, self).read, *args, **kwargs) + except IOClosed: + return b'' + + def send(self, data, flags=0): + if self._sslobj: + return self._call_trampolining( + super(GreenSSLSocket, self).send, data, flags) + else: + trampoline(self, write=True, timeout_exc=timeout_exc('timed out')) + return socket.send(self, data, flags) + + def sendto(self, data, addr, flags=0): + # *NOTE: gross, copied code from ssl.py becase it's not factored well enough to be used as-is + if self._sslobj: + raise ValueError("sendto not allowed on instances of %s" % + self.__class__) + else: + trampoline(self, write=True, timeout_exc=timeout_exc('timed out')) + return socket.sendto(self, data, addr, flags) + + def sendall(self, data, flags=0): + # *NOTE: gross, copied code from ssl.py becase it's not factored well enough to be used as-is + if self._sslobj: + if flags != 0: + raise ValueError( + "non-zero flags not allowed in calls to sendall() on %s" % + self.__class__) + amount = len(data) + count = 0 + data_to_send = data + while (count < amount): + v = self.send(data_to_send) + count += v + if v == 0: + trampoline(self, write=True, timeout_exc=timeout_exc('timed out')) + else: + data_to_send = data[count:] + return amount + else: + while True: + try: + return socket.sendall(self, data, flags) + except orig_socket.error as e: + if self.act_non_blocking: + raise + erno = get_errno(e) + if erno in greenio.SOCKET_BLOCKING: + trampoline(self, write=True, + timeout=self.gettimeout(), timeout_exc=timeout_exc('timed out')) + elif erno in greenio.SOCKET_CLOSED: + return '' + raise + + def recv(self, buflen=1024, flags=0): + return self._base_recv(buflen, flags, into=False) + + def recv_into(self, buffer, nbytes=None, flags=0): + # Copied verbatim from CPython + if buffer and nbytes is None: + nbytes = len(buffer) + elif nbytes is None: + nbytes = 1024 + # end of CPython code + + return self._base_recv(nbytes, flags, into=True, buffer_=buffer) + + def _base_recv(self, nbytes, flags, into, buffer_=None): + if into: + plain_socket_function = socket.recv_into + else: + plain_socket_function = socket.recv + + # *NOTE: gross, copied code from ssl.py becase it's not factored well enough to be used as-is + if self._sslobj: + if flags != 0: + raise ValueError( + "non-zero flags not allowed in calls to %s() on %s" % + plain_socket_function.__name__, self.__class__) + if into: + read = self.read(nbytes, buffer_) + else: + read = self.read(nbytes) + return read + else: + while True: + try: + args = [self, nbytes, flags] + if into: + args.insert(1, buffer_) + return plain_socket_function(*args) + except orig_socket.error as e: + if self.act_non_blocking: + raise + erno = get_errno(e) + if erno in greenio.SOCKET_BLOCKING: + try: + trampoline( + self, read=True, + timeout=self.gettimeout(), timeout_exc=timeout_exc('timed out')) + except IOClosed: + return b'' + elif erno in greenio.SOCKET_CLOSED: + return b'' + raise + + def recvfrom(self, addr, buflen=1024, flags=0): + if not self.act_non_blocking: + trampoline(self, read=True, timeout=self.gettimeout(), + timeout_exc=timeout_exc('timed out')) + return super(GreenSSLSocket, self).recvfrom(addr, buflen, flags) + + def recvfrom_into(self, buffer, nbytes=None, flags=0): + if not self.act_non_blocking: + trampoline(self, read=True, timeout=self.gettimeout(), + timeout_exc=timeout_exc('timed out')) + return super(GreenSSLSocket, self).recvfrom_into(buffer, nbytes, flags) + + def unwrap(self): + return GreenSocket(self._call_trampolining( + super(GreenSSLSocket, self).unwrap)) + + def do_handshake(self): + """Perform a TLS/SSL handshake.""" + return self._call_trampolining( + super(GreenSSLSocket, self).do_handshake) + + def _socket_connect(self, addr): + real_connect = socket.connect + if self.act_non_blocking: + return real_connect(self, addr) + else: + clock = hubs.get_hub().clock + # *NOTE: gross, copied code from greenio because it's not factored + # well enough to reuse + if self.gettimeout() is None: + while True: + try: + return real_connect(self, addr) + except orig_socket.error as exc: + if get_errno(exc) in CONNECT_ERR: + trampoline(self, write=True) + elif get_errno(exc) in CONNECT_SUCCESS: + return + else: + raise + else: + end = clock() + self.gettimeout() + while True: + try: + real_connect(self, addr) + except orig_socket.error as exc: + if get_errno(exc) in CONNECT_ERR: + trampoline( + self, write=True, + timeout=end - clock(), timeout_exc=timeout_exc('timed out')) + elif get_errno(exc) in CONNECT_SUCCESS: + return + else: + raise + if clock() >= end: + raise timeout_exc('timed out') + + def connect(self, addr): + """Connects to remote ADDR, and then wraps the connection in + an SSL channel.""" + # *NOTE: grrrrr copied this code from ssl.py because of the reference + # to socket.connect which we don't want to call directly + if self._sslobj: + raise ValueError("attempt to connect already-connected SSLSocket!") + self._socket_connect(addr) + server_side = False + try: + sslwrap = _ssl.sslwrap + except AttributeError: + # sslwrap was removed in 3.x and later in 2.7.9 + if six.PY2: + sslobj = self._context._wrap_socket(self._sock, server_side, ssl_sock=self) + else: + context = self.context if PY33 else self._context + sslobj = context._wrap_socket(self, server_side, server_hostname=self.server_hostname) + else: + sslobj = sslwrap(self._sock, server_side, self.keyfile, self.certfile, + self.cert_reqs, self.ssl_version, + self.ca_certs, *self.ciphers) + + try: + # This is added in Python 3.5, http://bugs.python.org/issue21965 + SSLObject + except NameError: + self._sslobj = sslobj + else: + if _is_under_py_3_7: + self._sslobj = SSLObject(sslobj, owner=self) + else: + self._sslobj = sslobj + + if self.do_handshake_on_connect: + self.do_handshake() + + def accept(self): + """Accepts a new connection from a remote client, and returns + a tuple containing that new connection wrapped with a server-side + SSL channel, and the address of the remote client.""" + # RDW grr duplication of code from greenio + if self.act_non_blocking: + newsock, addr = socket.accept(self) + else: + while True: + try: + newsock, addr = socket.accept(self) + break + except orig_socket.error as e: + if get_errno(e) not in greenio.SOCKET_BLOCKING: + raise + trampoline(self, read=True, timeout=self.gettimeout(), + timeout_exc=timeout_exc('timed out')) + + new_ssl = type(self)( + newsock, + server_side=True, + do_handshake_on_connect=False, + suppress_ragged_eofs=self.suppress_ragged_eofs, + _context=self._context, + ) + return (new_ssl, addr) + + def dup(self): + raise NotImplementedError("Can't dup an ssl object") + + +SSLSocket = GreenSSLSocket + + +def wrap_socket(sock, *a, **kw): + return GreenSSLSocket(sock, *a, **kw) + + +if hasattr(__ssl, 'sslwrap_simple'): + def sslwrap_simple(sock, keyfile=None, certfile=None): + """A replacement for the old socket.ssl function. Designed + for compatibility with Python 2.5 and earlier. Will disappear in + Python 3.0.""" + ssl_sock = GreenSSLSocket(sock, keyfile=keyfile, certfile=certfile, + server_side=False, + cert_reqs=CERT_NONE, + ssl_version=PROTOCOL_SSLv23, + ca_certs=None) + return ssl_sock + + +if hasattr(__ssl, 'SSLContext'): + _original_sslcontext = __ssl.SSLContext + + class GreenSSLContext(_original_sslcontext): + __slots__ = () + + def wrap_socket(self, sock, *a, **kw): + return GreenSSLSocket(sock, *a, _context=self, **kw) + + # https://github.com/eventlet/eventlet/issues/371 + # Thanks to Gevent developers for sharing patch to this problem. + if hasattr(_original_sslcontext.options, 'setter'): + # In 3.6, these became properties. They want to access the + # property __set__ method in the superclass, and they do so by using + # super(SSLContext, SSLContext). But we rebind SSLContext when we monkey + # patch, which causes infinite recursion. + # https://github.com/python/cpython/commit/328067c468f82e4ec1b5c510a4e84509e010f296 + @_original_sslcontext.options.setter + def options(self, value): + super(_original_sslcontext, _original_sslcontext).options.__set__(self, value) + + @_original_sslcontext.verify_flags.setter + def verify_flags(self, value): + super(_original_sslcontext, _original_sslcontext).verify_flags.__set__(self, value) + + @_original_sslcontext.verify_mode.setter + def verify_mode(self, value): + super(_original_sslcontext, _original_sslcontext).verify_mode.__set__(self, value) + + if hasattr(_original_sslcontext, "maximum_version"): + @_original_sslcontext.maximum_version.setter + def maximum_version(self, value): + super(_original_sslcontext, _original_sslcontext).maximum_version.__set__(self, value) + + if hasattr(_original_sslcontext, "minimum_version"): + @_original_sslcontext.minimum_version.setter + def minimum_version(self, value): + super(_original_sslcontext, _original_sslcontext).minimum_version.__set__(self, value) + + SSLContext = GreenSSLContext + + if hasattr(__ssl, 'create_default_context'): + _original_create_default_context = __ssl.create_default_context + + def green_create_default_context(*a, **kw): + # We can't just monkey-patch on the green version of `wrap_socket` + # on to SSLContext instances, but SSLContext.create_default_context + # does a bunch of work. Rather than re-implementing it all, just + # switch out the __class__ to get our `wrap_socket` implementation + context = _original_create_default_context(*a, **kw) + context.__class__ = GreenSSLContext + return context + + create_default_context = green_create_default_context + _create_default_https_context = green_create_default_context diff --git a/venv/lib/python3.12/site-packages/eventlet/green/subprocess.py b/venv/lib/python3.12/site-packages/eventlet/green/subprocess.py new file mode 100644 index 0000000..f021ced --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/subprocess.py @@ -0,0 +1,143 @@ +import errno +import sys +from types import FunctionType + +import eventlet +from eventlet import greenio +from eventlet import patcher +from eventlet.green import select, threading, time +import six + + +__patched__ = ['call', 'check_call', 'Popen'] +to_patch = [('select', select), ('threading', threading), ('time', time)] + +if sys.version_info > (3, 4): + from eventlet.green import selectors + to_patch.append(('selectors', selectors)) + +patcher.inject('subprocess', globals(), *to_patch) +subprocess_orig = patcher.original("subprocess") +subprocess_imported = sys.modules.get('subprocess', subprocess_orig) +mswindows = sys.platform == "win32" + + +if getattr(subprocess_orig, 'TimeoutExpired', None) is None: + # Backported from Python 3.3. + # https://bitbucket.org/eventlet/eventlet/issue/89 + class TimeoutExpired(Exception): + """This exception is raised when the timeout expires while waiting for + a child process. + """ + + def __init__(self, cmd, timeout, output=None): + self.cmd = cmd + self.timeout = timeout + self.output = output + + def __str__(self): + return ("Command '%s' timed out after %s seconds" % + (self.cmd, self.timeout)) +else: + TimeoutExpired = subprocess_imported.TimeoutExpired + + +# This is the meat of this module, the green version of Popen. +class Popen(subprocess_orig.Popen): + """eventlet-friendly version of subprocess.Popen""" + # We do not believe that Windows pipes support non-blocking I/O. At least, + # the Python file objects stored on our base-class object have no + # setblocking() method, and the Python fcntl module doesn't exist on + # Windows. (see eventlet.greenio.set_nonblocking()) As the sole purpose of + # this __init__() override is to wrap the pipes for eventlet-friendly + # non-blocking I/O, don't even bother overriding it on Windows. + if not mswindows: + def __init__(self, args, bufsize=0, *argss, **kwds): + self.args = args + # Forward the call to base-class constructor + subprocess_orig.Popen.__init__(self, args, 0, *argss, **kwds) + # Now wrap the pipes, if any. This logic is loosely borrowed from + # eventlet.processes.Process.run() method. + for attr in "stdin", "stdout", "stderr": + pipe = getattr(self, attr) + if pipe is not None and type(pipe) != greenio.GreenPipe: + # https://github.com/eventlet/eventlet/issues/243 + # AttributeError: '_io.TextIOWrapper' object has no attribute 'mode' + mode = getattr(pipe, 'mode', '') + if not mode: + if pipe.readable(): + mode += 'r' + if pipe.writable(): + mode += 'w' + # ValueError: can't have unbuffered text I/O + if bufsize == 0: + bufsize = -1 + wrapped_pipe = greenio.GreenPipe(pipe, mode, bufsize) + setattr(self, attr, wrapped_pipe) + __init__.__doc__ = subprocess_orig.Popen.__init__.__doc__ + + def wait(self, timeout=None, check_interval=0.01): + # Instead of a blocking OS call, this version of wait() uses logic + # borrowed from the eventlet 0.2 processes.Process.wait() method. + if timeout is not None: + endtime = time.time() + timeout + try: + while True: + status = self.poll() + if status is not None: + return status + if timeout is not None and time.time() > endtime: + raise TimeoutExpired(self.args, timeout) + eventlet.sleep(check_interval) + except OSError as e: + if e.errno == errno.ECHILD: + # no child process, this happens if the child process + # already died and has been cleaned up + return -1 + else: + raise + wait.__doc__ = subprocess_orig.Popen.wait.__doc__ + + if not mswindows: + # don't want to rewrite the original _communicate() method, we + # just want a version that uses eventlet.green.select.select() + # instead of select.select(). + _communicate = FunctionType( + six.get_function_code(six.get_unbound_function( + subprocess_orig.Popen._communicate)), + globals()) + try: + _communicate_with_select = FunctionType( + six.get_function_code(six.get_unbound_function( + subprocess_orig.Popen._communicate_with_select)), + globals()) + _communicate_with_poll = FunctionType( + six.get_function_code(six.get_unbound_function( + subprocess_orig.Popen._communicate_with_poll)), + globals()) + except AttributeError: + pass + + +# Borrow subprocess.call() and check_call(), but patch them so they reference +# OUR Popen class rather than subprocess.Popen. +def patched_function(function): + new_function = FunctionType(six.get_function_code(function), globals()) + if six.PY3: + new_function.__kwdefaults__ = function.__kwdefaults__ + new_function.__defaults__ = function.__defaults__ + return new_function + + +call = patched_function(subprocess_orig.call) +check_call = patched_function(subprocess_orig.check_call) +# check_output is Python 2.7+ +if hasattr(subprocess_orig, 'check_output'): + __patched__.append('check_output') + check_output = patched_function(subprocess_orig.check_output) +del patched_function + +# Keep exceptions identity. +# https://github.com/eventlet/eventlet/issues/413 +CalledProcessError = subprocess_imported.CalledProcessError +del subprocess_imported diff --git a/venv/lib/python3.12/site-packages/eventlet/green/thread.py b/venv/lib/python3.12/site-packages/eventlet/green/thread.py new file mode 100644 index 0000000..e26f6b3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/thread.py @@ -0,0 +1,115 @@ +"""Implements the standard thread module, using greenthreads.""" +from six.moves import _thread as __thread +import six +from eventlet.support import greenlets as greenlet +from eventlet import greenthread +from eventlet.lock import Lock +import sys + + +__patched__ = ['get_ident', 'start_new_thread', 'start_new', 'allocate_lock', + 'allocate', 'exit', 'interrupt_main', 'stack_size', '_local', + 'LockType', 'Lock', '_count'] + +error = __thread.error +LockType = Lock +__threadcount = 0 + + +if six.PY3: + def _set_sentinel(): + # TODO this is a dummy code, reimplementing this may be needed: + # https://hg.python.org/cpython/file/b5e9bc4352e1/Modules/_threadmodule.c#l1203 + return allocate_lock() + + TIMEOUT_MAX = __thread.TIMEOUT_MAX + + +def _count(): + return __threadcount + + +def get_ident(gr=None): + if gr is None: + return id(greenlet.getcurrent()) + else: + return id(gr) + + +def __thread_body(func, args, kwargs): + global __threadcount + __threadcount += 1 + try: + func(*args, **kwargs) + finally: + __threadcount -= 1 + + +def start_new_thread(function, args=(), kwargs=None): + if (sys.version_info >= (3, 4) + and getattr(function, '__module__', '') == 'threading' + and hasattr(function, '__self__')): + # Since Python 3.4, threading.Thread uses an internal lock + # automatically released when the python thread state is deleted. + # With monkey patching, eventlet uses green threads without python + # thread state, so the lock is not automatically released. + # + # Wrap _bootstrap_inner() to release explicitly the thread state lock + # when the thread completes. + thread = function.__self__ + bootstrap_inner = thread._bootstrap_inner + + def wrap_bootstrap_inner(): + try: + bootstrap_inner() + finally: + # The lock can be cleared (ex: by a fork()) + if thread._tstate_lock is not None: + thread._tstate_lock.release() + + thread._bootstrap_inner = wrap_bootstrap_inner + + kwargs = kwargs or {} + g = greenthread.spawn_n(__thread_body, function, args, kwargs) + return get_ident(g) + + +start_new = start_new_thread + + +def allocate_lock(*a): + return LockType(1) + + +allocate = allocate_lock + + +def exit(): + raise greenlet.GreenletExit + + +exit_thread = __thread.exit_thread + + +def interrupt_main(): + curr = greenlet.getcurrent() + if curr.parent and not curr.parent.dead: + curr.parent.throw(KeyboardInterrupt()) + else: + raise KeyboardInterrupt() + + +if hasattr(__thread, 'stack_size'): + __original_stack_size__ = __thread.stack_size + + def stack_size(size=None): + if size is None: + return __original_stack_size__() + if size > __original_stack_size__(): + return __original_stack_size__(size) + else: + pass + # not going to decrease stack_size, because otherwise other greenlets in + # this thread will suffer + +from eventlet.corolocal import local as _local diff --git a/venv/lib/python3.12/site-packages/eventlet/green/threading.py b/venv/lib/python3.12/site-packages/eventlet/green/threading.py new file mode 100644 index 0000000..fce9bca --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/threading.py @@ -0,0 +1,134 @@ +"""Implements the standard threading module, using greenthreads.""" +import eventlet +from eventlet.green import thread +from eventlet.green import time +from eventlet.support import greenlets as greenlet +import six + +__patched__ = ['_start_new_thread', '_allocate_lock', + '_sleep', 'local', 'stack_size', 'Lock', 'currentThread', + 'current_thread', '_after_fork', '_shutdown'] + +if six.PY2: + __patched__ += ['_get_ident'] +else: + __patched__ += ['get_ident', '_set_sentinel'] + +__orig_threading = eventlet.patcher.original('threading') +__threadlocal = __orig_threading.local() +__patched_enumerate = None + + +eventlet.patcher.inject( + 'threading', + globals(), + ('thread' if six.PY2 else '_thread', thread), + ('time', time)) + + +_count = 1 + + +class _GreenThread(object): + """Wrapper for GreenThread objects to provide Thread-like attributes + and methods""" + + def __init__(self, g): + global _count + self._g = g + self._name = 'GreenThread-%d' % _count + _count += 1 + + def __repr__(self): + return '<_GreenThread(%s, %r)>' % (self._name, self._g) + + def join(self, timeout=None): + return self._g.wait() + + def getName(self): + return self._name + get_name = getName + + def setName(self, name): + self._name = str(name) + set_name = setName + + name = property(getName, setName) + + ident = property(lambda self: id(self._g)) + + def isAlive(self): + return True + is_alive = isAlive + + daemon = property(lambda self: True) + + def isDaemon(self): + return self.daemon + is_daemon = isDaemon + + +__threading = None + + +def _fixup_thread(t): + # Some third-party packages (lockfile) will try to patch the + # threading.Thread class with a get_name attribute if it doesn't + # exist. Since we might return Thread objects from the original + # threading package that won't get patched, let's make sure each + # individual object gets patched too our patched threading.Thread + # class has been patched. This is why monkey patching can be bad... + global __threading + if not __threading: + __threading = __import__('threading') + + if (hasattr(__threading.Thread, 'get_name') and + not hasattr(t, 'get_name')): + t.get_name = t.getName + return t + + +def current_thread(): + global __patched_enumerate + g = greenlet.getcurrent() + if not g: + # Not currently in a greenthread, fall back to standard function + return _fixup_thread(__orig_threading.current_thread()) + + try: + active = __threadlocal.active + except AttributeError: + active = __threadlocal.active = {} + + g_id = id(g) + t = active.get(g_id) + if t is not None: + return t + + # FIXME: move import from function body to top + # (jaketesler@github) Furthermore, I was unable to have the current_thread() return correct results from + # threading.enumerate() unless the enumerate() function was a) imported at runtime using the gross __import__() call + # and b) was hot-patched using patch_function(). + # https://github.com/eventlet/eventlet/issues/172#issuecomment-379421165 + if __patched_enumerate is None: + __patched_enumerate = eventlet.patcher.patch_function(__import__('threading').enumerate) + found = [th for th in __patched_enumerate() if th.ident == g_id] + if found: + return found[0] + + # Add green thread to active if we can clean it up on exit + def cleanup(g): + del active[g_id] + try: + g.link(cleanup) + except AttributeError: + # Not a GreenThread type, so there's no way to hook into + # the green thread exiting. Fall back to the standard + # function then. + t = _fixup_thread(__orig_threading.current_thread()) + else: + t = active[g_id] = _GreenThread(g) + + return t + +currentThread = current_thread diff --git a/venv/lib/python3.12/site-packages/eventlet/green/time.py b/venv/lib/python3.12/site-packages/eventlet/green/time.py new file mode 100644 index 0000000..0fbe30e --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/time.py @@ -0,0 +1,6 @@ +__time = __import__('time') +from eventlet.patcher import slurp_properties +__patched__ = ['sleep'] +slurp_properties(__time, globals(), ignore=__patched__, srckeys=dir(__time)) +from eventlet.greenthread import sleep +sleep # silence pyflakes diff --git a/venv/lib/python3.12/site-packages/eventlet/green/urllib/__init__.py b/venv/lib/python3.12/site-packages/eventlet/green/urllib/__init__.py new file mode 100644 index 0000000..bcfc349 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/green/urllib/__init__.py @@ -0,0 +1,40 @@ +from eventlet import patcher +from eventlet.green import socket +from eventlet.green import time +from eventlet.green import httplib +from eventlet.green import ftplib +import six + +if six.PY2: + to_patch = [('socket', socket), ('httplib', httplib), + ('time', time), ('ftplib', ftplib)] + try: + from eventlet.green import ssl + to_patch.append(('ssl', ssl)) + except ImportError: + pass + + patcher.inject('urllib', globals(), *to_patch) + try: + URLopener + except NameError: + patcher.inject('urllib.request', globals(), *to_patch) + + + # patch a bunch of things that have imports inside the + # function body; this is lame and hacky but I don't feel + # too bad because urllib is a hacky pile of junk that no + # one should be using anyhow + URLopener.open_http = patcher.patch_function(URLopener.open_http, ('httplib', httplib)) + if hasattr(URLopener, 'open_https'): + URLopener.open_https = patcher.patch_function(URLopener.open_https, ('httplib', httplib)) + + URLopener.open_ftp = patcher.patch_function(URLopener.open_ftp, ('ftplib', ftplib)) + ftpwrapper.init = patcher.patch_function(ftpwrapper.init, ('ftplib', ftplib)) + ftpwrapper.retrfile = patcher.patch_function(ftpwrapper.retrfile, ('ftplib', ftplib)) + + del patcher + + # Run test program when run as a script + if __name__ == '__main__': + main() diff --git a/venv/lib/python3.12/site-packages/eventlet/green/urllib/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/urllib/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e7967c9afabd1b76827fd4929a4054d71d0abf5e GIT binary patch literal 1699 zcmaJ>O>7%Q6rS;}_s8*H+QyFE&_EgpL21@8B&`riq$(*iszO_dHg*q-V~_21)<4W{ ze#|Kdq$U#3+%V!2aY__bc}&3#=;E2Y@D?) zORzKUw6K$4F3wq)Q~5ZL_?+LSZ~JwK{V5(XX-7&xPGUvG66-=-+L2-7qDb{R2$w7F zf&huRU^~}FDK~PZ9ow)qhrj_4%!Tb}+owDv;@v)3$T*PrEqu&<2_O&w@}1=MwfEZy z`Eva>Zu?ff+oV5qAfJ4$g#gt*k9;Yg9mD-rMK5N4-Og(J=a?_pu`_tyu=limtBZCO zq{1#c_P;!V+_1f;?f=6QkWcU644z0k-$u{zblZ^!5QS3ydD06k5YDiDM-Y@b`dkLc z-48H%<6_|R<`P(DVqgi9RXL#C2i*pH8GsO??-lD}IY>G39JZf$8 zT0C-kp^(&-V!;$o>m_YSXbD}%nEpLX!3vOem@Z)vA?P^-uN;EjL(oUS z@4$7$a1XD3dbQCRs*D-_zKz%pZ}eYlbOtLI*1a3un`6eyQIhI^YvsKr%Q`)W!fOq| z|H!xIt8{HJ75BdIYEuBt^DFN@ocUyC{o2>}zlj-abhEq0Mw<@CIdLF#HqU{JBb9-A zI8qHqc0%=$$?C}Dld^F)X55I^!Vgw%?z6!I-n&|^4DIs+jo#79QoVPy+B>?V)W@c) zW7B&vV^%h9eOT*Ftcv?W&rwLK1nVKG8j^OzdgOXFa{bBd-l8#iuNJyb=>mu>+czjPqDzu=k-5H2+*0Ne4b=?3oL-vBy2BtrV*^!!7Yy6DnGLkO($kGyMMBRH@X w+zuJS_|EKa!Z_VGajem6`5`m~!H-eLlpE|q=ak6o3Xod&rKu~-?hqvQU#KvYHvj+t literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/urllib/__pycache__/error.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/urllib/__pycache__/error.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1834f23865202ab9dba67bad3cb806819293137 GIT binary patch literal 411 zcmYL`u};G<5Qcq8n?%r-iH!%KMZ^NuiWMdnn97tUN7LfOk>eJn1A+qT=o4D|}S-Tbm-RrI?;5aYq6Y03#8! z0JJuk5t%I$h(Qo~PBHXf15=taF(Hi)QoFKmYS*>f%lnImY`oQc4#$mycXIey JS{3?F{s4aJapeF2 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/green/urllib/__pycache__/parse.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/green/urllib/__pycache__/parse.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f50cca3363426e84f559cdb071427cb4f9c16e24 GIT binary patch literal 325 zcmX@j%ge<81ZNK2%LoS2k3k$5V1+V1I{_Kf8B!Qh7;_kM8KW2(8JHMS8M9bm3K4V) z6HtbEHB?|V6NJIY5Y1T0tjY2c#MNZH#a@tDlAMuRRK-(Tl#`R0q*stwRGjLk$#RP$ zwJbHSBqz0`h#9E#7F%XsR%&v|E%x-B{G`O3;*|`aK}P%v*3Zb#P1R4%&q=D(PtHgz zDXG*4YAn+S8m3=RS(1^TXRK#vq+gs_lB!#fn4F!Mo?5IAGh08sC^a=tA7ZyY*lxXo z%3mBdx%nxjIjMF4)TtEMpwm{ypkp|xX!X&-2%tAiA!$<5d7*j&3U z-BTtZtq%z0jXe%{=`>CJ4WLOpz=KnYl3l4R5>LEE>;?6Sd!4#rlL}V!JLmq+Ilpss zuAQGFkq{uyFF*cX>GA;lK@a{T9XL-f1OWIN6hV>Efx&1DL;ZjrFj$Q>IE^!SjW+~M zFoIgpi?OOZ0T}(3 zc_@4Z7?6z?hagKjGFm*#di80)YGC0=?Tx|7_sR^6_jTY%?wHRWsgbO`?dJ^azciA$ zQJ+6jV{DMGjUAaaUVGP98rVT49M8Sv^Z%i7+Rqu-XEa{+`K+9cWR+}u^M&I8#$Ex4 zWxxvf@}~{(IlBTjn3>q0G+R70C2+kw6#}q#)9myoPkrB;(Z| zo;;ab(Sw+;Mmn;#t)mDnR1SlmtwGyC93d~yOkfeNlaX}jAeJy#@sF7@g0SwTc~d<% z2p)bmF+*oY^kc)`___Z0IR0E(e+opuR}`io(-P4!bKWAoS+S6}ELbn%@I}Kik+bOS zN~qRru;AceSvObnx{c$0QLb9gmDcJ7r%HB+6;J4pz@hWkuU^R4i#kMDBv+I(i8hv& zKE6a9N>ZC65iAk#7Hi`WaW@d117WUOuR8RS98{|6@IH7-W(NC9YQ;35S}^t1O|?+T zJI4KBnH+}^tJDl7dP2D^1CTHDaX&1yhT&SB!H_Por zx|K+8YwfwSt+}&qV#PgoV<)lF2Xz-;M_z15-Pm;V^>%E!6`S53{eI&9gd4l&zISyecC8Wm zBHH=CzFki0(4YL6Oa9%P1LE*sfMKM^oJhAS?b|W3JXUz}Nzs#}nloSm4STC5K` to be non blocking. +""" +__zmq__ = __import__('zmq') +import eventlet.hubs +from eventlet.patcher import slurp_properties +from eventlet.support import greenlets as greenlet + +__patched__ = ['Context', 'Socket'] +slurp_properties(__zmq__, globals(), ignore=__patched__) + +from collections import deque + +try: + # alias XREQ/XREP to DEALER/ROUTER if available + if not hasattr(__zmq__, 'XREQ'): + XREQ = DEALER + if not hasattr(__zmq__, 'XREP'): + XREP = ROUTER +except NameError: + pass + + +class LockReleaseError(Exception): + pass + + +class _QueueLock(object): + """A Lock that can be acquired by at most one thread. Any other + thread calling acquire will be blocked in a queue. When release + is called, the threads are awoken in the order they blocked, + one at a time. This lock can be required recursively by the same + thread.""" + + def __init__(self): + self._waiters = deque() + self._count = 0 + self._holder = None + self._hub = eventlet.hubs.get_hub() + + def __nonzero__(self): + return bool(self._count) + + __bool__ = __nonzero__ + + def __enter__(self): + self.acquire() + + def __exit__(self, type, value, traceback): + self.release() + + def acquire(self): + current = greenlet.getcurrent() + if (self._waiters or self._count > 0) and self._holder is not current: + # block until lock is free + self._waiters.append(current) + self._hub.switch() + w = self._waiters.popleft() + + assert w is current, 'Waiting threads woken out of order' + assert self._count == 0, 'After waking a thread, the lock must be unacquired' + + self._holder = current + self._count += 1 + + def release(self): + if self._count <= 0: + raise LockReleaseError("Cannot release unacquired lock") + + self._count -= 1 + if self._count == 0: + self._holder = None + if self._waiters: + # wake next + self._hub.schedule_call_global(0, self._waiters[0].switch) + + +class _BlockedThread(object): + """Is either empty, or represents a single blocked thread that + blocked itself by calling the block() method. The thread can be + awoken by calling wake(). Wake() can be called multiple times and + all but the first call will have no effect.""" + + def __init__(self): + self._blocked_thread = None + self._wakeupper = None + self._hub = eventlet.hubs.get_hub() + + def __nonzero__(self): + return self._blocked_thread is not None + + __bool__ = __nonzero__ + + def block(self, deadline=None): + if self._blocked_thread is not None: + raise Exception("Cannot block more than one thread on one BlockedThread") + self._blocked_thread = greenlet.getcurrent() + + if deadline is not None: + self._hub.schedule_call_local(deadline - self._hub.clock(), self.wake) + + try: + self._hub.switch() + finally: + self._blocked_thread = None + # cleanup the wakeup task + if self._wakeupper is not None: + # Important to cancel the wakeup task so it doesn't + # spuriously wake this greenthread later on. + self._wakeupper.cancel() + self._wakeupper = None + + def wake(self): + """Schedules the blocked thread to be awoken and return + True. If wake has already been called or if there is no + blocked thread, then this call has no effect and returns + False.""" + if self._blocked_thread is not None and self._wakeupper is None: + self._wakeupper = self._hub.schedule_call_global(0, self._blocked_thread.switch) + return True + return False + + +class Context(__zmq__.Context): + """Subclass of :class:`zmq.Context` + """ + + def socket(self, socket_type): + """Overridden method to ensure that the green version of socket is used + + Behaves the same as :meth:`zmq.Context.socket`, but ensures + that a :class:`Socket` with all of its send and recv methods set to be + non-blocking is returned + """ + if self.closed: + raise ZMQError(ENOTSUP) + return Socket(self, socket_type) + + +def _wraps(source_fn): + """A decorator that copies the __name__ and __doc__ from the given + function + """ + def wrapper(dest_fn): + dest_fn.__name__ = source_fn.__name__ + dest_fn.__doc__ = source_fn.__doc__ + return dest_fn + return wrapper + +# Implementation notes: Each socket in 0mq contains a pipe that the +# background IO threads use to communicate with the socket. These +# events are important because they tell the socket when it is able to +# send and when it has messages waiting to be received. The read end +# of the events pipe is the same FD that getsockopt(zmq.FD) returns. +# +# Events are read from the socket's event pipe only on the thread that +# the 0mq context is associated with, which is the native thread the +# greenthreads are running on, and the only operations that cause the +# events to be read and processed are send(), recv() and +# getsockopt(zmq.EVENTS). This means that after doing any of these +# three operations, the ability of the socket to send or receive a +# message without blocking may have changed, but after the events are +# read the FD is no longer readable so the hub may not signal our +# listener. +# +# If we understand that after calling send() a message might be ready +# to be received and that after calling recv() a message might be able +# to be sent, what should we do next? There are two approaches: +# +# 1. Always wake the other thread if there is one waiting. This +# wakeup may be spurious because the socket might not actually be +# ready for a send() or recv(). However, if a thread is in a +# tight-loop successfully calling send() or recv() then the wakeups +# are naturally batched and there's very little cost added to each +# send/recv call. +# +# or +# +# 2. Call getsockopt(zmq.EVENTS) and explicitly check if the other +# thread should be woken up. This avoids spurious wake-ups but may +# add overhead because getsockopt will cause all events to be +# processed, whereas send and recv throttle processing +# events. Admittedly, all of the events will need to be processed +# eventually, but it is likely faster to batch the processing. +# +# Which approach is better? I have no idea. +# +# TODO: +# - Support MessageTrackers and make MessageTracker.wait green + +_Socket = __zmq__.Socket +_Socket_recv = _Socket.recv +_Socket_send = _Socket.send +_Socket_send_multipart = _Socket.send_multipart +_Socket_recv_multipart = _Socket.recv_multipart +_Socket_send_string = _Socket.send_string +_Socket_recv_string = _Socket.recv_string +_Socket_send_pyobj = _Socket.send_pyobj +_Socket_recv_pyobj = _Socket.recv_pyobj +_Socket_send_json = _Socket.send_json +_Socket_recv_json = _Socket.recv_json +_Socket_getsockopt = _Socket.getsockopt + + +class Socket(_Socket): + """Green version of :class:`zmq.core.socket.Socket + + The following three methods are always overridden: + * send + * recv + * getsockopt + To ensure that the ``zmq.NOBLOCK`` flag is set and that sending or receiving + is deferred to the hub (using :func:`eventlet.hubs.trampoline`) if a + ``zmq.EAGAIN`` (retry) error is raised + + For some socket types, the following methods are also overridden: + * send_multipart + * recv_multipart + """ + + def __init__(self, context, socket_type): + super(Socket, self).__init__(context, socket_type) + + self.__dict__['_eventlet_send_event'] = _BlockedThread() + self.__dict__['_eventlet_recv_event'] = _BlockedThread() + self.__dict__['_eventlet_send_lock'] = _QueueLock() + self.__dict__['_eventlet_recv_lock'] = _QueueLock() + + def event(fd): + # Some events arrived at the zmq socket. This may mean + # there's a message that can be read or there's space for + # a message to be written. + send_wake = self._eventlet_send_event.wake() + recv_wake = self._eventlet_recv_event.wake() + if not send_wake and not recv_wake: + # if no waiting send or recv thread was woken up, then + # force the zmq socket's events to be processed to + # avoid repeated wakeups + _Socket_getsockopt(self, EVENTS) + + hub = eventlet.hubs.get_hub() + self.__dict__['_eventlet_listener'] = hub.add(hub.READ, + self.getsockopt(FD), + event, + lambda _: None, + lambda: None) + self.__dict__['_eventlet_clock'] = hub.clock + + @_wraps(_Socket.close) + def close(self, linger=None): + super(Socket, self).close(linger) + if self._eventlet_listener is not None: + eventlet.hubs.get_hub().remove(self._eventlet_listener) + self.__dict__['_eventlet_listener'] = None + # wake any blocked threads + self._eventlet_send_event.wake() + self._eventlet_recv_event.wake() + + @_wraps(_Socket.getsockopt) + def getsockopt(self, option): + result = _Socket_getsockopt(self, option) + if option == EVENTS: + # Getting the events causes the zmq socket to process + # events which may mean a msg can be sent or received. If + # there is a greenthread blocked and waiting for events, + # it will miss the edge-triggered read event, so wake it + # up. + if (result & POLLOUT): + self._eventlet_send_event.wake() + if (result & POLLIN): + self._eventlet_recv_event.wake() + return result + + @_wraps(_Socket.send) + def send(self, msg, flags=0, copy=True, track=False): + """A send method that's safe to use when multiple greenthreads + are calling send, send_multipart, recv and recv_multipart on + the same socket. + """ + if flags & NOBLOCK: + result = _Socket_send(self, msg, flags, copy, track) + # Instead of calling both wake methods, could call + # self.getsockopt(EVENTS) which would trigger wakeups if + # needed. + self._eventlet_send_event.wake() + self._eventlet_recv_event.wake() + return result + + # TODO: pyzmq will copy the message buffer and create Message + # objects under some circumstances. We could do that work here + # once to avoid doing it every time the send is retried. + flags |= NOBLOCK + with self._eventlet_send_lock: + while True: + try: + return _Socket_send(self, msg, flags, copy, track) + except ZMQError as e: + if e.errno == EAGAIN: + self._eventlet_send_event.block() + else: + raise + finally: + # The call to send processes 0mq events and may + # make the socket ready to recv. Wake the next + # receiver. (Could check EVENTS for POLLIN here) + self._eventlet_recv_event.wake() + + @_wraps(_Socket.send_multipart) + def send_multipart(self, msg_parts, flags=0, copy=True, track=False): + """A send_multipart method that's safe to use when multiple + greenthreads are calling send, send_multipart, recv and + recv_multipart on the same socket. + """ + if flags & NOBLOCK: + return _Socket_send_multipart(self, msg_parts, flags, copy, track) + + # acquire lock here so the subsequent calls to send for the + # message parts after the first don't block + with self._eventlet_send_lock: + return _Socket_send_multipart(self, msg_parts, flags, copy, track) + + @_wraps(_Socket.send_string) + def send_string(self, u, flags=0, copy=True, encoding='utf-8'): + """A send_string method that's safe to use when multiple + greenthreads are calling send, send_string, recv and + recv_string on the same socket. + """ + if flags & NOBLOCK: + return _Socket_send_string(self, u, flags, copy, encoding) + + # acquire lock here so the subsequent calls to send for the + # message parts after the first don't block + with self._eventlet_send_lock: + return _Socket_send_string(self, u, flags, copy, encoding) + + @_wraps(_Socket.send_pyobj) + def send_pyobj(self, obj, flags=0, protocol=2): + """A send_pyobj method that's safe to use when multiple + greenthreads are calling send, send_pyobj, recv and + recv_pyobj on the same socket. + """ + if flags & NOBLOCK: + return _Socket_send_pyobj(self, obj, flags, protocol) + + # acquire lock here so the subsequent calls to send for the + # message parts after the first don't block + with self._eventlet_send_lock: + return _Socket_send_pyobj(self, obj, flags, protocol) + + @_wraps(_Socket.send_json) + def send_json(self, obj, flags=0, **kwargs): + """A send_json method that's safe to use when multiple + greenthreads are calling send, send_json, recv and + recv_json on the same socket. + """ + if flags & NOBLOCK: + return _Socket_send_json(self, obj, flags, **kwargs) + + # acquire lock here so the subsequent calls to send for the + # message parts after the first don't block + with self._eventlet_send_lock: + return _Socket_send_json(self, obj, flags, **kwargs) + + @_wraps(_Socket.recv) + def recv(self, flags=0, copy=True, track=False): + """A recv method that's safe to use when multiple greenthreads + are calling send, send_multipart, recv and recv_multipart on + the same socket. + """ + if flags & NOBLOCK: + msg = _Socket_recv(self, flags, copy, track) + # Instead of calling both wake methods, could call + # self.getsockopt(EVENTS) which would trigger wakeups if + # needed. + self._eventlet_send_event.wake() + self._eventlet_recv_event.wake() + return msg + + deadline = None + if hasattr(__zmq__, 'RCVTIMEO'): + sock_timeout = self.getsockopt(__zmq__.RCVTIMEO) + if sock_timeout == -1: + pass + elif sock_timeout > 0: + deadline = self._eventlet_clock() + sock_timeout / 1000.0 + else: + raise ValueError(sock_timeout) + + flags |= NOBLOCK + with self._eventlet_recv_lock: + while True: + try: + return _Socket_recv(self, flags, copy, track) + except ZMQError as e: + if e.errno == EAGAIN: + # zmq in its wisdom decided to reuse EAGAIN for timeouts + if deadline is not None and self._eventlet_clock() > deadline: + e.is_timeout = True + raise + + self._eventlet_recv_event.block(deadline=deadline) + else: + raise + finally: + # The call to recv processes 0mq events and may + # make the socket ready to send. Wake the next + # receiver. (Could check EVENTS for POLLOUT here) + self._eventlet_send_event.wake() + + @_wraps(_Socket.recv_multipart) + def recv_multipart(self, flags=0, copy=True, track=False): + """A recv_multipart method that's safe to use when multiple + greenthreads are calling send, send_multipart, recv and + recv_multipart on the same socket. + """ + if flags & NOBLOCK: + return _Socket_recv_multipart(self, flags, copy, track) + + # acquire lock here so the subsequent calls to recv for the + # message parts after the first don't block + with self._eventlet_recv_lock: + return _Socket_recv_multipart(self, flags, copy, track) + + @_wraps(_Socket.recv_string) + def recv_string(self, flags=0, encoding='utf-8'): + """A recv_string method that's safe to use when multiple + greenthreads are calling send, send_string, recv and + recv_string on the same socket. + """ + if flags & NOBLOCK: + return _Socket_recv_string(self, flags, encoding) + + # acquire lock here so the subsequent calls to recv for the + # message parts after the first don't block + with self._eventlet_recv_lock: + return _Socket_recv_string(self, flags, encoding) + + @_wraps(_Socket.recv_json) + def recv_json(self, flags=0, **kwargs): + """A recv_json method that's safe to use when multiple + greenthreads are calling send, send_json, recv and + recv_json on the same socket. + """ + if flags & NOBLOCK: + return _Socket_recv_json(self, flags, **kwargs) + + # acquire lock here so the subsequent calls to recv for the + # message parts after the first don't block + with self._eventlet_recv_lock: + return _Socket_recv_json(self, flags, **kwargs) + + @_wraps(_Socket.recv_pyobj) + def recv_pyobj(self, flags=0): + """A recv_pyobj method that's safe to use when multiple + greenthreads are calling send, send_pyobj, recv and + recv_pyobj on the same socket. + """ + if flags & NOBLOCK: + return _Socket_recv_pyobj(self, flags) + + # acquire lock here so the subsequent calls to recv for the + # message parts after the first don't block + with self._eventlet_recv_lock: + return _Socket_recv_pyobj(self, flags) diff --git a/venv/lib/python3.12/site-packages/eventlet/greenio/__init__.py b/venv/lib/python3.12/site-packages/eventlet/greenio/__init__.py new file mode 100644 index 0000000..f6c5247 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/greenio/__init__.py @@ -0,0 +1,8 @@ +import six + +from eventlet.greenio.base import * # noqa + +if six.PY2: + from eventlet.greenio.py2 import * # noqa +else: + from eventlet.greenio.py3 import * # noqa diff --git a/venv/lib/python3.12/site-packages/eventlet/greenio/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/greenio/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a17034f716207303b37c78b87f07377b5e9daa66 GIT binary patch literal 345 zcmX@j%ge<81ZNK2%UB7dAA>kBzyxJ{b^$V`Go&!2Fy=5sfoP^2#97IQ$P(Jc`Sxq?dUqQ)y3K7-8u6{w$)pPQGw;(Y&J25@ASRZDOKGY5R@$s2?nI-Y@dIgogIBatBQ%ZAE s?TUDSHZTHlu>_F#z|6?Vc$0B@yV@&Et; literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/greenio/__pycache__/base.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/greenio/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..380df6edcd979b24b7cd6440691df9f76066c777 GIT binary patch literal 22430 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 Kza#jhJM_Qir}i2E literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/greenio/__pycache__/py3.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/greenio/__pycache__/py3.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..edfe4f30bb8d3737b214456ed1a2500c55b807d7 GIT binary patch literal 9814 zcmbtaYfv0lcE0_Z_b@Mp;Uxq}AVx1NSxAv-bqWS|>q&^*}gMhJGW zDMd+;OKYJhiBL9G#_LU4D!C$6vZ>OaBu0Ods_c&$qYbldl5#5kv;VLlC6)XmIp_BD zJOJ5Sn=5hqKF)og^PO|%Y}IQ6+$NXvN3bq60*i^A$!~ra>mO-WpP)?9ruL147TTDzIb`4oTmAhKVA{4 zhzCM}cx9+EUKOgM*N5t9nu#@p8bq^bIm?C` zCDUk+G3CNHw38*T6482@h&IW4m(y!PO)Qa`;cwU79O+$H8-`Ge)G|7tr;B5xm#mUg zvP*8MN#Z}X-!Wisl}K}=mjgW6451=U1mZDq=0lFe9&k)$pqcM#!O$(q?L7! zkBmsNB)$$D|;yRfUs45W$i#{2z`b6-f+_L}QZXJS$65 zVk|l)g~jBs=29dzoJb~yV#(q2(ZpHJHPGL6x*Ntl1K-}hSD`mYBB@44CiQMKXD$aL zBqUL@^!9hr?lqe#N8)42STuoCvYv&GC0R}+H9H+SIW#H_tD5yy9RGC~f7sY4{N~P< zto;-+H;F_g0i>W@WMOAHi5I!c#NeusAo7<9$bB|SaDh#si*i(a6u5Pc8_;A8`u zoi3yY_O!RZZ&)a5i5!d)v38kCwwjXxLQ;%stdkfVBs){gv>-AmVG7!%NH|ZCkV;zS zI&cDQB{{wKI%47g)g-C6>>(( zI2S`cU_FRO`86^`^&7PB2?iBIQUZ)zm@5mp{Om-`IgM88HY1fHh4v!uSAM>eWCuAw z6lQ{D$lHX0e^=Wta&MCfCdlw3?b!<{SmrT64|G>dk1u$gaSY-HoW>Ufyq%?tp=nk0F{ND|Y{!_}5*J?DbiH)of3; zs&V1_nW{arCx3kEE8n(x@n>DjI}c@c9(v?!OFP=qzP1$)G?+iRV44bKH6(5V z(~QD*V;CTJn7%uVyb~(m(+nR48`jHvAl6L!Sx$M2w@NQNgrqou8Sq~_s_=!`=b@lbDi%S@mH?6K6BIlOR=(l$_TqWv zJBVr+E@UT>k4-abUPx1nA)0ELF~C+Javoq-wuoHuJQB<$5L%H;Gw(9b8(XiPU?xaT ztoc3}(Q>dSXqB5FA#aCB6JjW6awDcK1Kr)H!(DH_p_zO8yJa~k%U(*OYbI0o!0WxQ zci&;@achjEV1MLBj&T&r(ZJ<#Bp5`d|E(ArT;7Hh4Y z+quQ2rp{{aVYLA15oUMMf#p`DH74xj+U80ISBx&zL>!WE`{pYT>g|6F&?j5^xEWNWWZE2Iy zsM>lFXsCV1BGAGGrr&xIxkUmqZe0(HeEF&njRo7unIA4otifos#5Z zK%}7Lf;B*u676%!U96FC$bO`of`|gs+`;#cWqpC0?bq9HoJczwzAW>uvV!yAN?^xw zpd}M%xm|v*_D=16bt%xEcDFxW@l`)29MtC!=)H{Z@Je;V!huf?e{?uqy*pjLJL@iA zcGqXz^$X>E|G%A6jN9kmuy>*<2R8UWjT zG;fI(D8YKp%>}H<*MR-D#v~Z-Wvk2AG3-5 zxOcg$-N1hEEwCRj-yoG#`FEg?pko8W$+$HdKbnjtG!wdx2~j=`MN}0Q_)kUzkYB?d zc%ALIq45z-7(O?iIIlAw?mIAFj{8nQZpycj`y#Q}??Z)h4I*H`GWRT(MV7MwOI7Y% zt_)@>gSU6y+k0p4qnA!ERi2p>vcAgsu0Lzf*3^CY(yf;kx_)vzUEY#)`)=B<+veLI zxf@pPz*0}w9aK5~KUh<#g*pB4(GCy!XAeY+9xG_b#p=VIe(s@Pfb?)NyWwJ~p43%L zu`^~IHLw^NMVbwvz&6udQM){gO!Fx&1y?;Oz6SmVhz{7S#(^_Ig*qtYERrEj7#|C( zNsSjHYDBXQO{$U-Mk!ZNc<$U>#w`Xl=UNf_MW{=I5WnEQiOVwsYl;4{<=f&UQmEbc3;* z6yNh=3<4Ga>oPC4AfUDl9Y7YvXQN%H@1{w&22D`~-6X2^bgoCN)p`9Wq>3#*`5h=I z)s1@VBoZEnXojq^oOQ>FZ~L;ZIpb?yP(S(6M?d;_dWkwdPgh(Oj|l_y-m3WnA0EDS zIPGmrI~u=P<)HAHg5rOvv5EYRwL{?k*4sh2MIu0QIKOBhKm|wH?x&D=o|_=s3QmHt zjh6sB)%-T2=MPNAqmR9n!|*9%+_cec&oe%3JKQEh8}vIj1Y;zITq1cRob3Z6JP zQ17>Wwkx~4ExYf)^1hDDzK(T)E$?E`#q469uA7Lv5{z@Jchy9ko@GaU#!>&s(U^63 zR;^I_OhI0quYQsIeN9JMsiMQ}rH6{%hieX{mrCx!%I7zLK zDD^`UTt(FDZf5;ppWF&9Hd%B-P&idnvhdQa{39TslLH4ePy?>BW3yd{yGdubo2uf1 zJQtKCSX)RzLs2OwGJt1*Xy;*-a%(XYZpC=OT6r7J=na%Qn}VzE>im__w1BKT1;$ZP z-^|rkM9WP+sHw;YH5q79@Ik2>MjoR9uCVYBHUz|x!X?#~mY?Ze)72ip8u*r|Ufl-; za~t_HXxD6UDV~%kFGQsY`AsO&wJ~MhM$A4}_2LwRW}WMQ57HWseQ0Jxeozc>H8e5; z*R`4%cj}n$g?pMC!C7b;rjy|)MNy>sWC2Zug0eP(`ee5ty3&J6CSCosP&<8GKU zt$K*dKR2-i&Ah~2G3$J~?#wCbecxh3uDGk`4=t$6?!BM8_fpewpYFnbxTC{G7G2g( zFSl6vLZ_2^=oBDbVmIcQ^&zb1c^IiQqib6bvjX#OWj}6>k_I!u4Q4?RZHcD^N|8ND z{s62#P+oSJ9t|nH`_0>q#(M+UlzyH}q8mLJbW;_j2_vy_P z(|EtGSy9PvZ#cJ-G~te{(h{x95+ zy`Rd~Y@a=O^~{Q+a@kRran#MLAEs`l9y#`|Sjy)HZ@zQ=okx~N(6xcr7=+y8vpsM( z=q#T*Ip6%K@#SAS+P?Ajm<9>pjNi~bMZe>>;g5oIN=?B$kHFx$PykBM zs_Z843V?x#9v}_iA>=R4jIz9mQ)=q!3iDbS$RvccrZS@v=t zRB5byVJox#hfr5)LmJ(F1U1SKL?w>p(d$QVygYkyl{Hx_Ry^C5J-afVUAOsrjysO~ z;*#g(+0IosI?uJs&KUoRi>5VyiR3qbzmwQR+X1Qsothip|odjIU`y{3P~K?0(OKo+V#b+R^o0hAAq< zOZ7X*$JP#x`_y}a`zHYsC6J5uJryj(UWDA7*K%i*JpwNsVR&#rteNr`tD$`H-$57j zU4z2GtbMb8!2$&;wzZwP(Z20&i2(>TuDlR{asWkb{9<%ML4fp1Qp2m)ir~ zgEE!$8LYU3(R&yj#Apvj6-Lwu31tGJ%Vdpp33aQD#8mb*%Luh=9Hdq;_1NSP>K@lJ z!r{jzMmWu2T)Sqn2?rln+99eIgzCp-ys%@<;Sju!YguSwaS9cW>#V}@wF;|Hv06cP zv@SOu$uu5WBS84r!3gDR(2ZZ86HX^+l98EYlwO&~2v1ux>CdSe3-4=A6_peSL=wE( zvv4gIo}lmEVmLfpY(Sz)qKx@-NFV@!CP!x75boyk)0XeRd5fXRr!C**1*I(0q*TY} zeJ)eVd^a~s3Mly*gMY>0RpQlch6iN;1?2+Nzj8fBc-UH52WZ{s(6}nm8$)GsNdxlqz__*!%$~H_nnOU0~wuD8vOzZ z!Vm$f@MfVR>+#>Lxn6T)+pGz6u(xj6(~$8rEHp29f?zW0f&wpfF8?h5mEAp8KBvyV zecN)s`N6Kwj{IxSl6@e}4?JCW5eM8WGJ^A076-V)g7cYz_UCZ7J3vN{lRu=3ea_JVeZ_%G>(AzbgeJk?am;6ve#={5y))-s=*@h<27iJl42F-80 z(e6KsKaYqe_Yc7@JE*xOBO=i!WO`MnIrF6F#)lLc4F>xBT;xi-^CZgn*jQ3l^_#vL z9LXFG!wnspfdkab?CqEFhLVB=Trx%BCmQCnvE)!Brf43@Z6JdeqH;132T<2c<742C zfRleNg7+%2{66gze(XVW_)HI2h{r?QY99O<$Vl$jDPfRhN&WzbLvF*lz;93hBTE76 znWbIF(i1XjI22|D-t-Bu4a0mv>b@Y3FG$rFq~Qr`X4oeLqW^IdX8RLfVAwSR(Gv$@ z&M-^l^nZ}%FNp0+d*%F|w5@r@44%7d`*PW?Oxdm(`?_P0W&Crk>jb}FCwStox)u0I z)s6l%@nnf-nN(&-<*o9pZ+Gs$w qm-wbE?_U!*D?1~Ach$wb%483;t#Ni{kXfxJ{>B;WFNuc&=>Gu%6$#e> literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/greenio/base.py b/venv/lib/python3.12/site-packages/eventlet/greenio/base.py new file mode 100644 index 0000000..51a7ae1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/greenio/base.py @@ -0,0 +1,510 @@ +import errno +import os +import socket +import sys +import time +import warnings + +import eventlet +from eventlet.hubs import trampoline, notify_opened, IOClosed +from eventlet.support import get_errno +import six + +__all__ = [ + 'GreenSocket', '_GLOBAL_DEFAULT_TIMEOUT', 'set_nonblocking', + 'SOCKET_BLOCKING', 'SOCKET_CLOSED', 'CONNECT_ERR', 'CONNECT_SUCCESS', + 'shutdown_safe', 'SSL', + 'socket_timeout', +] + +BUFFER_SIZE = 4096 +CONNECT_ERR = set((errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK)) +CONNECT_SUCCESS = set((0, errno.EISCONN)) +if sys.platform[:3] == "win": + CONNECT_ERR.add(errno.WSAEINVAL) # Bug 67 + +if six.PY2: + _python2_fileobject = socket._fileobject + +_original_socket = eventlet.patcher.original('socket').socket + + +if sys.version_info >= (3, 10): + socket_timeout = socket.timeout # Really, TimeoutError +else: + socket_timeout = eventlet.timeout.wrap_is_timeout(socket.timeout) + + +def socket_connect(descriptor, address): + """ + Attempts to connect to the address, returns the descriptor if it succeeds, + returns None if it needs to trampoline, and raises any exceptions. + """ + err = descriptor.connect_ex(address) + if err in CONNECT_ERR: + return None + if err not in CONNECT_SUCCESS: + raise socket.error(err, errno.errorcode[err]) + return descriptor + + +def socket_checkerr(descriptor): + err = descriptor.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) + if err not in CONNECT_SUCCESS: + raise socket.error(err, errno.errorcode[err]) + + +def socket_accept(descriptor): + """ + Attempts to accept() on the descriptor, returns a client,address tuple + if it succeeds; returns None if it needs to trampoline, and raises + any exceptions. + """ + try: + return descriptor.accept() + except socket.error as e: + if get_errno(e) == errno.EWOULDBLOCK: + return None + raise + + +if sys.platform[:3] == "win": + # winsock sometimes throws ENOTCONN + SOCKET_BLOCKING = set((errno.EAGAIN, errno.EWOULDBLOCK,)) + SOCKET_CLOSED = set((errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN)) +else: + # oddly, on linux/darwin, an unconnected socket is expected to block, + # so we treat ENOTCONN the same as EWOULDBLOCK + SOCKET_BLOCKING = set((errno.EAGAIN, errno.EWOULDBLOCK, errno.ENOTCONN)) + SOCKET_CLOSED = set((errno.ECONNRESET, errno.ESHUTDOWN, errno.EPIPE)) + + +def set_nonblocking(fd): + """ + Sets the descriptor to be nonblocking. Works on many file-like + objects as well as sockets. Only sockets can be nonblocking on + Windows, however. + """ + try: + setblocking = fd.setblocking + except AttributeError: + # fd has no setblocking() method. It could be that this version of + # Python predates socket.setblocking(). In that case, we can still set + # the flag "by hand" on the underlying OS fileno using the fcntl + # module. + try: + import fcntl + except ImportError: + # Whoops, Windows has no fcntl module. This might not be a socket + # at all, but rather a file-like object with no setblocking() + # method. In particular, on Windows, pipes don't support + # non-blocking I/O and therefore don't have that method. Which + # means fcntl wouldn't help even if we could load it. + raise NotImplementedError("set_nonblocking() on a file object " + "with no setblocking() method " + "(Windows pipes don't support non-blocking I/O)") + # We managed to import fcntl. + fileno = fd.fileno() + orig_flags = fcntl.fcntl(fileno, fcntl.F_GETFL) + new_flags = orig_flags | os.O_NONBLOCK + if new_flags != orig_flags: + fcntl.fcntl(fileno, fcntl.F_SETFL, new_flags) + else: + # socket supports setblocking() + setblocking(0) + + +try: + from socket import _GLOBAL_DEFAULT_TIMEOUT +except ImportError: + _GLOBAL_DEFAULT_TIMEOUT = object() + + +class GreenSocket(object): + """ + Green version of socket.socket class, that is intended to be 100% + API-compatible. + + It also recognizes the keyword parameter, 'set_nonblocking=True'. + Pass False to indicate that socket is already in non-blocking mode + to save syscalls. + """ + + # This placeholder is to prevent __getattr__ from creating an infinite call loop + fd = None + + def __init__(self, family=socket.AF_INET, *args, **kwargs): + should_set_nonblocking = kwargs.pop('set_nonblocking', True) + if isinstance(family, six.integer_types): + fd = _original_socket(family, *args, **kwargs) + # Notify the hub that this is a newly-opened socket. + notify_opened(fd.fileno()) + else: + fd = family + + # import timeout from other socket, if it was there + try: + self._timeout = fd.gettimeout() or socket.getdefaulttimeout() + except AttributeError: + self._timeout = socket.getdefaulttimeout() + + # Filter fd.fileno() != -1 so that won't call set non-blocking on + # closed socket + if should_set_nonblocking and fd.fileno() != -1: + set_nonblocking(fd) + self.fd = fd + # when client calls setblocking(0) or settimeout(0) the socket must + # act non-blocking + self.act_non_blocking = False + + # Copy some attributes from underlying real socket. + # This is the easiest way that i found to fix + # https://bitbucket.org/eventlet/eventlet/issue/136 + # Only `getsockopt` is required to fix that issue, others + # are just premature optimization to save __getattr__ call. + self.bind = fd.bind + self.close = fd.close + self.fileno = fd.fileno + self.getsockname = fd.getsockname + self.getsockopt = fd.getsockopt + self.listen = fd.listen + self.setsockopt = fd.setsockopt + self.shutdown = fd.shutdown + self._closed = False + + @property + def _sock(self): + return self + + if six.PY3: + def _get_io_refs(self): + return self.fd._io_refs + + def _set_io_refs(self, value): + self.fd._io_refs = value + + _io_refs = property(_get_io_refs, _set_io_refs) + + # Forward unknown attributes to fd, cache the value for future use. + # I do not see any simple attribute which could be changed + # so caching everything in self is fine. + # If we find such attributes - only attributes having __get__ might be cached. + # For now - I do not want to complicate it. + def __getattr__(self, name): + if self.fd is None: + raise AttributeError(name) + attr = getattr(self.fd, name) + setattr(self, name, attr) + return attr + + def _trampoline(self, fd, read=False, write=False, timeout=None, timeout_exc=None): + """ We need to trampoline via the event hub. + We catch any signal back from the hub indicating that the operation we + were waiting on was associated with a filehandle that's since been + invalidated. + """ + if self._closed: + # If we did any logging, alerting to a second trampoline attempt on a closed + # socket here would be useful. + raise IOClosed() + try: + return trampoline(fd, read=read, write=write, timeout=timeout, + timeout_exc=timeout_exc, + mark_as_closed=self._mark_as_closed) + except IOClosed: + # This socket's been obsoleted. De-fang it. + self._mark_as_closed() + raise + + def accept(self): + if self.act_non_blocking: + res = self.fd.accept() + notify_opened(res[0].fileno()) + return res + fd = self.fd + _timeout_exc = socket_timeout('timed out') + while True: + res = socket_accept(fd) + if res is not None: + client, addr = res + notify_opened(client.fileno()) + set_nonblocking(client) + return type(self)(client), addr + self._trampoline(fd, read=True, timeout=self.gettimeout(), timeout_exc=_timeout_exc) + + def _mark_as_closed(self): + """ Mark this socket as being closed """ + self._closed = True + + def __del__(self): + # This is in case self.close is not assigned yet (currently the constructor does it) + close = getattr(self, 'close', None) + if close is not None: + close() + + def connect(self, address): + if self.act_non_blocking: + return self.fd.connect(address) + fd = self.fd + _timeout_exc = socket_timeout('timed out') + if self.gettimeout() is None: + while not socket_connect(fd, address): + try: + self._trampoline(fd, write=True) + except IOClosed: + raise socket.error(errno.EBADFD) + socket_checkerr(fd) + else: + end = time.time() + self.gettimeout() + while True: + if socket_connect(fd, address): + return + if time.time() >= end: + raise _timeout_exc + timeout = end - time.time() + try: + self._trampoline(fd, write=True, timeout=timeout, timeout_exc=_timeout_exc) + except IOClosed: + # ... we need some workable errno here. + raise socket.error(errno.EBADFD) + socket_checkerr(fd) + + def connect_ex(self, address): + if self.act_non_blocking: + return self.fd.connect_ex(address) + fd = self.fd + if self.gettimeout() is None: + while not socket_connect(fd, address): + try: + self._trampoline(fd, write=True) + socket_checkerr(fd) + except socket.error as ex: + return get_errno(ex) + except IOClosed: + return errno.EBADFD + return 0 + else: + end = time.time() + self.gettimeout() + timeout_exc = socket.timeout(errno.EAGAIN) + while True: + try: + if socket_connect(fd, address): + return 0 + if time.time() >= end: + raise timeout_exc + self._trampoline(fd, write=True, timeout=end - time.time(), + timeout_exc=timeout_exc) + socket_checkerr(fd) + except socket.error as ex: + return get_errno(ex) + except IOClosed: + return errno.EBADFD + return 0 + + def dup(self, *args, **kw): + sock = self.fd.dup(*args, **kw) + newsock = type(self)(sock, set_nonblocking=False) + newsock.settimeout(self.gettimeout()) + return newsock + + if six.PY3: + def makefile(self, *args, **kwargs): + return _original_socket.makefile(self, *args, **kwargs) + else: + def makefile(self, *args, **kwargs): + dupped = self.dup() + res = _python2_fileobject(dupped, *args, **kwargs) + if hasattr(dupped, "_drop"): + dupped._drop() + # Making the close function of dupped None so that when garbage collector + # kicks in and tries to call del, which will ultimately call close, _drop + # doesn't get called on dupped twice as it has been already explicitly called in + # previous line + dupped.close = None + return res + + def makeGreenFile(self, *args, **kw): + warnings.warn("makeGreenFile has been deprecated, please use " + "makefile instead", DeprecationWarning, stacklevel=2) + return self.makefile(*args, **kw) + + def _read_trampoline(self): + self._trampoline( + self.fd, + read=True, + timeout=self.gettimeout(), + timeout_exc=socket_timeout('timed out')) + + def _recv_loop(self, recv_meth, empty_val, *args): + if self.act_non_blocking: + return recv_meth(*args) + + while True: + try: + # recv: bufsize=0? + # recv_into: buffer is empty? + # This is needed because behind the scenes we use sockets in + # nonblocking mode and builtin recv* methods. Attempting to read + # 0 bytes from a nonblocking socket using a builtin recv* method + # does not raise a timeout exception. Since we're simulating + # a blocking socket here we need to produce a timeout exception + # if needed, hence the call to trampoline. + if not args[0]: + self._read_trampoline() + return recv_meth(*args) + except socket.error as e: + if get_errno(e) in SOCKET_BLOCKING: + pass + elif get_errno(e) in SOCKET_CLOSED: + return empty_val + else: + raise + + try: + self._read_trampoline() + except IOClosed as e: + # Perhaps we should return '' instead? + raise EOFError() + + def recv(self, bufsize, flags=0): + return self._recv_loop(self.fd.recv, b'', bufsize, flags) + + def recvfrom(self, bufsize, flags=0): + return self._recv_loop(self.fd.recvfrom, b'', bufsize, flags) + + def recv_into(self, buffer, nbytes=0, flags=0): + return self._recv_loop(self.fd.recv_into, 0, buffer, nbytes, flags) + + def recvfrom_into(self, buffer, nbytes=0, flags=0): + return self._recv_loop(self.fd.recvfrom_into, 0, buffer, nbytes, flags) + + def _send_loop(self, send_method, data, *args): + if self.act_non_blocking: + return send_method(data, *args) + + _timeout_exc = socket_timeout('timed out') + while True: + try: + return send_method(data, *args) + except socket.error as e: + eno = get_errno(e) + if eno == errno.ENOTCONN or eno not in SOCKET_BLOCKING: + raise + + try: + self._trampoline(self.fd, write=True, timeout=self.gettimeout(), + timeout_exc=_timeout_exc) + except IOClosed: + raise socket.error(errno.ECONNRESET, 'Connection closed by another thread') + + def send(self, data, flags=0): + return self._send_loop(self.fd.send, data, flags) + + def sendto(self, data, *args): + return self._send_loop(self.fd.sendto, data, *args) + + def sendall(self, data, flags=0): + tail = self.send(data, flags) + len_data = len(data) + while tail < len_data: + tail += self.send(data[tail:], flags) + + def setblocking(self, flag): + if flag: + self.act_non_blocking = False + self._timeout = None + else: + self.act_non_blocking = True + self._timeout = 0.0 + + def settimeout(self, howlong): + if howlong is None or howlong == _GLOBAL_DEFAULT_TIMEOUT: + self.setblocking(True) + return + try: + f = howlong.__float__ + except AttributeError: + raise TypeError('a float is required') + howlong = f() + if howlong < 0.0: + raise ValueError('Timeout value out of range') + if howlong == 0.0: + self.act_non_blocking = True + self._timeout = 0.0 + else: + self.act_non_blocking = False + self._timeout = howlong + + def gettimeout(self): + return self._timeout + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + if "__pypy__" in sys.builtin_module_names: + def _reuse(self): + getattr(self.fd, '_sock', self.fd)._reuse() + + def _drop(self): + getattr(self.fd, '_sock', self.fd)._drop() + + +def _operation_on_closed_file(*args, **kwargs): + raise ValueError("I/O operation on closed file") + + +greenpipe_doc = """ + GreenPipe is a cooperative replacement for file class. + It will cooperate on pipes. It will block on regular file. + Differences from file class: + - mode is r/w property. Should re r/o + - encoding property not implemented + - write/writelines will not raise TypeError exception when non-string data is written + it will write str(data) instead + - Universal new lines are not supported and newlines property not implementeded + - file argument can be descriptor, file name or file object. + """ + +# import SSL module here so we can refer to greenio.SSL.exceptionclass +try: + from OpenSSL import SSL +except ImportError: + # pyOpenSSL not installed, define exceptions anyway for convenience + class SSL(object): + class WantWriteError(Exception): + pass + + class WantReadError(Exception): + pass + + class ZeroReturnError(Exception): + pass + + class SysCallError(Exception): + pass + + +def shutdown_safe(sock): + """Shuts down the socket. This is a convenience method for + code that wants to gracefully handle regular sockets, SSL.Connection + sockets from PyOpenSSL and ssl.SSLSocket objects from Python 2.7 interchangeably. + Both types of ssl socket require a shutdown() before close, + but they have different arity on their shutdown method. + + Regular sockets don't need a shutdown before close, but it doesn't hurt. + """ + try: + try: + # socket, ssl.SSLSocket + return sock.shutdown(socket.SHUT_RDWR) + except TypeError: + # SSL.Connection + return sock.shutdown() + except socket.error as e: + # we don't care if the socket is already closed; + # this will often be the case in an http server context + if get_errno(e) not in (errno.ENOTCONN, errno.EBADF, errno.ENOTSOCK): + raise diff --git a/venv/lib/python3.12/site-packages/eventlet/greenio/py2.py b/venv/lib/python3.12/site-packages/eventlet/greenio/py2.py new file mode 100644 index 0000000..74d2ff5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/greenio/py2.py @@ -0,0 +1,230 @@ +import errno +import os + +from eventlet.greenio.base import ( + _operation_on_closed_file, + greenpipe_doc, + set_nonblocking, + socket, + SOCKET_BLOCKING, +) +from eventlet.hubs import trampoline, notify_close, notify_opened, IOClosed +from eventlet.support import get_errno +import six + +__all__ = ['_fileobject', 'GreenPipe'] + +_fileobject = socket._fileobject + + +class GreenPipe(_fileobject): + + __doc__ = greenpipe_doc + + def __init__(self, f, mode='r', bufsize=-1): + if not isinstance(f, six.string_types + (int, file)): + raise TypeError('f(ile) should be int, str, unicode or file, not %r' % f) + + if isinstance(f, six.string_types): + f = open(f, mode, 0) + + if isinstance(f, int): + fileno = f + self._name = "" % fileno + else: + fileno = os.dup(f.fileno()) + self._name = f.name + if f.mode != mode: + raise ValueError('file.mode %r does not match mode parameter %r' % (f.mode, mode)) + self._name = f.name + f.close() + + super(GreenPipe, self).__init__(_SocketDuckForFd(fileno), mode, bufsize) + set_nonblocking(self) + self.softspace = 0 + + @property + def name(self): + return self._name + + def __repr__(self): + return "<%s %s %r, mode %r at 0x%x>" % ( + self.closed and 'closed' or 'open', + self.__class__.__name__, + self.name, + self.mode, + (id(self) < 0) and (sys.maxint + id(self)) or id(self)) + + def close(self): + super(GreenPipe, self).close() + for method in [ + 'fileno', 'flush', 'isatty', 'next', 'read', 'readinto', + 'readline', 'readlines', 'seek', 'tell', 'truncate', + 'write', 'xreadlines', '__iter__', '__next__', 'writelines']: + setattr(self, method, _operation_on_closed_file) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def _get_readahead_len(self): + return len(self._rbuf.getvalue()) + + def _clear_readahead_buf(self): + len = self._get_readahead_len() + if len > 0: + self.read(len) + + def tell(self): + self.flush() + try: + return os.lseek(self.fileno(), 0, 1) - self._get_readahead_len() + except OSError as e: + raise IOError(*e.args) + + def seek(self, offset, whence=0): + self.flush() + if whence == 1 and offset == 0: # tell synonym + return self.tell() + if whence == 1: # adjust offset by what is read ahead + offset -= self._get_readahead_len() + try: + rv = os.lseek(self.fileno(), offset, whence) + except OSError as e: + raise IOError(*e.args) + else: + self._clear_readahead_buf() + return rv + + if getattr(file, "truncate", None): # not all OSes implement truncate + def truncate(self, size=-1): + self.flush() + if size == -1: + size = self.tell() + try: + rv = os.ftruncate(self.fileno(), size) + except OSError as e: + raise IOError(*e.args) + else: + self.seek(size) # move position&clear buffer + return rv + + def isatty(self): + try: + return os.isatty(self.fileno()) + except OSError as e: + raise IOError(*e.args) + + +class _SocketDuckForFd(object): + """Class implementing all socket method used by _fileobject + in cooperative manner using low level os I/O calls. + """ + _refcount = 0 + + def __init__(self, fileno): + self._fileno = fileno + notify_opened(fileno) + self._closed = False + + def _trampoline(self, fd, read=False, write=False, timeout=None, timeout_exc=None): + if self._closed: + # Don't trampoline if we're already closed. + raise IOClosed() + try: + return trampoline(fd, read=read, write=write, timeout=timeout, + timeout_exc=timeout_exc, + mark_as_closed=self._mark_as_closed) + except IOClosed: + # Our fileno has been obsoleted. Defang ourselves to + # prevent spurious closes. + self._mark_as_closed() + raise + + def _mark_as_closed(self): + current = self._closed + self._closed = True + return current + + @property + def _sock(self): + return self + + def fileno(self): + return self._fileno + + def recv(self, buflen): + while True: + try: + data = os.read(self._fileno, buflen) + return data + except OSError as e: + if get_errno(e) not in SOCKET_BLOCKING: + raise IOError(*e.args) + self._trampoline(self, read=True) + + def recv_into(self, buf, nbytes=0, flags=0): + if nbytes == 0: + nbytes = len(buf) + data = self.recv(nbytes) + buf[:nbytes] = data + return len(data) + + def send(self, data): + while True: + try: + return os.write(self._fileno, data) + except OSError as e: + if get_errno(e) not in SOCKET_BLOCKING: + raise IOError(*e.args) + else: + trampoline(self, write=True) + + def sendall(self, data): + len_data = len(data) + os_write = os.write + fileno = self._fileno + try: + total_sent = os_write(fileno, data) + except OSError as e: + if get_errno(e) != errno.EAGAIN: + raise IOError(*e.args) + total_sent = 0 + while total_sent < len_data: + self._trampoline(self, write=True) + try: + total_sent += os_write(fileno, data[total_sent:]) + except OSError as e: + if get_errno(e) != errno. EAGAIN: + raise IOError(*e.args) + + def __del__(self): + self._close() + + def _close(self): + was_closed = self._mark_as_closed() + if was_closed: + return + if notify_close: + # If closing from __del__, notify_close may have + # already been cleaned up and set to None + notify_close(self._fileno) + try: + os.close(self._fileno) + except: + # os.close may fail if __init__ didn't complete + # (i.e file dscriptor passed to popen was invalid + pass + + def __repr__(self): + return "%s:%d" % (self.__class__.__name__, self._fileno) + + def _reuse(self): + self._refcount += 1 + + def _drop(self): + self._refcount -= 1 + if self._refcount == 0: + self._close() diff --git a/venv/lib/python3.12/site-packages/eventlet/greenio/py3.py b/venv/lib/python3.12/site-packages/eventlet/greenio/py3.py new file mode 100644 index 0000000..fc6c67f --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/greenio/py3.py @@ -0,0 +1,217 @@ +import _pyio as _original_pyio +import errno +import os as _original_os +import socket as _original_socket +from io import ( + BufferedRandom as _OriginalBufferedRandom, + BufferedReader as _OriginalBufferedReader, + BufferedWriter as _OriginalBufferedWriter, + DEFAULT_BUFFER_SIZE, + TextIOWrapper as _OriginalTextIOWrapper, + IOBase as _OriginalIOBase, +) +from types import FunctionType + +from eventlet.greenio.base import ( + _operation_on_closed_file, + greenpipe_doc, + set_nonblocking, + SOCKET_BLOCKING, +) +from eventlet.hubs import notify_close, notify_opened, IOClosed, trampoline +from eventlet.support import get_errno +import six + +__all__ = ['_fileobject', 'GreenPipe'] + +# TODO get rid of this, it only seems like the original _fileobject +_fileobject = _original_socket.SocketIO + +# Large part of the following code is copied from the original +# eventlet.greenio module + + +class GreenFileIO(_OriginalIOBase): + def __init__(self, name, mode='r', closefd=True, opener=None): + if isinstance(name, int): + fileno = name + self._name = "" % fileno + else: + assert isinstance(name, six.string_types) + with open(name, mode) as fd: + self._name = fd.name + fileno = _original_os.dup(fd.fileno()) + + notify_opened(fileno) + self._fileno = fileno + self._mode = mode + self._closed = False + set_nonblocking(self) + self._seekable = None + + @property + def closed(self): + return self._closed + + def seekable(self): + if self._seekable is None: + try: + _original_os.lseek(self._fileno, 0, _original_os.SEEK_CUR) + except IOError as e: + if get_errno(e) == errno.ESPIPE: + self._seekable = False + else: + raise + else: + self._seekable = True + + return self._seekable + + def readable(self): + return 'r' in self._mode or '+' in self._mode + + def writable(self): + return 'w' in self._mode or '+' in self._mode or 'a' in self._mode + + def fileno(self): + return self._fileno + + def read(self, size=-1): + if size == -1: + return self.readall() + + while True: + try: + return _original_os.read(self._fileno, size) + except OSError as e: + if get_errno(e) not in SOCKET_BLOCKING: + raise IOError(*e.args) + self._trampoline(self, read=True) + + def readall(self): + buf = [] + while True: + try: + chunk = _original_os.read(self._fileno, DEFAULT_BUFFER_SIZE) + if chunk == b'': + return b''.join(buf) + buf.append(chunk) + except OSError as e: + if get_errno(e) not in SOCKET_BLOCKING: + raise IOError(*e.args) + self._trampoline(self, read=True) + + def readinto(self, b): + up_to = len(b) + data = self.read(up_to) + bytes_read = len(data) + b[:bytes_read] = data + return bytes_read + + def isatty(self): + try: + return _original_os.isatty(self.fileno()) + except OSError as e: + raise IOError(*e.args) + + def _trampoline(self, fd, read=False, write=False, timeout=None, timeout_exc=None): + if self._closed: + # Don't trampoline if we're already closed. + raise IOClosed() + try: + return trampoline(fd, read=read, write=write, timeout=timeout, + timeout_exc=timeout_exc, + mark_as_closed=self._mark_as_closed) + except IOClosed: + # Our fileno has been obsoleted. Defang ourselves to + # prevent spurious closes. + self._mark_as_closed() + raise + + def _mark_as_closed(self): + """ Mark this socket as being closed """ + self._closed = True + + def write(self, data): + view = memoryview(data) + datalen = len(data) + offset = 0 + while offset < datalen: + try: + written = _original_os.write(self._fileno, view[offset:]) + except OSError as e: + if get_errno(e) not in SOCKET_BLOCKING: + raise IOError(*e.args) + trampoline(self, write=True) + else: + offset += written + return offset + + def close(self): + if not self._closed: + self._closed = True + _original_os.close(self._fileno) + notify_close(self._fileno) + for method in [ + 'fileno', 'flush', 'isatty', 'next', 'read', 'readinto', + 'readline', 'readlines', 'seek', 'tell', 'truncate', + 'write', 'xreadlines', '__iter__', '__next__', 'writelines']: + setattr(self, method, _operation_on_closed_file) + + def truncate(self, size=-1): + if size == -1: + size = self.tell() + try: + rv = _original_os.ftruncate(self._fileno, size) + except OSError as e: + raise IOError(*e.args) + else: + self.seek(size) # move position&clear buffer + return rv + + def seek(self, offset, whence=_original_os.SEEK_SET): + try: + return _original_os.lseek(self._fileno, offset, whence) + except OSError as e: + raise IOError(*e.args) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + +_open_environment = dict(globals()) +_open_environment.update(dict( + BufferedRandom=_OriginalBufferedRandom, + BufferedWriter=_OriginalBufferedWriter, + BufferedReader=_OriginalBufferedReader, + TextIOWrapper=_OriginalTextIOWrapper, + FileIO=GreenFileIO, + os=_original_os, +)) +if hasattr(_original_pyio, 'text_encoding'): + _open_environment['text_encoding'] = _original_pyio.text_encoding + +_pyio_open = getattr(_original_pyio.open, '__wrapped__', _original_pyio.open) +_open = FunctionType( + six.get_function_code(_pyio_open), + _open_environment, +) + + +def GreenPipe(name, mode="r", buffering=-1, encoding=None, errors=None, + newline=None, closefd=True, opener=None): + try: + fileno = name.fileno() + except AttributeError: + pass + else: + fileno = _original_os.dup(fileno) + name.close() + name = fileno + + return _open(name, mode, buffering, encoding, errors, newline, closefd, opener) + +GreenPipe.__doc__ = greenpipe_doc diff --git a/venv/lib/python3.12/site-packages/eventlet/greenpool.py b/venv/lib/python3.12/site-packages/eventlet/greenpool.py new file mode 100644 index 0000000..952659c --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/greenpool.py @@ -0,0 +1,257 @@ +import traceback + +import eventlet +from eventlet import queue +from eventlet.support import greenlets as greenlet +import six + +__all__ = ['GreenPool', 'GreenPile'] + +DEBUG = True + + +class GreenPool(object): + """The GreenPool class is a pool of green threads. + """ + + def __init__(self, size=1000): + try: + size = int(size) + except ValueError as e: + msg = 'GreenPool() expect size :: int, actual: {0} {1}'.format(type(size), str(e)) + raise TypeError(msg) + if size < 0: + msg = 'GreenPool() expect size >= 0, actual: {0}'.format(repr(size)) + raise ValueError(msg) + self.size = size + self.coroutines_running = set() + self.sem = eventlet.Semaphore(size) + self.no_coros_running = eventlet.Event() + + def resize(self, new_size): + """ Change the max number of greenthreads doing work at any given time. + + If resize is called when there are more than *new_size* greenthreads + already working on tasks, they will be allowed to complete but no new + tasks will be allowed to get launched until enough greenthreads finish + their tasks to drop the overall quantity below *new_size*. Until + then, the return value of free() will be negative. + """ + size_delta = new_size - self.size + self.sem.counter += size_delta + self.size = new_size + + def running(self): + """ Returns the number of greenthreads that are currently executing + functions in the GreenPool.""" + return len(self.coroutines_running) + + def free(self): + """ Returns the number of greenthreads available for use. + + If zero or less, the next call to :meth:`spawn` or :meth:`spawn_n` will + block the calling greenthread until a slot becomes available.""" + return self.sem.counter + + def spawn(self, function, *args, **kwargs): + """Run the *function* with its arguments in its own green thread. + Returns the :class:`GreenThread ` + object that is running the function, which can be used to retrieve the + results. + + If the pool is currently at capacity, ``spawn`` will block until one of + the running greenthreads completes its task and frees up a slot. + + This function is reentrant; *function* can call ``spawn`` on the same + pool without risk of deadlocking the whole thing. + """ + # if reentering an empty pool, don't try to wait on a coroutine freeing + # itself -- instead, just execute in the current coroutine + current = eventlet.getcurrent() + if self.sem.locked() and current in self.coroutines_running: + # a bit hacky to use the GT without switching to it + gt = eventlet.greenthread.GreenThread(current) + gt.main(function, args, kwargs) + return gt + else: + self.sem.acquire() + gt = eventlet.spawn(function, *args, **kwargs) + if not self.coroutines_running: + self.no_coros_running = eventlet.Event() + self.coroutines_running.add(gt) + gt.link(self._spawn_done) + return gt + + def _spawn_n_impl(self, func, args, kwargs, coro): + try: + try: + func(*args, **kwargs) + except (KeyboardInterrupt, SystemExit, greenlet.GreenletExit): + raise + except: + if DEBUG: + traceback.print_exc() + finally: + if coro is None: + return + else: + coro = eventlet.getcurrent() + self._spawn_done(coro) + + def spawn_n(self, function, *args, **kwargs): + """Create a greenthread to run the *function*, the same as + :meth:`spawn`. The difference is that :meth:`spawn_n` returns + None; the results of *function* are not retrievable. + """ + # if reentering an empty pool, don't try to wait on a coroutine freeing + # itself -- instead, just execute in the current coroutine + current = eventlet.getcurrent() + if self.sem.locked() and current in self.coroutines_running: + self._spawn_n_impl(function, args, kwargs, None) + else: + self.sem.acquire() + g = eventlet.spawn_n( + self._spawn_n_impl, + function, args, kwargs, True) + if not self.coroutines_running: + self.no_coros_running = eventlet.Event() + self.coroutines_running.add(g) + + def waitall(self): + """Waits until all greenthreads in the pool are finished working.""" + assert eventlet.getcurrent() not in self.coroutines_running, \ + "Calling waitall() from within one of the " \ + "GreenPool's greenthreads will never terminate." + if self.running(): + self.no_coros_running.wait() + + def _spawn_done(self, coro): + self.sem.release() + if coro is not None: + self.coroutines_running.remove(coro) + # if done processing (no more work is waiting for processing), + # we can finish off any waitall() calls that might be pending + if self.sem.balance == self.size: + self.no_coros_running.send(None) + + def waiting(self): + """Return the number of greenthreads waiting to spawn. + """ + if self.sem.balance < 0: + return -self.sem.balance + else: + return 0 + + def _do_map(self, func, it, gi): + for args in it: + gi.spawn(func, *args) + gi.done_spawning() + + def starmap(self, function, iterable): + """This is the same as :func:`itertools.starmap`, except that *func* is + executed in a separate green thread for each item, with the concurrency + limited by the pool's size. In operation, starmap consumes a constant + amount of memory, proportional to the size of the pool, and is thus + suited for iterating over extremely long input lists. + """ + if function is None: + function = lambda *a: a + # We use a whole separate greenthread so its spawn() calls can block + # without blocking OUR caller. On the other hand, we must assume that + # our caller will immediately start trying to iterate over whatever we + # return. If that were a GreenPile, our caller would always see an + # empty sequence because the hub hasn't even entered _do_map() yet -- + # _do_map() hasn't had a chance to spawn a single greenthread on this + # GreenPool! A GreenMap is safe to use with different producer and + # consumer greenthreads, because it doesn't raise StopIteration until + # the producer has explicitly called done_spawning(). + gi = GreenMap(self.size) + eventlet.spawn_n(self._do_map, function, iterable, gi) + return gi + + def imap(self, function, *iterables): + """This is the same as :func:`itertools.imap`, and has the same + concurrency and memory behavior as :meth:`starmap`. + + It's quite convenient for, e.g., farming out jobs from a file:: + + def worker(line): + return do_something(line) + pool = GreenPool() + for result in pool.imap(worker, open("filename", 'r')): + print(result) + """ + return self.starmap(function, six.moves.zip(*iterables)) + + +class GreenPile(object): + """GreenPile is an abstraction representing a bunch of I/O-related tasks. + + Construct a GreenPile with an existing GreenPool object. The GreenPile will + then use that pool's concurrency as it processes its jobs. There can be + many GreenPiles associated with a single GreenPool. + + A GreenPile can also be constructed standalone, not associated with any + GreenPool. To do this, construct it with an integer size parameter instead + of a GreenPool. + + It is not advisable to iterate over a GreenPile in a different greenthread + than the one which is calling spawn. The iterator will exit early in that + situation. + """ + + def __init__(self, size_or_pool=1000): + if isinstance(size_or_pool, GreenPool): + self.pool = size_or_pool + else: + self.pool = GreenPool(size_or_pool) + self.waiters = queue.LightQueue() + self.counter = 0 + + def spawn(self, func, *args, **kw): + """Runs *func* in its own green thread, with the result available by + iterating over the GreenPile object.""" + self.counter += 1 + try: + gt = self.pool.spawn(func, *args, **kw) + self.waiters.put(gt) + except: + self.counter -= 1 + raise + + def __iter__(self): + return self + + def next(self): + """Wait for the next result, suspending the current greenthread until it + is available. Raises StopIteration when there are no more results.""" + if self.counter == 0: + raise StopIteration() + return self._next() + __next__ = next + + def _next(self): + try: + return self.waiters.get().wait() + finally: + self.counter -= 1 + + +# this is identical to GreenPile but it blocks on spawn if the results +# aren't consumed, and it doesn't generate its own StopIteration exception, +# instead relying on the spawning process to send one in when it's done +class GreenMap(GreenPile): + def __init__(self, size_or_pool): + super(GreenMap, self).__init__(size_or_pool) + self.waiters = queue.LightQueue(maxsize=self.pool.size) + + def done_spawning(self): + self.spawn(lambda: StopIteration()) + + def next(self): + val = self._next() + if isinstance(val, StopIteration): + raise val + else: + return val + __next__ = next diff --git a/venv/lib/python3.12/site-packages/eventlet/greenthread.py b/venv/lib/python3.12/site-packages/eventlet/greenthread.py new file mode 100644 index 0000000..c26d5a1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/greenthread.py @@ -0,0 +1,302 @@ +from collections import deque +import sys + +from eventlet import event +from eventlet import hubs +from eventlet import support +from eventlet import timeout +from eventlet.hubs import timer +from eventlet.support import greenlets as greenlet +import six +import warnings + +__all__ = ['getcurrent', 'sleep', 'spawn', 'spawn_n', + 'kill', + 'spawn_after', 'spawn_after_local', 'GreenThread'] + +getcurrent = greenlet.getcurrent + + +def sleep(seconds=0): + """Yield control to another eligible coroutine until at least *seconds* have + elapsed. + + *seconds* may be specified as an integer, or a float if fractional seconds + are desired. Calling :func:`~greenthread.sleep` with *seconds* of 0 is the + canonical way of expressing a cooperative yield. For example, if one is + looping over a large list performing an expensive calculation without + calling any socket methods, it's a good idea to call ``sleep(0)`` + occasionally; otherwise nothing else will run. + """ + hub = hubs.get_hub() + current = getcurrent() + assert hub.greenlet is not current, 'do not call blocking functions from the mainloop' + timer = hub.schedule_call_global(seconds, current.switch) + try: + hub.switch() + finally: + timer.cancel() + + +def spawn(func, *args, **kwargs): + """Create a greenthread to run ``func(*args, **kwargs)``. Returns a + :class:`GreenThread` object which you can use to get the results of the + call. + + Execution control returns immediately to the caller; the created greenthread + is merely scheduled to be run at the next available opportunity. + Use :func:`spawn_after` to arrange for greenthreads to be spawned + after a finite delay. + """ + hub = hubs.get_hub() + g = GreenThread(hub.greenlet) + hub.schedule_call_global(0, g.switch, func, args, kwargs) + return g + + +def spawn_n(func, *args, **kwargs): + """Same as :func:`spawn`, but returns a ``greenlet`` object from + which it is not possible to retrieve either a return value or + whether it raised any exceptions. This is faster than + :func:`spawn`; it is fastest if there are no keyword arguments. + + If an exception is raised in the function, spawn_n prints a stack + trace; the print can be disabled by calling + :func:`eventlet.debug.hub_exceptions` with False. + """ + return _spawn_n(0, func, args, kwargs)[1] + + +def spawn_after(seconds, func, *args, **kwargs): + """Spawns *func* after *seconds* have elapsed. It runs as scheduled even if + the current greenthread has completed. + + *seconds* may be specified as an integer, or a float if fractional seconds + are desired. The *func* will be called with the given *args* and + keyword arguments *kwargs*, and will be executed within its own greenthread. + + The return value of :func:`spawn_after` is a :class:`GreenThread` object, + which can be used to retrieve the results of the call. + + To cancel the spawn and prevent *func* from being called, + call :meth:`GreenThread.cancel` on the return value of :func:`spawn_after`. + This will not abort the function if it's already started running, which is + generally the desired behavior. If terminating *func* regardless of whether + it's started or not is the desired behavior, call :meth:`GreenThread.kill`. + """ + hub = hubs.get_hub() + g = GreenThread(hub.greenlet) + hub.schedule_call_global(seconds, g.switch, func, args, kwargs) + return g + + +def spawn_after_local(seconds, func, *args, **kwargs): + """Spawns *func* after *seconds* have elapsed. The function will NOT be + called if the current greenthread has exited. + + *seconds* may be specified as an integer, or a float if fractional seconds + are desired. The *func* will be called with the given *args* and + keyword arguments *kwargs*, and will be executed within its own greenthread. + + The return value of :func:`spawn_after` is a :class:`GreenThread` object, + which can be used to retrieve the results of the call. + + To cancel the spawn and prevent *func* from being called, + call :meth:`GreenThread.cancel` on the return value. This will not abort the + function if it's already started running. If terminating *func* regardless + of whether it's started or not is the desired behavior, call + :meth:`GreenThread.kill`. + """ + hub = hubs.get_hub() + g = GreenThread(hub.greenlet) + hub.schedule_call_local(seconds, g.switch, func, args, kwargs) + return g + + +def call_after_global(seconds, func, *args, **kwargs): + warnings.warn( + "call_after_global is renamed to spawn_after, which" + "has the same signature and semantics (plus a bit extra). Please do a" + " quick search-and-replace on your codebase, thanks!", + DeprecationWarning, stacklevel=2) + return _spawn_n(seconds, func, args, kwargs)[0] + + +def call_after_local(seconds, function, *args, **kwargs): + warnings.warn( + "call_after_local is renamed to spawn_after_local, which" + "has the same signature and semantics (plus a bit extra).", + DeprecationWarning, stacklevel=2) + hub = hubs.get_hub() + g = greenlet.greenlet(function, parent=hub.greenlet) + t = hub.schedule_call_local(seconds, g.switch, *args, **kwargs) + return t + + +call_after = call_after_local + + +def exc_after(seconds, *throw_args): + warnings.warn("Instead of exc_after, which is deprecated, use " + "Timeout(seconds, exception)", + DeprecationWarning, stacklevel=2) + if seconds is None: # dummy argument, do nothing + return timer.Timer(seconds, lambda: None) + hub = hubs.get_hub() + return hub.schedule_call_local(seconds, getcurrent().throw, *throw_args) + +# deprecate, remove +TimeoutError, with_timeout = ( + support.wrap_deprecated(old, new)(fun) for old, new, fun in ( + ('greenthread.TimeoutError', 'Timeout', timeout.Timeout), + ('greenthread.with_timeout', 'with_timeout', timeout.with_timeout), + )) + + +def _spawn_n(seconds, func, args, kwargs): + hub = hubs.get_hub() + g = greenlet.greenlet(func, parent=hub.greenlet) + t = hub.schedule_call_global(seconds, g.switch, *args, **kwargs) + return t, g + + +class GreenThread(greenlet.greenlet): + """The GreenThread class is a type of Greenlet which has the additional + property of being able to retrieve the return value of the main function. + Do not construct GreenThread objects directly; call :func:`spawn` to get one. + """ + + def __init__(self, parent): + greenlet.greenlet.__init__(self, self.main, parent) + self._exit_event = event.Event() + self._resolving_links = False + self._exit_funcs = None + + def wait(self): + """ Returns the result of the main function of this GreenThread. If the + result is a normal return value, :meth:`wait` returns it. If it raised + an exception, :meth:`wait` will raise the same exception (though the + stack trace will unavoidably contain some frames from within the + greenthread module).""" + return self._exit_event.wait() + + def link(self, func, *curried_args, **curried_kwargs): + """ Set up a function to be called with the results of the GreenThread. + + The function must have the following signature:: + + def func(gt, [curried args/kwargs]): + + When the GreenThread finishes its run, it calls *func* with itself + and with the `curried arguments `_ supplied + at link-time. If the function wants to retrieve the result of the GreenThread, + it should call wait() on its first argument. + + Note that *func* is called within execution context of + the GreenThread, so it is possible to interfere with other linked + functions by doing things like switching explicitly to another + greenthread. + """ + if self._exit_funcs is None: + self._exit_funcs = deque() + self._exit_funcs.append((func, curried_args, curried_kwargs)) + if self._exit_event.ready(): + self._resolve_links() + + def unlink(self, func, *curried_args, **curried_kwargs): + """ remove linked function set by :meth:`link` + + Remove successfully return True, otherwise False + """ + if not self._exit_funcs: + return False + try: + self._exit_funcs.remove((func, curried_args, curried_kwargs)) + return True + except ValueError: + return False + + def main(self, function, args, kwargs): + try: + result = function(*args, **kwargs) + except: + self._exit_event.send_exception(*sys.exc_info()) + self._resolve_links() + raise + else: + self._exit_event.send(result) + self._resolve_links() + + def _resolve_links(self): + # ca and ckw are the curried function arguments + if self._resolving_links: + return + if not self._exit_funcs: + return + self._resolving_links = True + try: + while self._exit_funcs: + f, ca, ckw = self._exit_funcs.popleft() + f(self, *ca, **ckw) + finally: + self._resolving_links = False + + def kill(self, *throw_args): + """Kills the greenthread using :func:`kill`. After being killed + all calls to :meth:`wait` will raise *throw_args* (which default + to :class:`greenlet.GreenletExit`).""" + return kill(self, *throw_args) + + def cancel(self, *throw_args): + """Kills the greenthread using :func:`kill`, but only if it hasn't + already started running. After being canceled, + all calls to :meth:`wait` will raise *throw_args* (which default + to :class:`greenlet.GreenletExit`).""" + return cancel(self, *throw_args) + + +def cancel(g, *throw_args): + """Like :func:`kill`, but only terminates the greenthread if it hasn't + already started execution. If the grenthread has already started + execution, :func:`cancel` has no effect.""" + if not g: + kill(g, *throw_args) + + +def kill(g, *throw_args): + """Terminates the target greenthread by raising an exception into it. + Whatever that greenthread might be doing; be it waiting for I/O or another + primitive, it sees an exception right away. + + By default, this exception is GreenletExit, but a specific exception + may be specified. *throw_args* should be the same as the arguments to + raise; either an exception instance or an exc_info tuple. + + Calling :func:`kill` causes the calling greenthread to cooperatively yield. + """ + if g.dead: + return + hub = hubs.get_hub() + if not g: + # greenlet hasn't started yet and therefore throw won't work + # on its own; semantically we want it to be as though the main + # method never got called + def just_raise(*a, **kw): + if throw_args: + six.reraise(throw_args[0], throw_args[1], throw_args[2]) + else: + raise greenlet.GreenletExit() + g.run = just_raise + if isinstance(g, GreenThread): + # it's a GreenThread object, so we want to call its main + # method to take advantage of the notification + try: + g.main(just_raise, (), {}) + except: + pass + current = getcurrent() + if current is not hub.greenlet: + # arrange to wake the caller back up immediately + hub.ensure_greenlet() + hub.schedule_call_global(0, current.switch) + g.throw(*throw_args) diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/__init__.py b/venv/lib/python3.12/site-packages/eventlet/hubs/__init__.py new file mode 100644 index 0000000..867628c --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/hubs/__init__.py @@ -0,0 +1,190 @@ +import importlib +import inspect +import os +import warnings + +from eventlet import patcher +from eventlet.support import greenlets as greenlet +import six + + +__all__ = ["use_hub", "get_hub", "get_default_hub", "trampoline"] + +threading = patcher.original('threading') +_threadlocal = threading.local() + + +# order is important, get_default_hub returns first available from here +builtin_hub_names = ('epolls', 'kqueue', 'poll', 'selects') +builtin_hub_modules = tuple(importlib.import_module('eventlet.hubs.' + name) for name in builtin_hub_names) + + +class HubError(Exception): + pass + + +def get_default_hub(): + """Select the default hub implementation based on what multiplexing + libraries are installed. The order that the hubs are tried is: + + * epoll + * kqueue + * poll + * select + + It won't automatically select the pyevent hub, because it's not + python-thread-safe. + + .. include:: ../doc/common.txt + .. note :: |internal| + """ + for mod in builtin_hub_modules: + if mod.is_available(): + return mod + + raise HubError('no built-in hubs are available: {}'.format(builtin_hub_modules)) + + +def use_hub(mod=None): + """Use the module *mod*, containing a class called Hub, as the + event hub. Usually not required; the default hub is usually fine. + + `mod` can be an actual hub class, a module, a string, or None. + + If `mod` is a class, use it directly. + If `mod` is a module, use `module.Hub` class + If `mod` is a string and contains either '.' or ':' + then `use_hub` uses 'package.subpackage.module:Class' convention, + otherwise imports `eventlet.hubs.mod`. + If `mod` is None, `use_hub` uses the default hub. + + Only call use_hub during application initialization, + because it resets the hub's state and any existing + timers or listeners will never be resumed. + + These two threadlocal attributes are not part of Eventlet public API: + - `threadlocal.Hub` (capital H) is hub constructor, used when no hub is currently active + - `threadlocal.hub` (lowercase h) is active hub instance + """ + if mod is None: + mod = os.environ.get('EVENTLET_HUB', None) + if mod is None: + mod = get_default_hub() + if hasattr(_threadlocal, 'hub'): + del _threadlocal.hub + + classname = '' + if isinstance(mod, six.string_types): + assert mod.strip(), "Need to specify a hub" + if '.' in mod or ':' in mod: + modulename, _, classname = mod.strip().partition(':') + else: + modulename = 'eventlet.hubs.' + mod + mod = importlib.import_module(modulename) + + if hasattr(mod, 'is_available'): + if not mod.is_available(): + raise Exception('selected hub is not available on this system mod={}'.format(mod)) + else: + msg = '''Please provide `is_available()` function in your custom Eventlet hub {mod}. +It must return bool: whether hub supports current platform. See eventlet/hubs/{{epoll,kqueue}} for example. +'''.format(mod=mod) + warnings.warn(msg, DeprecationWarning, stacklevel=3) + + hubclass = mod + if not inspect.isclass(mod): + hubclass = getattr(mod, classname or 'Hub') + + _threadlocal.Hub = hubclass + + +def get_hub(): + """Get the current event hub singleton object. + + .. note :: |internal| + """ + try: + hub = _threadlocal.hub + except AttributeError: + try: + _threadlocal.Hub + except AttributeError: + use_hub() + hub = _threadlocal.hub = _threadlocal.Hub() + return hub + + +# Lame middle file import because complex dependencies in import graph +from eventlet import timeout + + +def trampoline(fd, read=None, write=None, timeout=None, + timeout_exc=timeout.Timeout, + mark_as_closed=None): + """Suspend the current coroutine until the given socket object or file + descriptor is ready to *read*, ready to *write*, or the specified + *timeout* elapses, depending on arguments specified. + + To wait for *fd* to be ready to read, pass *read* ``=True``; ready to + write, pass *write* ``=True``. To specify a timeout, pass the *timeout* + argument in seconds. + + If the specified *timeout* elapses before the socket is ready to read or + write, *timeout_exc* will be raised instead of ``trampoline()`` + returning normally. + + .. note :: |internal| + """ + t = None + hub = get_hub() + current = greenlet.getcurrent() + assert hub.greenlet is not current, 'do not call blocking functions from the mainloop' + assert not ( + read and write), 'not allowed to trampoline for reading and writing' + try: + fileno = fd.fileno() + except AttributeError: + fileno = fd + if timeout is not None: + def _timeout(exc): + # This is only useful to insert debugging + current.throw(exc) + t = hub.schedule_call_global(timeout, _timeout, timeout_exc) + try: + if read: + listener = hub.add(hub.READ, fileno, current.switch, current.throw, mark_as_closed) + elif write: + listener = hub.add(hub.WRITE, fileno, current.switch, current.throw, mark_as_closed) + try: + return hub.switch() + finally: + hub.remove(listener) + finally: + if t is not None: + t.cancel() + + +def notify_close(fd): + """ + A particular file descriptor has been explicitly closed. Register for any + waiting listeners to be notified on the next run loop. + """ + hub = get_hub() + hub.notify_close(fd) + + +def notify_opened(fd): + """ + Some file descriptors may be closed 'silently' - that is, by the garbage + collector, by an external library, etc. When the OS returns a file descriptor + from an open call (or something similar), this may be the only indication we + have that the FD has been closed and then recycled. + We let the hub know that the old file descriptor is dead; any stuck listeners + will be disabled and notified in turn. + """ + hub = get_hub() + hub.mark_as_reopened(fd) + + +class IOClosed(IOError): + pass diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68372a85f30cb16cfcfe0b8b12171eb07946aec8 GIT binary patch literal 8340 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 IV0vNy1*@XGWB>pF literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/hub.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/hub.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aff91afc5f26f613d6bb71c8113e9f10499f668f GIT binary patch literal 24243 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/kqueue.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/kqueue.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ecb2fc5e5b1e066a8e4e2104dbd29a55705e6e3 GIT binary patch literal 6643 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 Qsk*7{liR-~NGr4b4=taO=l}o! literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/pyevent.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/pyevent.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1db3b4cc92796ca638c0c075a37b466b244756a5 GIT binary patch literal 9876 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/selects.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/selects.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc39348243555a0b836bd461bc541e729d1c005d GIT binary patch literal 3778 zcmcH+TWDL!b?!q?OVR7vvYp4umXlbqBds5Cx6}>D8)p+joHjO1BX&_`X>3U=OUm49 zd!4-sCgei_L+rL7_s@ZaBqY$3E;NNgo3OjIw9u$ZD(zkO0sZQq;-nC~AMKedUCEm| zus=JXnKNh3Irq$&^U@!!Rx^U}r$>K}>x~Hgi*B@`s{_xw8H5TbhGI+#g=#AkVqnjv z*f}o5&G8|gp?EH(o70E%3}O-Hl6y2k%{9i~1zB{2jIX89HE2vGD#?sdvaQCoa|Uet z&V3D@VH@1mnEo0H*;w=riW#nFY( zHdK-4-7po9R5ues604ELn7eg&68YE(qK54iWlF&)S*N%2g2$UiU`5EBwUnvF!?kIZ5*fPq9xVJQH*ih-K0qCdN& zSQ275vJgq6BGV~MtndZ;xS|cFWwhDi{OQR0Tcwts$9xZI0o+$^rs@B@6HW>UBkDRK zHpal?a4|l{U1LK066@0`+^PBLXyf8QZGfL@l(_1FO{2&tsfh&=Z2*><@s-g5d+o)( zh6HNWSH?~Wm*FhO$jk}!-add>6OtfqY9ndVpyn#Gv&<}-h1j`U*Ns6d2Y9r^dUtZF zQP||75cuIpj+3ad)v;SV$z0=Rc8zWU5eHbprR|%m+=vYZU4-b(aaXvNSZ!To8h*@O z4tq{Zm_#J(k>VsXKO6VV#Ke9N!E>2~M0ysm*po?P5rBx~i4g3Wj>J4!Oz4T(Qy&>X zL?lWtE{PJJ^EXZieY^>uS>aO&QBq8F#%WAMg+D(YJfY|=oIgD^t~#`$!-QlAp~h28 zvsenlKAlmFW5E;Q$#ZX?Q5>pTI2^-blq9lJhWL1e2j`tp43ShS45};aOiW?0NQF@4 zh=oeCh3lN4Jf*tq0{`>Bf}~~?e+<(ydSOH^_o`I5vUX)7b}Ml+QFOmqat?fa;(_qn z1HV4-d;ZVnKbVW(o-7Sd6`fOgL&e##g>h!<)5*N7Qv*^D zVMyOp9mz(>Xc&YjrL315LHI7%A!o=Lb0*mYq5LD*wa9F8f0ILlUK_g zHLB(+w`k$XAes9nrLRo$NM>cj3@dZ8afY2?;Z~yvy$jmqEIF%eh5aDCr3uNFZ&Z_E z9<3aQ1cxBOHQ!{HW&jdgBxjZ!n!S=v4Qj5kS+k%dXxQDFf%kh6B05NFC}+N61~2-$ zQX4G6|g zX{8ViYC>BsHTy!g)s3H?m^x3~6i;JB;UE(e+8`=?I+MvNY;;<2OkSLv8b2Ez|KZ!? z-}?=85~J0@ut=a^0l-|tsJY!-LsFQ~7DHi(!YnE*)Cs82NYT$EQaGJaudnFU z7Dp7RJJL$f9JQLjFGdp5Wg{&F6nX>5#9PpOETf9Et>9ntZwR+KZgv!%eKfyon9iG> zMQ4BB@VTw+XNxdx+J%C7&Ak4tCw6be;@oWQSU>!e_wwcjP;u^Czg%?s@`fsFGPP~G z_ZM<&xsA~M&IcEMH(PQ~TtAf$<}a;wR|VvTD#DtMA(GhCzj#YA3n7VL%lE^s~S;H-|gT=|9$s^uKSk9UEke8M$Ky|WVSnN5+S!dm_q;#vz0+F~hRVW7Q5d=ZXCgQ2f*0`eoGyLcXkaqxOAe_tB#J=oVreofV;@%Cb}^tqP8OJoKy4UyT0p z_!D7lQ`lP;{6)e4SK-ZVaR46dVc)(J!{`$fIN8U4GVD6p%YAA%bkfUxI@kyEXWa~) zy*j{u))zFrGh!h0nW6tQhQsN|91e#Sb2vPgiOr`dYzc=i%|}wT6V?04F`y^3WmCId z)pH2#h1H3MnMf1gHMGT=b%7V{UAlo9A*Jy-Nf#xBgoPju>K!+s)w$N>ST&rVcHy-k zGiFF)Hj$2`YC)=oE+=9M0u%b6lS<2l$>8|JxhdKZsZpxz3Bq53#Pqy`$JPFc)^9(8Hnju|3=) ID+BZY0FjgW3IG5A literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/timer.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/hubs/__pycache__/timer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf1de1f61e2598db48cadea9414ac8cfcfdbb642 GIT binary patch literal 5258 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 F{~t5mLsb9( literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/epolls.py b/venv/lib/python3.12/site-packages/eventlet/hubs/epolls.py new file mode 100644 index 0000000..07fec14 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/hubs/epolls.py @@ -0,0 +1,31 @@ +import errno +from eventlet import patcher, support +from eventlet.hubs import hub, poll +select = patcher.original('select') + + +def is_available(): + return hasattr(select, 'epoll') + + +# NOTE: we rely on the fact that the epoll flag constants +# are identical in value to the poll constants +class Hub(poll.Hub): + def __init__(self, clock=None): + super(Hub, self).__init__(clock=clock) + self.poll = select.epoll() + + def add(self, evtype, fileno, cb, tb, mac): + oldlisteners = bool(self.listeners[self.READ].get(fileno) or + self.listeners[self.WRITE].get(fileno)) + # not super() to avoid double register() + listener = hub.BaseHub.add(self, evtype, fileno, cb, tb, mac) + try: + self.register(fileno, new=not oldlisteners) + except IOError as ex: # ignore EEXIST, #80 + if support.get_errno(ex) != errno.EEXIST: + raise + return listener + + def do_poll(self, seconds): + return self.poll.poll(seconds) diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/hub.py b/venv/lib/python3.12/site-packages/eventlet/hubs/hub.py new file mode 100644 index 0000000..db55958 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/hubs/hub.py @@ -0,0 +1,501 @@ +import errno +import heapq +import math +import signal +import sys +import traceback + +arm_alarm = None +if hasattr(signal, 'setitimer'): + def alarm_itimer(seconds): + signal.setitimer(signal.ITIMER_REAL, seconds) + arm_alarm = alarm_itimer +else: + try: + import itimer + arm_alarm = itimer.alarm + except ImportError: + def alarm_signal(seconds): + signal.alarm(math.ceil(seconds)) + arm_alarm = alarm_signal + +import eventlet.hubs +from eventlet.hubs import timer +from eventlet.support import greenlets as greenlet, clear_sys_exc_info +try: + from monotonic import monotonic +except ImportError: + from time import monotonic + +import six + +g_prevent_multiple_readers = True + +READ = "read" +WRITE = "write" + + +def closed_callback(fileno): + """ Used to de-fang a callback that may be triggered by a loop in BaseHub.wait + """ + # No-op. + pass + + +class FdListener(object): + + def __init__(self, evtype, fileno, cb, tb, mark_as_closed): + """ The following are required: + cb - the standard callback, which will switch into the + listening greenlet to indicate that the event waited upon + is ready + tb - a 'throwback'. This is typically greenlet.throw, used + to raise a signal into the target greenlet indicating that + an event was obsoleted by its underlying filehandle being + repurposed. + mark_as_closed - if any listener is obsoleted, this is called + (in the context of some other client greenlet) to alert + underlying filehandle-wrapping objects that they've been + closed. + """ + assert (evtype is READ or evtype is WRITE) + self.evtype = evtype + self.fileno = fileno + self.cb = cb + self.tb = tb + self.mark_as_closed = mark_as_closed + self.spent = False + self.greenlet = greenlet.getcurrent() + + def __repr__(self): + return "%s(%r, %r, %r, %r)" % (type(self).__name__, self.evtype, self.fileno, + self.cb, self.tb) + __str__ = __repr__ + + def defang(self): + self.cb = closed_callback + if self.mark_as_closed is not None: + self.mark_as_closed() + self.spent = True + + +noop = FdListener(READ, 0, lambda x: None, lambda x: None, None) + + +# in debug mode, track the call site that created the listener + + +class DebugListener(FdListener): + + def __init__(self, evtype, fileno, cb, tb, mark_as_closed): + self.where_called = traceback.format_stack() + self.greenlet = greenlet.getcurrent() + super(DebugListener, self).__init__(evtype, fileno, cb, tb, mark_as_closed) + + def __repr__(self): + return "DebugListener(%r, %r, %r, %r, %r, %r)\n%sEndDebugFdListener" % ( + self.evtype, + self.fileno, + self.cb, + self.tb, + self.mark_as_closed, + self.greenlet, + ''.join(self.where_called)) + __str__ = __repr__ + + +def alarm_handler(signum, frame): + import inspect + raise RuntimeError("Blocking detector ALARMED at" + str(inspect.getframeinfo(frame))) + + +class BaseHub(object): + """ Base hub class for easing the implementation of subclasses that are + specific to a particular underlying event architecture. """ + + SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) + + READ = READ + WRITE = WRITE + + def __init__(self, clock=None): + self.listeners = {READ: {}, WRITE: {}} + self.secondaries = {READ: {}, WRITE: {}} + self.closed = [] + + if clock is None: + clock = monotonic + self.clock = clock + + self.greenlet = greenlet.greenlet(self.run) + self.stopping = False + self.running = False + self.timers = [] + self.next_timers = [] + self.lclass = FdListener + self.timers_canceled = 0 + self.debug_exceptions = True + self.debug_blocking = False + self.debug_blocking_resolution = 1 + + def block_detect_pre(self): + # shortest alarm we can possibly raise is one second + tmp = signal.signal(signal.SIGALRM, alarm_handler) + if tmp != alarm_handler: + self._old_signal_handler = tmp + + arm_alarm(self.debug_blocking_resolution) + + def block_detect_post(self): + if (hasattr(self, "_old_signal_handler") and + self._old_signal_handler): + signal.signal(signal.SIGALRM, self._old_signal_handler) + signal.alarm(0) + + def add(self, evtype, fileno, cb, tb, mark_as_closed): + """ Signals an intent to or write a particular file descriptor. + + The *evtype* argument is either the constant READ or WRITE. + + The *fileno* argument is the file number of the file of interest. + + The *cb* argument is the callback which will be called when the file + is ready for reading/writing. + + The *tb* argument is the throwback used to signal (into the greenlet) + that the file was closed. + + The *mark_as_closed* is used in the context of the event hub to + prepare a Python object as being closed, pre-empting further + close operations from accidentally shutting down the wrong OS thread. + """ + listener = self.lclass(evtype, fileno, cb, tb, mark_as_closed) + bucket = self.listeners[evtype] + if fileno in bucket: + if g_prevent_multiple_readers: + raise RuntimeError( + "Second simultaneous %s on fileno %s " + "detected. Unless you really know what you're doing, " + "make sure that only one greenthread can %s any " + "particular socket. Consider using a pools.Pool. " + "If you do know what you're doing and want to disable " + "this error, call " + "eventlet.debug.hub_prevent_multiple_readers(False) - MY THREAD=%s; " + "THAT THREAD=%s" % ( + evtype, fileno, evtype, cb, bucket[fileno])) + # store off the second listener in another structure + self.secondaries[evtype].setdefault(fileno, []).append(listener) + else: + bucket[fileno] = listener + return listener + + def _obsolete(self, fileno): + """ We've received an indication that 'fileno' has been obsoleted. + Any current listeners must be defanged, and notifications to + their greenlets queued up to send. + """ + found = False + for evtype, bucket in six.iteritems(self.secondaries): + if fileno in bucket: + for listener in bucket[fileno]: + found = True + self.closed.append(listener) + listener.defang() + del bucket[fileno] + + # For the primary listeners, we actually need to call remove, + # which may modify the underlying OS polling objects. + for evtype, bucket in six.iteritems(self.listeners): + if fileno in bucket: + listener = bucket[fileno] + found = True + self.closed.append(listener) + self.remove(listener) + listener.defang() + + return found + + def notify_close(self, fileno): + """ We might want to do something when a fileno is closed. + However, currently it suffices to obsolete listeners only + when we detect an old fileno being recycled, on open. + """ + pass + + def remove(self, listener): + if listener.spent: + # trampoline may trigger this in its finally section. + return + + fileno = listener.fileno + evtype = listener.evtype + if listener is self.listeners[evtype][fileno]: + del self.listeners[evtype][fileno] + # migrate a secondary listener to be the primary listener + if fileno in self.secondaries[evtype]: + sec = self.secondaries[evtype][fileno] + if sec: + self.listeners[evtype][fileno] = sec.pop(0) + if not sec: + del self.secondaries[evtype][fileno] + else: + self.secondaries[evtype][fileno].remove(listener) + if not self.secondaries[evtype][fileno]: + del self.secondaries[evtype][fileno] + + def mark_as_reopened(self, fileno): + """ If a file descriptor is returned by the OS as the result of some + open call (or equivalent), that signals that it might be being + recycled. + + Catch the case where the fd was previously in use. + """ + self._obsolete(fileno) + + def remove_descriptor(self, fileno): + """ Completely remove all listeners for this fileno. For internal use + only.""" + # gather any listeners we have + listeners = [] + listeners.append(self.listeners[READ].get(fileno, noop)) + listeners.append(self.listeners[WRITE].get(fileno, noop)) + listeners.extend(self.secondaries[READ].get(fileno, ())) + listeners.extend(self.secondaries[WRITE].get(fileno, ())) + for listener in listeners: + try: + # listener.cb may want to remove(listener) + listener.cb(fileno) + except Exception: + self.squelch_generic_exception(sys.exc_info()) + # NOW this fileno is now dead to all + self.listeners[READ].pop(fileno, None) + self.listeners[WRITE].pop(fileno, None) + self.secondaries[READ].pop(fileno, None) + self.secondaries[WRITE].pop(fileno, None) + + def close_one(self): + """ Triggered from the main run loop. If a listener's underlying FD was + closed somehow, throw an exception back to the trampoline, which should + be able to manage it appropriately. + """ + listener = self.closed.pop() + if not listener.greenlet.dead: + # There's no point signalling a greenlet that's already dead. + listener.tb(eventlet.hubs.IOClosed(errno.ENOTCONN, "Operation on closed file")) + + def ensure_greenlet(self): + if self.greenlet.dead: + # create new greenlet sharing same parent as original + new = greenlet.greenlet(self.run, self.greenlet.parent) + # need to assign as parent of old greenlet + # for those greenlets that are currently + # children of the dead hub and may subsequently + # exit without further switching to hub. + self.greenlet.parent = new + self.greenlet = new + + def switch(self): + cur = greenlet.getcurrent() + assert cur is not self.greenlet, 'Cannot switch to MAINLOOP from MAINLOOP' + switch_out = getattr(cur, 'switch_out', None) + if switch_out is not None: + try: + switch_out() + except: + self.squelch_generic_exception(sys.exc_info()) + self.ensure_greenlet() + try: + if self.greenlet.parent is not cur: + cur.parent = self.greenlet + except ValueError: + pass # gets raised if there is a greenlet parent cycle + clear_sys_exc_info() + return self.greenlet.switch() + + def squelch_exception(self, fileno, exc_info): + traceback.print_exception(*exc_info) + sys.stderr.write("Removing descriptor: %r\n" % (fileno,)) + sys.stderr.flush() + try: + self.remove_descriptor(fileno) + except Exception as e: + sys.stderr.write("Exception while removing descriptor! %r\n" % (e,)) + sys.stderr.flush() + + def wait(self, seconds=None): + raise NotImplementedError("Implement this in a subclass") + + def default_sleep(self): + return 60.0 + + def sleep_until(self): + t = self.timers + if not t: + return None + return t[0][0] + + def run(self, *a, **kw): + """Run the runloop until abort is called. + """ + # accept and discard variable arguments because they will be + # supplied if other greenlets have run and exited before the + # hub's greenlet gets a chance to run + if self.running: + raise RuntimeError("Already running!") + try: + self.running = True + self.stopping = False + while not self.stopping: + while self.closed: + # We ditch all of these first. + self.close_one() + self.prepare_timers() + if self.debug_blocking: + self.block_detect_pre() + self.fire_timers(self.clock()) + if self.debug_blocking: + self.block_detect_post() + self.prepare_timers() + wakeup_when = self.sleep_until() + if wakeup_when is None: + sleep_time = self.default_sleep() + else: + sleep_time = wakeup_when - self.clock() + if sleep_time > 0: + self.wait(sleep_time) + else: + self.wait(0) + else: + self.timers_canceled = 0 + del self.timers[:] + del self.next_timers[:] + finally: + self.running = False + self.stopping = False + + def abort(self, wait=False): + """Stop the runloop. If run is executing, it will exit after + completing the next runloop iteration. + + Set *wait* to True to cause abort to switch to the hub immediately and + wait until it's finished processing. Waiting for the hub will only + work from the main greenthread; all other greenthreads will become + unreachable. + """ + if self.running: + self.stopping = True + if wait: + assert self.greenlet is not greenlet.getcurrent( + ), "Can't abort with wait from inside the hub's greenlet." + # schedule an immediate timer just so the hub doesn't sleep + self.schedule_call_global(0, lambda: None) + # switch to it; when done the hub will switch back to its parent, + # the main greenlet + self.switch() + + def squelch_generic_exception(self, exc_info): + if self.debug_exceptions: + traceback.print_exception(*exc_info) + sys.stderr.flush() + clear_sys_exc_info() + + def squelch_timer_exception(self, timer, exc_info): + if self.debug_exceptions: + traceback.print_exception(*exc_info) + sys.stderr.flush() + clear_sys_exc_info() + + def add_timer(self, timer): + scheduled_time = self.clock() + timer.seconds + self.next_timers.append((scheduled_time, timer)) + return scheduled_time + + def timer_canceled(self, timer): + self.timers_canceled += 1 + len_timers = len(self.timers) + len(self.next_timers) + if len_timers > 1000 and len_timers / 2 <= self.timers_canceled: + self.timers_canceled = 0 + self.timers = [t for t in self.timers if not t[1].called] + self.next_timers = [t for t in self.next_timers if not t[1].called] + heapq.heapify(self.timers) + + def prepare_timers(self): + heappush = heapq.heappush + t = self.timers + for item in self.next_timers: + if item[1].called: + self.timers_canceled -= 1 + else: + heappush(t, item) + del self.next_timers[:] + + def schedule_call_local(self, seconds, cb, *args, **kw): + """Schedule a callable to be called after 'seconds' seconds have + elapsed. Cancel the timer if greenlet has exited. + seconds: The number of seconds to wait. + cb: The callable to call after the given time. + *args: Arguments to pass to the callable when called. + **kw: Keyword arguments to pass to the callable when called. + """ + t = timer.LocalTimer(seconds, cb, *args, **kw) + self.add_timer(t) + return t + + def schedule_call_global(self, seconds, cb, *args, **kw): + """Schedule a callable to be called after 'seconds' seconds have + elapsed. The timer will NOT be canceled if the current greenlet has + exited before the timer fires. + seconds: The number of seconds to wait. + cb: The callable to call after the given time. + *args: Arguments to pass to the callable when called. + **kw: Keyword arguments to pass to the callable when called. + """ + t = timer.Timer(seconds, cb, *args, **kw) + self.add_timer(t) + return t + + def fire_timers(self, when): + t = self.timers + heappop = heapq.heappop + + while t: + next = t[0] + + exp = next[0] + timer = next[1] + + if when < exp: + break + + heappop(t) + + try: + if timer.called: + self.timers_canceled -= 1 + else: + timer() + except self.SYSTEM_EXCEPTIONS: + raise + except: + self.squelch_timer_exception(timer, sys.exc_info()) + clear_sys_exc_info() + + # for debugging: + + def get_readers(self): + return self.listeners[READ].values() + + def get_writers(self): + return self.listeners[WRITE].values() + + def get_timers_count(hub): + return len(hub.timers) + len(hub.next_timers) + + def set_debug_listeners(self, value): + if value: + self.lclass = DebugListener + else: + self.lclass = FdListener + + def set_timer_exceptions(self, value): + self.debug_exceptions = value diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/kqueue.py b/venv/lib/python3.12/site-packages/eventlet/hubs/kqueue.py new file mode 100644 index 0000000..bad4a87 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/hubs/kqueue.py @@ -0,0 +1,112 @@ +import os +import sys +from eventlet import patcher, support +from eventlet.hubs import hub +import six +select = patcher.original('select') +time = patcher.original('time') + + +def is_available(): + return hasattr(select, 'kqueue') + + +class Hub(hub.BaseHub): + MAX_EVENTS = 100 + + def __init__(self, clock=None): + self.FILTERS = { + hub.READ: select.KQ_FILTER_READ, + hub.WRITE: select.KQ_FILTER_WRITE, + } + super(Hub, self).__init__(clock) + self._events = {} + self._init_kqueue() + + def _init_kqueue(self): + self.kqueue = select.kqueue() + self._pid = os.getpid() + + def _reinit_kqueue(self): + self.kqueue.close() + self._init_kqueue() + events = [e for i in six.itervalues(self._events) + for e in six.itervalues(i)] + self.kqueue.control(events, 0, 0) + + def _control(self, events, max_events, timeout): + try: + return self.kqueue.control(events, max_events, timeout) + except (OSError, IOError): + # have we forked? + if os.getpid() != self._pid: + self._reinit_kqueue() + return self.kqueue.control(events, max_events, timeout) + raise + + def add(self, evtype, fileno, cb, tb, mac): + listener = super(Hub, self).add(evtype, fileno, cb, tb, mac) + events = self._events.setdefault(fileno, {}) + if evtype not in events: + try: + event = select.kevent(fileno, self.FILTERS.get(evtype), select.KQ_EV_ADD) + self._control([event], 0, 0) + events[evtype] = event + except ValueError: + super(Hub, self).remove(listener) + raise + return listener + + def _delete_events(self, events): + del_events = [ + select.kevent(e.ident, e.filter, select.KQ_EV_DELETE) + for e in events + ] + self._control(del_events, 0, 0) + + def remove(self, listener): + super(Hub, self).remove(listener) + evtype = listener.evtype + fileno = listener.fileno + if not self.listeners[evtype].get(fileno): + event = self._events[fileno].pop(evtype, None) + if event is None: + return + try: + self._delete_events((event,)) + except OSError: + pass + + def remove_descriptor(self, fileno): + super(Hub, self).remove_descriptor(fileno) + try: + events = self._events.pop(fileno).values() + self._delete_events(events) + except KeyError: + pass + except OSError: + pass + + def wait(self, seconds=None): + readers = self.listeners[self.READ] + writers = self.listeners[self.WRITE] + + if not readers and not writers: + if seconds: + time.sleep(seconds) + return + result = self._control([], self.MAX_EVENTS, seconds) + SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS + for event in result: + fileno = event.ident + evfilt = event.filter + try: + if evfilt == select.KQ_FILTER_READ: + readers.get(fileno, hub.noop).cb(fileno) + if evfilt == select.KQ_FILTER_WRITE: + writers.get(fileno, hub.noop).cb(fileno) + except SYSTEM_EXCEPTIONS: + raise + except: + self.squelch_exception(fileno, sys.exc_info()) + support.clear_sys_exc_info() diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/poll.py b/venv/lib/python3.12/site-packages/eventlet/hubs/poll.py new file mode 100644 index 0000000..1bbd401 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/hubs/poll.py @@ -0,0 +1,119 @@ +import errno +import sys + +from eventlet import patcher, support +from eventlet.hubs import hub +select = patcher.original('select') +time = patcher.original('time') + + +def is_available(): + return hasattr(select, 'poll') + + +class Hub(hub.BaseHub): + def __init__(self, clock=None): + super(Hub, self).__init__(clock) + self.EXC_MASK = select.POLLERR | select.POLLHUP + self.READ_MASK = select.POLLIN | select.POLLPRI + self.WRITE_MASK = select.POLLOUT + self.poll = select.poll() + + def add(self, evtype, fileno, cb, tb, mac): + listener = super(Hub, self).add(evtype, fileno, cb, tb, mac) + self.register(fileno, new=True) + return listener + + def remove(self, listener): + super(Hub, self).remove(listener) + self.register(listener.fileno) + + def register(self, fileno, new=False): + mask = 0 + if self.listeners[self.READ].get(fileno): + mask |= self.READ_MASK | self.EXC_MASK + if self.listeners[self.WRITE].get(fileno): + mask |= self.WRITE_MASK | self.EXC_MASK + try: + if mask: + if new: + self.poll.register(fileno, mask) + else: + try: + self.poll.modify(fileno, mask) + except (IOError, OSError): + self.poll.register(fileno, mask) + else: + try: + self.poll.unregister(fileno) + except (KeyError, IOError, OSError): + # raised if we try to remove a fileno that was + # already removed/invalid + pass + except ValueError: + # fileno is bad, issue 74 + self.remove_descriptor(fileno) + raise + + def remove_descriptor(self, fileno): + super(Hub, self).remove_descriptor(fileno) + try: + self.poll.unregister(fileno) + except (KeyError, ValueError, IOError, OSError): + # raised if we try to remove a fileno that was + # already removed/invalid + pass + + def do_poll(self, seconds): + # poll.poll expects integral milliseconds + return self.poll.poll(int(seconds * 1000.0)) + + def wait(self, seconds=None): + readers = self.listeners[self.READ] + writers = self.listeners[self.WRITE] + + if not readers and not writers: + if seconds: + time.sleep(seconds) + return + try: + presult = self.do_poll(seconds) + except (IOError, select.error) as e: + if support.get_errno(e) == errno.EINTR: + return + raise + SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS + + if self.debug_blocking: + self.block_detect_pre() + + # Accumulate the listeners to call back to prior to + # triggering any of them. This is to keep the set + # of callbacks in sync with the events we've just + # polled for. It prevents one handler from invalidating + # another. + callbacks = set() + noop = hub.noop # shave getattr + for fileno, event in presult: + if event & self.READ_MASK: + callbacks.add((readers.get(fileno, noop), fileno)) + if event & self.WRITE_MASK: + callbacks.add((writers.get(fileno, noop), fileno)) + if event & select.POLLNVAL: + self.remove_descriptor(fileno) + continue + if event & self.EXC_MASK: + callbacks.add((readers.get(fileno, noop), fileno)) + callbacks.add((writers.get(fileno, noop), fileno)) + + for listener, fileno in callbacks: + try: + listener.cb(fileno) + except SYSTEM_EXCEPTIONS: + raise + except: + self.squelch_exception(fileno, sys.exc_info()) + support.clear_sys_exc_info() + + if self.debug_blocking: + self.block_detect_post() diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/pyevent.py b/venv/lib/python3.12/site-packages/eventlet/hubs/pyevent.py new file mode 100644 index 0000000..503aad4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/hubs/pyevent.py @@ -0,0 +1,193 @@ +import sys +import traceback +import types +import warnings + +from eventlet.support import greenlets as greenlet +import six +from eventlet.hubs.hub import BaseHub, READ, WRITE + +try: + import event +except ImportError: + event = None + + +def is_available(): + return event is not None + + +class event_wrapper(object): + + def __init__(self, impl=None, seconds=None): + self.impl = impl + self.seconds = seconds + + def __repr__(self): + if self.impl is not None: + return repr(self.impl) + else: + return object.__repr__(self) + + def __str__(self): + if self.impl is not None: + return str(self.impl) + else: + return object.__str__(self) + + def cancel(self): + if self.impl is not None: + self.impl.delete() + self.impl = None + + @property + def pending(self): + return bool(self.impl and self.impl.pending()) + + +class Hub(BaseHub): + + SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) + + def __init__(self): + super(Hub, self).__init__() + event.init() + + self.signal_exc_info = None + self.signal( + 2, + lambda signalnum, frame: self.greenlet.parent.throw(KeyboardInterrupt)) + self.events_to_add = [] + + warnings.warn( + "ACTION REQUIRED eventlet pyevent hub is deprecated and will be removed soon", + DeprecationWarning, + ) + + def dispatch(self): + loop = event.loop + while True: + for e in self.events_to_add: + if e is not None and e.impl is not None and e.seconds is not None: + e.impl.add(e.seconds) + e.seconds = None + self.events_to_add = [] + result = loop() + + if getattr(event, '__event_exc', None) is not None: + # only have to do this because of bug in event.loop + t = getattr(event, '__event_exc') + setattr(event, '__event_exc', None) + assert getattr(event, '__event_exc') is None + six.reraise(t[0], t[1], t[2]) + + if result != 0: + return result + + def run(self): + while True: + try: + self.dispatch() + except greenlet.GreenletExit: + break + except self.SYSTEM_EXCEPTIONS: + raise + except: + if self.signal_exc_info is not None: + self.schedule_call_global( + 0, greenlet.getcurrent().parent.throw, *self.signal_exc_info) + self.signal_exc_info = None + else: + self.squelch_timer_exception(None, sys.exc_info()) + + def abort(self, wait=True): + self.schedule_call_global(0, self.greenlet.throw, greenlet.GreenletExit) + if wait: + assert self.greenlet is not greenlet.getcurrent( + ), "Can't abort with wait from inside the hub's greenlet." + self.switch() + + def _getrunning(self): + return bool(self.greenlet) + + def _setrunning(self, value): + pass # exists for compatibility with BaseHub + running = property(_getrunning, _setrunning) + + def add(self, evtype, fileno, real_cb, real_tb, mac): + # this is stupid: pyevent won't call a callback unless it's a function, + # so we have to force it to be one here + if isinstance(real_cb, types.BuiltinMethodType): + def cb(_d): + real_cb(_d) + else: + cb = real_cb + + if evtype is READ: + evt = event.read(fileno, cb, fileno) + elif evtype is WRITE: + evt = event.write(fileno, cb, fileno) + + return super(Hub, self).add(evtype, fileno, evt, real_tb, mac) + + def signal(self, signalnum, handler): + def wrapper(): + try: + handler(signalnum, None) + except: + self.signal_exc_info = sys.exc_info() + event.abort() + return event_wrapper(event.signal(signalnum, wrapper)) + + def remove(self, listener): + super(Hub, self).remove(listener) + listener.cb.delete() + + def remove_descriptor(self, fileno): + for lcontainer in six.itervalues(self.listeners): + listener = lcontainer.pop(fileno, None) + if listener: + try: + listener.cb.delete() + except self.SYSTEM_EXCEPTIONS: + raise + except: + traceback.print_exc() + + def schedule_call_local(self, seconds, cb, *args, **kwargs): + current = greenlet.getcurrent() + if current is self.greenlet: + return self.schedule_call_global(seconds, cb, *args, **kwargs) + event_impl = event.event(_scheduled_call_local, (cb, args, kwargs, current)) + wrapper = event_wrapper(event_impl, seconds=seconds) + self.events_to_add.append(wrapper) + return wrapper + + schedule_call = schedule_call_local + + def schedule_call_global(self, seconds, cb, *args, **kwargs): + event_impl = event.event(_scheduled_call, (cb, args, kwargs)) + wrapper = event_wrapper(event_impl, seconds=seconds) + self.events_to_add.append(wrapper) + return wrapper + + def _version_info(self): + baseversion = event.__version__ + return baseversion + + +def _scheduled_call(event_impl, handle, evtype, arg): + cb, args, kwargs = arg + try: + cb(*args, **kwargs) + finally: + event_impl.delete() + + +def _scheduled_call_local(event_impl, handle, evtype, arg): + cb, args, kwargs, caller_greenlet = arg + try: + if not caller_greenlet.dead: + cb(*args, **kwargs) + finally: + event_impl.delete() diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/selects.py b/venv/lib/python3.12/site-packages/eventlet/hubs/selects.py new file mode 100644 index 0000000..0ead5b8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/hubs/selects.py @@ -0,0 +1,64 @@ +import errno +import sys +from eventlet import patcher, support +from eventlet.hubs import hub +select = patcher.original('select') +time = patcher.original('time') + +try: + BAD_SOCK = set((errno.EBADF, errno.WSAENOTSOCK)) +except AttributeError: + BAD_SOCK = set((errno.EBADF,)) + + +def is_available(): + return hasattr(select, 'select') + + +class Hub(hub.BaseHub): + def _remove_bad_fds(self): + """ Iterate through fds, removing the ones that are bad per the + operating system. + """ + all_fds = list(self.listeners[self.READ]) + list(self.listeners[self.WRITE]) + for fd in all_fds: + try: + select.select([fd], [], [], 0) + except select.error as e: + if support.get_errno(e) in BAD_SOCK: + self.remove_descriptor(fd) + + def wait(self, seconds=None): + readers = self.listeners[self.READ] + writers = self.listeners[self.WRITE] + if not readers and not writers: + if seconds: + time.sleep(seconds) + return + reader_fds = list(readers) + writer_fds = list(writers) + all_fds = reader_fds + writer_fds + try: + r, w, er = select.select(reader_fds, writer_fds, all_fds, seconds) + except select.error as e: + if support.get_errno(e) == errno.EINTR: + return + elif support.get_errno(e) in BAD_SOCK: + self._remove_bad_fds() + return + else: + raise + + for fileno in er: + readers.get(fileno, hub.noop).cb(fileno) + writers.get(fileno, hub.noop).cb(fileno) + + for listeners, events in ((readers, r), (writers, w)): + for fileno in events: + try: + listeners.get(fileno, hub.noop).cb(fileno) + except self.SYSTEM_EXCEPTIONS: + raise + except: + self.squelch_exception(fileno, sys.exc_info()) + support.clear_sys_exc_info() diff --git a/venv/lib/python3.12/site-packages/eventlet/hubs/timer.py b/venv/lib/python3.12/site-packages/eventlet/hubs/timer.py new file mode 100644 index 0000000..1dfd561 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/hubs/timer.py @@ -0,0 +1,106 @@ +import traceback + +import eventlet.hubs +from eventlet.support import greenlets as greenlet +import six + +""" If true, captures a stack trace for each timer when constructed. This is +useful for debugging leaking timers, to find out where the timer was set up. """ +_g_debug = False + + +class Timer(object): + def __init__(self, seconds, cb, *args, **kw): + """Create a timer. + seconds: The minimum number of seconds to wait before calling + cb: The callback to call when the timer has expired + *args: The arguments to pass to cb + **kw: The keyword arguments to pass to cb + + This timer will not be run unless it is scheduled in a runloop by + calling timer.schedule() or runloop.add_timer(timer). + """ + self.seconds = seconds + self.tpl = cb, args, kw + self.called = False + if _g_debug: + self.traceback = six.StringIO() + traceback.print_stack(file=self.traceback) + + @property + def pending(self): + return not self.called + + def __repr__(self): + secs = getattr(self, 'seconds', None) + cb, args, kw = getattr(self, 'tpl', (None, None, None)) + retval = "Timer(%s, %s, *%s, **%s)" % ( + secs, cb, args, kw) + if _g_debug and hasattr(self, 'traceback'): + retval += '\n' + self.traceback.getvalue() + return retval + + def copy(self): + cb, args, kw = self.tpl + return self.__class__(self.seconds, cb, *args, **kw) + + def schedule(self): + """Schedule this timer to run in the current runloop. + """ + self.called = False + self.scheduled_time = eventlet.hubs.get_hub().add_timer(self) + return self + + def __call__(self, *args): + if not self.called: + self.called = True + cb, args, kw = self.tpl + try: + cb(*args, **kw) + finally: + try: + del self.tpl + except AttributeError: + pass + + def cancel(self): + """Prevent this timer from being called. If the timer has already + been called or canceled, has no effect. + """ + if not self.called: + self.called = True + eventlet.hubs.get_hub().timer_canceled(self) + try: + del self.tpl + except AttributeError: + pass + + # No default ordering in 3.x. heapq uses < + # FIXME should full set be added? + def __lt__(self, other): + return id(self) < id(other) + + +class LocalTimer(Timer): + + def __init__(self, *args, **kwargs): + self.greenlet = greenlet.getcurrent() + Timer.__init__(self, *args, **kwargs) + + @property + def pending(self): + if self.greenlet is None or self.greenlet.dead: + return False + return not self.called + + def __call__(self, *args): + if not self.called: + self.called = True + if self.greenlet is not None and self.greenlet.dead: + return + cb, args, kw = self.tpl + cb(*args, **kw) + + def cancel(self): + self.greenlet = None + Timer.cancel(self) diff --git a/venv/lib/python3.12/site-packages/eventlet/lock.py b/venv/lib/python3.12/site-packages/eventlet/lock.py new file mode 100644 index 0000000..4b21e0b --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/lock.py @@ -0,0 +1,37 @@ +from eventlet import hubs +from eventlet.semaphore import Semaphore + + +class Lock(Semaphore): + + """A lock. + This is API-compatible with :class:`threading.Lock`. + + It is a context manager, and thus can be used in a with block:: + + lock = Lock() + with lock: + do_some_stuff() + """ + + def release(self, blocking=True): + """Modify behaviour vs :class:`Semaphore` to raise a RuntimeError + exception if the value is greater than zero. This corrects behaviour + to realign with :class:`threading.Lock`. + """ + if self.counter > 0: + raise RuntimeError("release unlocked lock") + + # Consciously *do not* call super().release(), but instead inline + # Semaphore.release() here. We've seen issues with logging._lock + # deadlocking because garbage collection happened to run mid-release + # and eliminating the extra stack frame should help prevent that. + # See https://github.com/eventlet/eventlet/issues/742 + self.counter += 1 + if self._waiters: + hubs.get_hub().schedule_call_global(0, self._do_acquire) + return True + + def _at_fork_reinit(self): + self.counter = 1 + self._waiters.clear() diff --git a/venv/lib/python3.12/site-packages/eventlet/patcher.py b/venv/lib/python3.12/site-packages/eventlet/patcher.py new file mode 100644 index 0000000..b249d6f --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/patcher.py @@ -0,0 +1,530 @@ +try: + import _imp as imp +except ImportError: + import imp +import sys +try: + # Only for this purpose, it's irrelevant if `os` was already patched. + # https://github.com/eventlet/eventlet/pull/661 + from os import register_at_fork +except ImportError: + register_at_fork = None + +import eventlet +import six + + +__all__ = ['inject', 'import_patched', 'monkey_patch', 'is_monkey_patched'] + +__exclude = set(('__builtins__', '__file__', '__name__')) + + +class SysModulesSaver(object): + """Class that captures some subset of the current state of + sys.modules. Pass in an iterator of module names to the + constructor.""" + + def __init__(self, module_names=()): + self._saved = {} + imp.acquire_lock() + self.save(*module_names) + + def save(self, *module_names): + """Saves the named modules to the object.""" + for modname in module_names: + self._saved[modname] = sys.modules.get(modname, None) + + def restore(self): + """Restores the modules that the saver knows about into + sys.modules. + """ + try: + for modname, mod in six.iteritems(self._saved): + if mod is not None: + sys.modules[modname] = mod + else: + try: + del sys.modules[modname] + except KeyError: + pass + finally: + imp.release_lock() + + +def inject(module_name, new_globals, *additional_modules): + """Base method for "injecting" greened modules into an imported module. It + imports the module specified in *module_name*, arranging things so + that the already-imported modules in *additional_modules* are used when + *module_name* makes its imports. + + **Note:** This function does not create or change any sys.modules item, so + if your greened module use code like 'sys.modules["your_module_name"]', you + need to update sys.modules by yourself. + + *new_globals* is either None or a globals dictionary that gets populated + with the contents of the *module_name* module. This is useful when creating + a "green" version of some other module. + + *additional_modules* should be a collection of two-element tuples, of the + form (, ). If it's not specified, a default selection of + name/module pairs is used, which should cover all use cases but may be + slower because there are inevitably redundant or unnecessary imports. + """ + patched_name = '__patched_module_' + module_name + if patched_name in sys.modules: + # returning already-patched module so as not to destroy existing + # references to patched modules + return sys.modules[patched_name] + + if not additional_modules: + # supply some defaults + additional_modules = ( + _green_os_modules() + + _green_select_modules() + + _green_socket_modules() + + _green_thread_modules() + + _green_time_modules()) + # _green_MySQLdb()) # enable this after a short baking-in period + + # after this we are gonna screw with sys.modules, so capture the + # state of all the modules we're going to mess with, and lock + saver = SysModulesSaver([name for name, m in additional_modules]) + saver.save(module_name) + + # Cover the target modules so that when you import the module it + # sees only the patched versions + for name, mod in additional_modules: + sys.modules[name] = mod + + # Remove the old module from sys.modules and reimport it while + # the specified modules are in place + sys.modules.pop(module_name, None) + # Also remove sub modules and reimport. Use copy the keys to list + # because of the pop operations will change the content of sys.modules + # within th loop + for imported_module_name in list(sys.modules.keys()): + if imported_module_name.startswith(module_name + '.'): + sys.modules.pop(imported_module_name, None) + try: + module = __import__(module_name, {}, {}, module_name.split('.')[:-1]) + + if new_globals is not None: + # Update the given globals dictionary with everything from this new module + for name in dir(module): + if name not in __exclude: + new_globals[name] = getattr(module, name) + + # Keep a reference to the new module to prevent it from dying + sys.modules[patched_name] = module + finally: + saver.restore() # Put the original modules back + + return module + + +def import_patched(module_name, *additional_modules, **kw_additional_modules): + """Imports a module in a way that ensures that the module uses "green" + versions of the standard library modules, so that everything works + nonblockingly. + + The only required argument is the name of the module to be imported. + """ + return inject( + module_name, + None, + *additional_modules + tuple(kw_additional_modules.items())) + + +def patch_function(func, *additional_modules): + """Decorator that returns a version of the function that patches + some modules for the duration of the function call. This is + deeply gross and should only be used for functions that import + network libraries within their function bodies that there is no + way of getting around.""" + if not additional_modules: + # supply some defaults + additional_modules = ( + _green_os_modules() + + _green_select_modules() + + _green_socket_modules() + + _green_thread_modules() + + _green_time_modules()) + + def patched(*args, **kw): + saver = SysModulesSaver() + for name, mod in additional_modules: + saver.save(name) + sys.modules[name] = mod + try: + return func(*args, **kw) + finally: + saver.restore() + return patched + + +def _original_patch_function(func, *module_names): + """Kind of the contrapositive of patch_function: decorates a + function such that when it's called, sys.modules is populated only + with the unpatched versions of the specified modules. Unlike + patch_function, only the names of the modules need be supplied, + and there are no defaults. This is a gross hack; tell your kids not + to import inside function bodies!""" + def patched(*args, **kw): + saver = SysModulesSaver(module_names) + for name in module_names: + sys.modules[name] = original(name) + try: + return func(*args, **kw) + finally: + saver.restore() + return patched + + +def original(modname): + """ This returns an unpatched version of a module; this is useful for + Eventlet itself (i.e. tpool).""" + # note that it's not necessary to temporarily install unpatched + # versions of all patchable modules during the import of the + # module; this is because none of them import each other, except + # for threading which imports thread + original_name = '__original_module_' + modname + if original_name in sys.modules: + return sys.modules.get(original_name) + + # re-import the "pure" module and store it in the global _originals + # dict; be sure to restore whatever module had that name already + saver = SysModulesSaver((modname,)) + sys.modules.pop(modname, None) + # some rudimentary dependency checking -- fortunately the modules + # we're working on don't have many dependencies so we can just do + # some special-casing here + if six.PY2: + deps = {'threading': 'thread', 'Queue': 'threading'} + if six.PY3: + deps = {'threading': '_thread', 'queue': 'threading'} + if modname in deps: + dependency = deps[modname] + saver.save(dependency) + sys.modules[dependency] = original(dependency) + try: + real_mod = __import__(modname, {}, {}, modname.split('.')[:-1]) + if modname in ('Queue', 'queue') and not hasattr(real_mod, '_threading'): + # tricky hack: Queue's constructor in <2.7 imports + # threading on every instantiation; therefore we wrap + # it so that it always gets the original threading + real_mod.Queue.__init__ = _original_patch_function( + real_mod.Queue.__init__, + 'threading') + # save a reference to the unpatched module so it doesn't get lost + sys.modules[original_name] = real_mod + finally: + saver.restore() + + return sys.modules[original_name] + +already_patched = {} + + +def monkey_patch(**on): + """Globally patches certain system modules to be greenthread-friendly. + + The keyword arguments afford some control over which modules are patched. + If no keyword arguments are supplied, all possible modules are patched. + If keywords are set to True, only the specified modules are patched. E.g., + ``monkey_patch(socket=True, select=True)`` patches only the select and + socket modules. Most arguments patch the single module of the same name + (os, time, select). The exceptions are socket, which also patches the ssl + module if present; and thread, which patches thread, threading, and Queue. + + It's safe to call monkey_patch multiple times. + """ + + # Workaround for import cycle observed as following in monotonic + # RuntimeError: no suitable implementation for this system + # see https://github.com/eventlet/eventlet/issues/401#issuecomment-325015989 + # + # Make sure the hub is completely imported before any + # monkey-patching, or we risk recursion if the process of importing + # the hub calls into monkey-patched modules. + eventlet.hubs.get_hub() + + accepted_args = set(('os', 'select', 'socket', + 'thread', 'time', 'psycopg', 'MySQLdb', + 'builtins', 'subprocess')) + # To make sure only one of them is passed here + assert not ('__builtin__' in on and 'builtins' in on) + try: + b = on.pop('__builtin__') + except KeyError: + pass + else: + on['builtins'] = b + + default_on = on.pop("all", None) + + for k in six.iterkeys(on): + if k not in accepted_args: + raise TypeError("monkey_patch() got an unexpected " + "keyword argument %r" % k) + if default_on is None: + default_on = not (True in on.values()) + for modname in accepted_args: + if modname == 'MySQLdb': + # MySQLdb is only on when explicitly patched for the moment + on.setdefault(modname, False) + if modname == 'builtins': + on.setdefault(modname, False) + on.setdefault(modname, default_on) + + if on['thread'] and not already_patched.get('thread'): + _green_existing_locks() + + modules_to_patch = [] + for name, modules_function in [ + ('os', _green_os_modules), + ('select', _green_select_modules), + ('socket', _green_socket_modules), + ('thread', _green_thread_modules), + ('time', _green_time_modules), + ('MySQLdb', _green_MySQLdb), + ('builtins', _green_builtins), + ('subprocess', _green_subprocess_modules), + ]: + if on[name] and not already_patched.get(name): + modules_to_patch += modules_function() + already_patched[name] = True + + if on['psycopg'] and not already_patched.get('psycopg'): + try: + from eventlet.support import psycopg2_patcher + psycopg2_patcher.make_psycopg_green() + already_patched['psycopg'] = True + except ImportError: + # note that if we get an importerror from trying to + # monkeypatch psycopg, we will continually retry it + # whenever monkey_patch is called; this should not be a + # performance problem but it allows is_monkey_patched to + # tell us whether or not we succeeded + pass + + _threading = original('threading') + imp.acquire_lock() + try: + for name, mod in modules_to_patch: + orig_mod = sys.modules.get(name) + if orig_mod is None: + orig_mod = __import__(name) + for attr_name in mod.__patched__: + patched_attr = getattr(mod, attr_name, None) + if patched_attr is not None: + setattr(orig_mod, attr_name, patched_attr) + deleted = getattr(mod, '__deleted__', []) + for attr_name in deleted: + if hasattr(orig_mod, attr_name): + delattr(orig_mod, attr_name) + + # https://github.com/eventlet/eventlet/issues/592 + if name == 'threading' and register_at_fork: + def fix_threading_active( + _global_dict=_threading.current_thread.__globals__, + # alias orig_mod as patched to reflect its new state + # https://github.com/eventlet/eventlet/pull/661#discussion_r509877481 + _patched=orig_mod, + ): + _prefork_active = [None] + + def before_fork(): + _prefork_active[0] = _global_dict['_active'] + _global_dict['_active'] = _patched._active + + def after_fork(): + _global_dict['_active'] = _prefork_active[0] + + register_at_fork( + before=before_fork, + after_in_parent=after_fork) + fix_threading_active() + finally: + imp.release_lock() + + if sys.version_info >= (3, 3): + import importlib._bootstrap + thread = original('_thread') + # importlib must use real thread locks, not eventlet.Semaphore + importlib._bootstrap._thread = thread + + # Issue #185: Since Python 3.3, threading.RLock is implemented in C and + # so call a C function to get the thread identifier, instead of calling + # threading.get_ident(). Force the Python implementation of RLock which + # calls threading.get_ident() and so is compatible with eventlet. + import threading + threading.RLock = threading._PyRLock + + # Issue #508: Since Python 3.7 queue.SimpleQueue is implemented in C, + # causing a deadlock. Replace the C implementation with the Python one. + if sys.version_info >= (3, 7): + import queue + queue.SimpleQueue = queue._PySimpleQueue + + +def is_monkey_patched(module): + """Returns True if the given module is monkeypatched currently, False if + not. *module* can be either the module itself or its name. + + Based entirely off the name of the module, so if you import a + module some other way than with the import keyword (including + import_patched), this might not be correct about that particular + module.""" + return module in already_patched or \ + getattr(module, '__name__', None) in already_patched + + +def _green_existing_locks(): + """Make locks created before monkey-patching safe. + + RLocks rely on a Lock and on Python 2, if an unpatched Lock blocks, it + blocks the native thread. We need to replace these with green Locks. + + This was originally noticed in the stdlib logging module.""" + import gc + import threading + import eventlet.green.thread + lock_type = type(threading.Lock()) + rlock_type = type(threading.RLock()) + if hasattr(threading, '_PyRLock'): + # this happens on CPython3 and PyPy >= 7.0.0: "py3-style" rlocks, they + # are implemented natively in C and RPython respectively + py3_style = True + pyrlock_type = type(threading._PyRLock()) + else: + # this happens on CPython2.7 and PyPy < 7.0.0: "py2-style" rlocks, + # they are implemented in pure-python + py3_style = False + pyrlock_type = None + + # We're monkey-patching so there can't be any greenlets yet, ergo our thread + # ID is the only valid owner possible. + tid = eventlet.green.thread.get_ident() + for obj in gc.get_objects(): + if isinstance(obj, rlock_type): + if not py3_style and isinstance(obj._RLock__block, lock_type): + _fix_py2_rlock(obj, tid) + elif py3_style and not isinstance(obj, pyrlock_type): + _fix_py3_rlock(obj) + + +def _fix_py2_rlock(rlock, tid): + import eventlet.green.threading + old = rlock._RLock__block + new = eventlet.green.threading.Lock() + rlock._RLock__block = new + if old.locked(): + new.acquire() + rlock._RLock__owner = tid + + +def _fix_py3_rlock(old): + import gc + import threading + new = threading._PyRLock() + while old._is_owned(): + old.release() + new.acquire() + if old._is_owned(): + new.acquire() + gc.collect() + for ref in gc.get_referrers(old): + try: + ref_vars = vars(ref) + except TypeError: + pass + else: + for k, v in ref_vars.items(): + if v == old: + setattr(ref, k, new) + + +def _green_os_modules(): + from eventlet.green import os + return [('os', os)] + + +def _green_select_modules(): + from eventlet.green import select + modules = [('select', select)] + + if sys.version_info >= (3, 4): + from eventlet.green import selectors + modules.append(('selectors', selectors)) + + return modules + + +def _green_socket_modules(): + from eventlet.green import socket + try: + from eventlet.green import ssl + return [('socket', socket), ('ssl', ssl)] + except ImportError: + return [('socket', socket)] + + +def _green_subprocess_modules(): + from eventlet.green import subprocess + return [('subprocess', subprocess)] + + +def _green_thread_modules(): + from eventlet.green import Queue + from eventlet.green import thread + from eventlet.green import threading + if six.PY2: + return [('Queue', Queue), ('thread', thread), ('threading', threading)] + if six.PY3: + return [('queue', Queue), ('_thread', thread), ('threading', threading)] + + +def _green_time_modules(): + from eventlet.green import time + return [('time', time)] + + +def _green_MySQLdb(): + try: + from eventlet.green import MySQLdb + return [('MySQLdb', MySQLdb)] + except ImportError: + return [] + + +def _green_builtins(): + try: + from eventlet.green import builtin + return [('__builtin__' if six.PY2 else 'builtins', builtin)] + except ImportError: + return [] + + +def slurp_properties(source, destination, ignore=[], srckeys=None): + """Copy properties from *source* (assumed to be a module) to + *destination* (assumed to be a dict). + + *ignore* lists properties that should not be thusly copied. + *srckeys* is a list of keys to copy, if the source's __all__ is + untrustworthy. + """ + if srckeys is None: + srckeys = source.__all__ + destination.update(dict([ + (name, getattr(source, name)) + for name in srckeys + if not (name.startswith('__') or name in ignore) + ])) + + +if __name__ == "__main__": + sys.argv.pop(0) + monkey_patch() + with open(sys.argv[0]) as f: + code = compile(f.read(), sys.argv[0], 'exec') + exec(code) diff --git a/venv/lib/python3.12/site-packages/eventlet/pools.py b/venv/lib/python3.12/site-packages/eventlet/pools.py new file mode 100644 index 0000000..ee9b77b --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/pools.py @@ -0,0 +1,186 @@ +from __future__ import print_function + +import collections +from contextlib import contextmanager + +from eventlet import queue + + +__all__ = ['Pool', 'TokenPool'] + + +class Pool(object): + """ + Pool class implements resource limitation and construction. + + There are two ways of using Pool: passing a `create` argument or + subclassing. In either case you must provide a way to create + the resource. + + When using `create` argument, pass a function with no arguments:: + + http_pool = pools.Pool(create=httplib2.Http) + + If you need to pass arguments, build a nullary function with either + `lambda` expression:: + + http_pool = pools.Pool(create=lambda: httplib2.Http(timeout=90)) + + or :func:`functools.partial`:: + + from functools import partial + http_pool = pools.Pool(create=partial(httplib2.Http, timeout=90)) + + When subclassing, define only the :meth:`create` method + to implement the desired resource:: + + class MyPool(pools.Pool): + def create(self): + return MyObject() + + If using 2.5 or greater, the :meth:`item` method acts as a context manager; + that's the best way to use it:: + + with mypool.item() as thing: + thing.dostuff() + + The maximum size of the pool can be modified at runtime via + the :meth:`resize` method. + + Specifying a non-zero *min-size* argument pre-populates the pool with + *min_size* items. *max-size* sets a hard limit to the size of the pool -- + it cannot contain any more items than *max_size*, and if there are already + *max_size* items 'checked out' of the pool, the pool will cause any + greenthread calling :meth:`get` to cooperatively yield until an item + is :meth:`put` in. + """ + + def __init__(self, min_size=0, max_size=4, order_as_stack=False, create=None): + """*order_as_stack* governs the ordering of the items in the free pool. + If ``False`` (the default), the free items collection (of items that + were created and were put back in the pool) acts as a round-robin, + giving each item approximately equal utilization. If ``True``, the + free pool acts as a FILO stack, which preferentially re-uses items that + have most recently been used. + """ + self.min_size = min_size + self.max_size = max_size + self.order_as_stack = order_as_stack + self.current_size = 0 + self.channel = queue.LightQueue(0) + self.free_items = collections.deque() + if create is not None: + self.create = create + + for x in range(min_size): + self.current_size += 1 + self.free_items.append(self.create()) + + def get(self): + """Return an item from the pool, when one is available. This may + cause the calling greenthread to block. + """ + if self.free_items: + return self.free_items.popleft() + self.current_size += 1 + if self.current_size <= self.max_size: + try: + created = self.create() + except: + self.current_size -= 1 + raise + return created + self.current_size -= 1 # did not create + return self.channel.get() + + @contextmanager + def item(self): + """ Get an object out of the pool, for use with with statement. + + >>> from eventlet import pools + >>> pool = pools.TokenPool(max_size=4) + >>> with pool.item() as obj: + ... print("got token") + ... + got token + >>> pool.free() + 4 + """ + obj = self.get() + try: + yield obj + finally: + self.put(obj) + + def put(self, item): + """Put an item back into the pool, when done. This may + cause the putting greenthread to block. + """ + if self.current_size > self.max_size: + self.current_size -= 1 + return + + if self.waiting(): + try: + self.channel.put(item, block=False) + return + except queue.Full: + pass + + if self.order_as_stack: + self.free_items.appendleft(item) + else: + self.free_items.append(item) + + def resize(self, new_size): + """Resize the pool to *new_size*. + + Adjusting this number does not affect existing items checked out of + the pool, nor on any greenthreads who are waiting for an item to free + up. Some indeterminate number of :meth:`get`/:meth:`put` + cycles will be necessary before the new maximum size truly matches + the actual operation of the pool. + """ + self.max_size = new_size + + def free(self): + """Return the number of free items in the pool. This corresponds + to the number of :meth:`get` calls needed to empty the pool. + """ + return len(self.free_items) + self.max_size - self.current_size + + def waiting(self): + """Return the number of routines waiting for a pool item. + """ + return max(0, self.channel.getting() - self.channel.putting()) + + def create(self): + """Generate a new pool item. In order for the pool to + function, either this method must be overriden in a subclass + or the pool must be constructed with the `create` argument. + It accepts no arguments and returns a single instance of + whatever thing the pool is supposed to contain. + + In general, :meth:`create` is called whenever the pool exceeds its + previous high-water mark of concurrently-checked-out-items. In other + words, in a new pool with *min_size* of 0, the very first call + to :meth:`get` will result in a call to :meth:`create`. If the first + caller calls :meth:`put` before some other caller calls :meth:`get`, + then the first item will be returned, and :meth:`create` will not be + called a second time. + """ + raise NotImplementedError("Implement in subclass") + + +class Token(object): + pass + + +class TokenPool(Pool): + """A pool which gives out tokens (opaque unique objects), which indicate + that the coroutine which holds the token has a right to consume some + limited resource. + """ + + def create(self): + return Token() diff --git a/venv/lib/python3.12/site-packages/eventlet/queue.py b/venv/lib/python3.12/site-packages/eventlet/queue.py new file mode 100644 index 0000000..b61c2f8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/queue.py @@ -0,0 +1,492 @@ +# Copyright (c) 2009 Denis Bilenko, denis.bilenko at gmail com +# Copyright (c) 2010 Eventlet Contributors (see AUTHORS) +# and licensed under the MIT license: +# +# 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. + +"""Synchronized queues. + +The :mod:`eventlet.queue` module implements multi-producer, multi-consumer +queues that work across greenlets, with the API similar to the classes found in +the standard :mod:`Queue` and :class:`multiprocessing ` +modules. + +A major difference is that queues in this module operate as channels when +initialized with *maxsize* of zero. In such case, both :meth:`Queue.empty` +and :meth:`Queue.full` return ``True`` and :meth:`Queue.put` always blocks until +a call to :meth:`Queue.get` retrieves the item. + +An interesting difference, made possible because of greenthreads, is +that :meth:`Queue.qsize`, :meth:`Queue.empty`, and :meth:`Queue.full` *can* be +used as indicators of whether the subsequent :meth:`Queue.get` +or :meth:`Queue.put` will not block. The new methods :meth:`Queue.getting` +and :meth:`Queue.putting` report on the number of greenthreads blocking +in :meth:`put ` or :meth:`get ` respectively. +""" +from __future__ import print_function + +import sys +import heapq +import collections +import traceback + +from eventlet.event import Event +from eventlet.greenthread import getcurrent +from eventlet.hubs import get_hub +import six +from six.moves import queue as Stdlib_Queue +from eventlet.timeout import Timeout + + +__all__ = ['Queue', 'PriorityQueue', 'LifoQueue', 'LightQueue', 'Full', 'Empty'] + +_NONE = object() +Full = six.moves.queue.Full +Empty = six.moves.queue.Empty + + +class Waiter(object): + """A low level synchronization class. + + Wrapper around greenlet's ``switch()`` and ``throw()`` calls that makes them safe: + + * switching will occur only if the waiting greenlet is executing :meth:`wait` + method currently. Otherwise, :meth:`switch` and :meth:`throw` are no-ops. + * any error raised in the greenlet is handled inside :meth:`switch` and :meth:`throw` + + The :meth:`switch` and :meth:`throw` methods must only be called from the :class:`Hub` greenlet. + The :meth:`wait` method must be called from a greenlet other than :class:`Hub`. + """ + __slots__ = ['greenlet'] + + def __init__(self): + self.greenlet = None + + def __repr__(self): + if self.waiting: + waiting = ' waiting' + else: + waiting = '' + return '<%s at %s%s greenlet=%r>' % ( + type(self).__name__, hex(id(self)), waiting, self.greenlet, + ) + + def __str__(self): + """ + >>> print(Waiter()) + + """ + if self.waiting: + waiting = ' waiting' + else: + waiting = '' + return '<%s%s greenlet=%s>' % (type(self).__name__, waiting, self.greenlet) + + def __nonzero__(self): + return self.greenlet is not None + + __bool__ = __nonzero__ + + @property + def waiting(self): + return self.greenlet is not None + + def switch(self, value=None): + """Wake up the greenlet that is calling wait() currently (if there is one). + Can only be called from Hub's greenlet. + """ + assert getcurrent() is get_hub( + ).greenlet, "Can only use Waiter.switch method from the mainloop" + if self.greenlet is not None: + try: + self.greenlet.switch(value) + except Exception: + traceback.print_exc() + + def throw(self, *throw_args): + """Make greenlet calling wait() wake up (if there is a wait()). + Can only be called from Hub's greenlet. + """ + assert getcurrent() is get_hub( + ).greenlet, "Can only use Waiter.switch method from the mainloop" + if self.greenlet is not None: + try: + self.greenlet.throw(*throw_args) + except Exception: + traceback.print_exc() + + # XXX should be renamed to get() ? and the whole class is called Receiver? + def wait(self): + """Wait until switch() or throw() is called. + """ + assert self.greenlet is None, 'This Waiter is already used by %r' % (self.greenlet, ) + self.greenlet = getcurrent() + try: + return get_hub().switch() + finally: + self.greenlet = None + + +class LightQueue(object): + """ + This is a variant of Queue that behaves mostly like the standard + :class:`Stdlib_Queue`. It differs by not supporting the + :meth:`task_done ` or + :meth:`join ` methods, and is a little faster for + not having that overhead. + """ + + def __init__(self, maxsize=None): + if maxsize is None or maxsize < 0: # None is not comparable in 3.x + self.maxsize = None + else: + self.maxsize = maxsize + self.getters = set() + self.putters = set() + self._event_unlock = None + self._init(maxsize) + + # QQQ make maxsize into a property with setter that schedules unlock if necessary + + def _init(self, maxsize): + self.queue = collections.deque() + + def _get(self): + return self.queue.popleft() + + def _put(self, item): + self.queue.append(item) + + def __repr__(self): + return '<%s at %s %s>' % (type(self).__name__, hex(id(self)), self._format()) + + def __str__(self): + return '<%s %s>' % (type(self).__name__, self._format()) + + def _format(self): + result = 'maxsize=%r' % (self.maxsize, ) + if getattr(self, 'queue', None): + result += ' queue=%r' % self.queue + if self.getters: + result += ' getters[%s]' % len(self.getters) + if self.putters: + result += ' putters[%s]' % len(self.putters) + if self._event_unlock is not None: + result += ' unlocking' + return result + + def qsize(self): + """Return the size of the queue.""" + return len(self.queue) + + def resize(self, size): + """Resizes the queue's maximum size. + + If the size is increased, and there are putters waiting, they may be woken up.""" + # None is not comparable in 3.x + if self.maxsize is not None and (size is None or size > self.maxsize): + # Maybe wake some stuff up + self._schedule_unlock() + self.maxsize = size + + def putting(self): + """Returns the number of greenthreads that are blocked waiting to put + items into the queue.""" + return len(self.putters) + + def getting(self): + """Returns the number of greenthreads that are blocked waiting on an + empty queue.""" + return len(self.getters) + + def empty(self): + """Return ``True`` if the queue is empty, ``False`` otherwise.""" + return not self.qsize() + + def full(self): + """Return ``True`` if the queue is full, ``False`` otherwise. + + ``Queue(None)`` is never full. + """ + # None is not comparable in 3.x + return self.maxsize is not None and self.qsize() >= self.maxsize + + def put(self, item, block=True, timeout=None): + """Put an item into the queue. + + If optional arg *block* is true and *timeout* is ``None`` (the default), + block if necessary until a free slot is available. If *timeout* is + a positive number, it blocks at most *timeout* seconds and raises + the :class:`Full` exception if no free slot was available within that time. + Otherwise (*block* is false), put an item on the queue if a free slot + is immediately available, else raise the :class:`Full` exception (*timeout* + is ignored in that case). + """ + if self.maxsize is None or self.qsize() < self.maxsize: + # there's a free slot, put an item right away + self._put(item) + if self.getters: + self._schedule_unlock() + elif not block and get_hub().greenlet is getcurrent(): + # we're in the mainloop, so we cannot wait; we can switch() to other greenlets though + # find a getter and deliver an item to it + while self.getters: + getter = self.getters.pop() + if getter: + self._put(item) + item = self._get() + getter.switch(item) + return + raise Full + elif block: + waiter = ItemWaiter(item, block) + self.putters.add(waiter) + timeout = Timeout(timeout, Full) + try: + if self.getters: + self._schedule_unlock() + result = waiter.wait() + assert result is waiter, "Invalid switch into Queue.put: %r" % (result, ) + if waiter.item is not _NONE: + self._put(item) + finally: + timeout.cancel() + self.putters.discard(waiter) + elif self.getters: + waiter = ItemWaiter(item, block) + self.putters.add(waiter) + self._schedule_unlock() + result = waiter.wait() + assert result is waiter, "Invalid switch into Queue.put: %r" % (result, ) + if waiter.item is not _NONE: + raise Full + else: + raise Full + + def put_nowait(self, item): + """Put an item into the queue without blocking. + + Only enqueue the item if a free slot is immediately available. + Otherwise raise the :class:`Full` exception. + """ + self.put(item, False) + + def get(self, block=True, timeout=None): + """Remove and return an item from the queue. + + If optional args *block* is true and *timeout* is ``None`` (the default), + block if necessary until an item is available. If *timeout* is a positive number, + it blocks at most *timeout* seconds and raises the :class:`Empty` exception + if no item was available within that time. Otherwise (*block* is false), return + an item if one is immediately available, else raise the :class:`Empty` exception + (*timeout* is ignored in that case). + """ + if self.qsize(): + if self.putters: + self._schedule_unlock() + return self._get() + elif not block and get_hub().greenlet is getcurrent(): + # special case to make get_nowait() runnable in the mainloop greenlet + # there are no items in the queue; try to fix the situation by unlocking putters + while self.putters: + putter = self.putters.pop() + if putter: + putter.switch(putter) + if self.qsize(): + return self._get() + raise Empty + elif block: + waiter = Waiter() + timeout = Timeout(timeout, Empty) + try: + self.getters.add(waiter) + if self.putters: + self._schedule_unlock() + try: + return waiter.wait() + except: + self._schedule_unlock() + raise + finally: + self.getters.discard(waiter) + timeout.cancel() + else: + raise Empty + + def get_nowait(self): + """Remove and return an item from the queue without blocking. + + Only get an item if one is immediately available. Otherwise + raise the :class:`Empty` exception. + """ + return self.get(False) + + def _unlock(self): + try: + while True: + if self.qsize() and self.getters: + getter = self.getters.pop() + if getter: + try: + item = self._get() + except: + getter.throw(*sys.exc_info()) + else: + getter.switch(item) + elif self.putters and self.getters: + putter = self.putters.pop() + if putter: + getter = self.getters.pop() + if getter: + item = putter.item + # this makes greenlet calling put() not to call _put() again + putter.item = _NONE + self._put(item) + item = self._get() + getter.switch(item) + putter.switch(putter) + else: + self.putters.add(putter) + elif self.putters and (self.getters or + self.maxsize is None or + self.qsize() < self.maxsize): + putter = self.putters.pop() + putter.switch(putter) + elif self.putters and not self.getters: + full = [p for p in self.putters if not p.block] + if not full: + break + for putter in full: + self.putters.discard(putter) + get_hub().schedule_call_global( + 0, putter.greenlet.throw, Full) + else: + break + finally: + self._event_unlock = None # QQQ maybe it's possible to obtain this info from libevent? + # i.e. whether this event is pending _OR_ currently executing + # testcase: 2 greenlets: while True: q.put(q.get()) - nothing else has a change to execute + # to avoid this, schedule unlock with timer(0, ...) once in a while + + def _schedule_unlock(self): + if self._event_unlock is None: + self._event_unlock = get_hub().schedule_call_global(0, self._unlock) + + +class ItemWaiter(Waiter): + __slots__ = ['item', 'block'] + + def __init__(self, item, block): + Waiter.__init__(self) + self.item = item + self.block = block + + +class Queue(LightQueue): + '''Create a queue object with a given maximum size. + + If *maxsize* is less than zero or ``None``, the queue size is infinite. + + ``Queue(0)`` is a channel, that is, its :meth:`put` method always blocks + until the item is delivered. (This is unlike the standard + :class:`Stdlib_Queue`, where 0 means infinite size). + + In all other respects, this Queue class resembles the standard library, + :class:`Stdlib_Queue`. + ''' + + def __init__(self, maxsize=None): + LightQueue.__init__(self, maxsize) + self.unfinished_tasks = 0 + self._cond = Event() + + def _format(self): + result = LightQueue._format(self) + if self.unfinished_tasks: + result += ' tasks=%s _cond=%s' % (self.unfinished_tasks, self._cond) + return result + + def _put(self, item): + LightQueue._put(self, item) + self._put_bookkeeping() + + def _put_bookkeeping(self): + self.unfinished_tasks += 1 + if self._cond.ready(): + self._cond.reset() + + def task_done(self): + '''Indicate that a formerly enqueued task is complete. Used by queue consumer threads. + For each :meth:`get ` used to fetch a task, a subsequent call to + :meth:`task_done` tells the queue that the processing on the task is complete. + + If a :meth:`join` is currently blocking, it will resume when all items have been processed + (meaning that a :meth:`task_done` call was received for every item that had been + :meth:`put ` into the queue). + + Raises a :exc:`ValueError` if called more times than there were items placed in the queue. + ''' + + if self.unfinished_tasks <= 0: + raise ValueError('task_done() called too many times') + self.unfinished_tasks -= 1 + if self.unfinished_tasks == 0: + self._cond.send(None) + + def join(self): + '''Block until all items in the queue have been gotten and processed. + + The count of unfinished tasks goes up whenever an item is added to the queue. + The count goes down whenever a consumer thread calls :meth:`task_done` to indicate + that the item was retrieved and all work on it is complete. When the count of + unfinished tasks drops to zero, :meth:`join` unblocks. + ''' + if self.unfinished_tasks > 0: + self._cond.wait() + + +class PriorityQueue(Queue): + '''A subclass of :class:`Queue` that retrieves entries in priority order (lowest first). + + Entries are typically tuples of the form: ``(priority number, data)``. + ''' + + def _init(self, maxsize): + self.queue = [] + + def _put(self, item, heappush=heapq.heappush): + heappush(self.queue, item) + self._put_bookkeeping() + + def _get(self, heappop=heapq.heappop): + return heappop(self.queue) + + +class LifoQueue(Queue): + '''A subclass of :class:`Queue` that retrieves most recently added entries first.''' + + def _init(self, maxsize): + self.queue = [] + + def _put(self, item): + self.queue.append(item) + self._put_bookkeeping() + + def _get(self): + return self.queue.pop() diff --git a/venv/lib/python3.12/site-packages/eventlet/semaphore.py b/venv/lib/python3.12/site-packages/eventlet/semaphore.py new file mode 100644 index 0000000..18b5b05 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/semaphore.py @@ -0,0 +1,315 @@ +import collections + +import eventlet +from eventlet import hubs + + +class Semaphore(object): + + """An unbounded semaphore. + Optionally initialize with a resource *count*, then :meth:`acquire` and + :meth:`release` resources as needed. Attempting to :meth:`acquire` when + *count* is zero suspends the calling greenthread until *count* becomes + nonzero again. + + This is API-compatible with :class:`threading.Semaphore`. + + It is a context manager, and thus can be used in a with block:: + + sem = Semaphore(2) + with sem: + do_some_stuff() + + If not specified, *value* defaults to 1. + + It is possible to limit acquire time:: + + sem = Semaphore() + ok = sem.acquire(timeout=0.1) + # True if acquired, False if timed out. + + """ + + def __init__(self, value=1): + try: + value = int(value) + except ValueError as e: + msg = 'Semaphore() expect value :: int, actual: {0} {1}'.format(type(value), str(e)) + raise TypeError(msg) + if value < 0: + msg = 'Semaphore() expect value >= 0, actual: {0}'.format(repr(value)) + raise ValueError(msg) + self.counter = value + self._waiters = collections.deque() + + def __repr__(self): + params = (self.__class__.__name__, hex(id(self)), + self.counter, len(self._waiters)) + return '<%s at %s c=%s _w[%s]>' % params + + def __str__(self): + params = (self.__class__.__name__, self.counter, len(self._waiters)) + return '<%s c=%s _w[%s]>' % params + + def locked(self): + """Returns true if a call to acquire would block. + """ + return self.counter <= 0 + + def bounded(self): + """Returns False; for consistency with + :class:`~eventlet.semaphore.CappedSemaphore`. + """ + return False + + def acquire(self, blocking=True, timeout=None): + """Acquire a semaphore. + + When invoked without arguments: if the internal counter is larger than + zero on entry, decrement it by one and return immediately. If it is zero + on entry, block, waiting until some other thread has called release() to + make it larger than zero. This is done with proper interlocking so that + if multiple acquire() calls are blocked, release() will wake exactly one + of them up. The implementation may pick one at random, so the order in + which blocked threads are awakened should not be relied on. There is no + return value in this case. + + When invoked with blocking set to true, do the same thing as when called + without arguments, and return true. + + When invoked with blocking set to false, do not block. If a call without + an argument would block, return false immediately; otherwise, do the + same thing as when called without arguments, and return true. + + Timeout value must be strictly positive. + """ + if timeout == -1: + timeout = None + if timeout is not None and timeout < 0: + raise ValueError("timeout value must be strictly positive") + if not blocking: + if timeout is not None: + raise ValueError("can't specify timeout for non-blocking acquire") + timeout = 0 + if not blocking and self.locked(): + return False + + current_thread = eventlet.getcurrent() + + if self.counter <= 0 or self._waiters: + if current_thread not in self._waiters: + self._waiters.append(current_thread) + try: + if timeout is not None: + ok = False + with eventlet.Timeout(timeout, False): + while self.counter <= 0: + hubs.get_hub().switch() + ok = True + if not ok: + return False + else: + # If someone else is already in this wait loop, give them + # a chance to get out. + while True: + hubs.get_hub().switch() + if self.counter > 0: + break + finally: + try: + self._waiters.remove(current_thread) + except ValueError: + # Fine if its already been dropped. + pass + + self.counter -= 1 + return True + + def __enter__(self): + self.acquire() + + def release(self, blocking=True): + """Release a semaphore, incrementing the internal counter by one. When + it was zero on entry and another thread is waiting for it to become + larger than zero again, wake up that thread. + + The *blocking* argument is for consistency with CappedSemaphore and is + ignored + """ + self.counter += 1 + if self._waiters: + hubs.get_hub().schedule_call_global(0, self._do_acquire) + return True + + def _do_acquire(self): + if self._waiters and self.counter > 0: + waiter = self._waiters.popleft() + waiter.switch() + + def __exit__(self, typ, val, tb): + self.release() + + @property + def balance(self): + """An integer value that represents how many new calls to + :meth:`acquire` or :meth:`release` would be needed to get the counter to + 0. If it is positive, then its value is the number of acquires that can + happen before the next acquire would block. If it is negative, it is + the negative of the number of releases that would be required in order + to make the counter 0 again (one more release would push the counter to + 1 and unblock acquirers). It takes into account how many greenthreads + are currently blocking in :meth:`acquire`. + """ + # positive means there are free items + # zero means there are no free items but nobody has requested one + # negative means there are requests for items, but no items + return self.counter - len(self._waiters) + + +class BoundedSemaphore(Semaphore): + + """A bounded semaphore checks to make sure its current value doesn't exceed + its initial value. If it does, ValueError is raised. In most situations + semaphores are used to guard resources with limited capacity. If the + semaphore is released too many times it's a sign of a bug. If not given, + *value* defaults to 1. + """ + + def __init__(self, value=1): + super(BoundedSemaphore, self).__init__(value) + self.original_counter = value + + def release(self, blocking=True): + """Release a semaphore, incrementing the internal counter by one. If + the counter would exceed the initial value, raises ValueError. When + it was zero on entry and another thread is waiting for it to become + larger than zero again, wake up that thread. + + The *blocking* argument is for consistency with :class:`CappedSemaphore` + and is ignored + """ + if self.counter >= self.original_counter: + raise ValueError("Semaphore released too many times") + return super(BoundedSemaphore, self).release(blocking) + + +class CappedSemaphore(object): + + """A blockingly bounded semaphore. + + Optionally initialize with a resource *count*, then :meth:`acquire` and + :meth:`release` resources as needed. Attempting to :meth:`acquire` when + *count* is zero suspends the calling greenthread until count becomes nonzero + again. Attempting to :meth:`release` after *count* has reached *limit* + suspends the calling greenthread until *count* becomes less than *limit* + again. + + This has the same API as :class:`threading.Semaphore`, though its + semantics and behavior differ subtly due to the upper limit on calls + to :meth:`release`. It is **not** compatible with + :class:`threading.BoundedSemaphore` because it blocks when reaching *limit* + instead of raising a ValueError. + + It is a context manager, and thus can be used in a with block:: + + sem = CappedSemaphore(2) + with sem: + do_some_stuff() + """ + + def __init__(self, count, limit): + if count < 0: + raise ValueError("CappedSemaphore must be initialized with a " + "positive number, got %s" % count) + if count > limit: + # accidentally, this also catches the case when limit is None + raise ValueError("'count' cannot be more than 'limit'") + self.lower_bound = Semaphore(count) + self.upper_bound = Semaphore(limit - count) + + def __repr__(self): + params = (self.__class__.__name__, hex(id(self)), + self.balance, self.lower_bound, self.upper_bound) + return '<%s at %s b=%s l=%s u=%s>' % params + + def __str__(self): + params = (self.__class__.__name__, self.balance, + self.lower_bound, self.upper_bound) + return '<%s b=%s l=%s u=%s>' % params + + def locked(self): + """Returns true if a call to acquire would block. + """ + return self.lower_bound.locked() + + def bounded(self): + """Returns true if a call to release would block. + """ + return self.upper_bound.locked() + + def acquire(self, blocking=True): + """Acquire a semaphore. + + When invoked without arguments: if the internal counter is larger than + zero on entry, decrement it by one and return immediately. If it is zero + on entry, block, waiting until some other thread has called release() to + make it larger than zero. This is done with proper interlocking so that + if multiple acquire() calls are blocked, release() will wake exactly one + of them up. The implementation may pick one at random, so the order in + which blocked threads are awakened should not be relied on. There is no + return value in this case. + + When invoked with blocking set to true, do the same thing as when called + without arguments, and return true. + + When invoked with blocking set to false, do not block. If a call without + an argument would block, return false immediately; otherwise, do the + same thing as when called without arguments, and return true. + """ + if not blocking and self.locked(): + return False + self.upper_bound.release() + try: + return self.lower_bound.acquire() + except: + self.upper_bound.counter -= 1 + # using counter directly means that it can be less than zero. + # however I certainly don't need to wait here and I don't seem to have + # a need to care about such inconsistency + raise + + def __enter__(self): + self.acquire() + + def release(self, blocking=True): + """Release a semaphore. In this class, this behaves very much like + an :meth:`acquire` but in the opposite direction. + + Imagine the docs of :meth:`acquire` here, but with every direction + reversed. When calling this method, it will block if the internal + counter is greater than or equal to *limit*. + """ + if not blocking and self.bounded(): + return False + self.lower_bound.release() + try: + return self.upper_bound.acquire() + except: + self.lower_bound.counter -= 1 + raise + + def __exit__(self, typ, val, tb): + self.release() + + @property + def balance(self): + """An integer value that represents how many new calls to + :meth:`acquire` or :meth:`release` would be needed to get the counter to + 0. If it is positive, then its value is the number of acquires that can + happen before the next acquire would block. If it is negative, it is + the negative of the number of releases that would be required in order + to make the counter 0 again (one more release would push the counter to + 1 and unblock acquirers). It takes into account how many greenthreads + are currently blocking in :meth:`acquire` and :meth:`release`. + """ + return self.lower_bound.balance - self.upper_bound.balance diff --git a/venv/lib/python3.12/site-packages/eventlet/support/__init__.py b/venv/lib/python3.12/site-packages/eventlet/support/__init__.py new file mode 100644 index 0000000..43bac91 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/support/__init__.py @@ -0,0 +1,78 @@ +import inspect +import functools +import sys +import warnings + +from eventlet.support import greenlets + + +_MISSING = object() + + +def get_errno(exc): + """ Get the error code out of socket.error objects. + socket.error in <2.5 does not have errno attribute + socket.error in 3.x does not allow indexing access + e.args[0] works for all. + There are cases when args[0] is not errno. + i.e. http://bugs.python.org/issue6471 + Maybe there are cases when errno is set, but it is not the first argument? + """ + + try: + if exc.errno is not None: + return exc.errno + except AttributeError: + pass + try: + return exc.args[0] + except IndexError: + return None + + +if sys.version_info[0] < 3 and not greenlets.preserves_excinfo: + from sys import exc_clear as clear_sys_exc_info +else: + def clear_sys_exc_info(): + """No-op In py3k. + Exception information is not visible outside of except statements. + sys.exc_clear became obsolete and removed.""" + pass + +if sys.version_info[0] < 3: + def bytes_to_str(b, encoding='ascii'): + return b +else: + def bytes_to_str(b, encoding='ascii'): + return b.decode(encoding) + +PY33 = sys.version_info[:2] == (3, 3) + + +def wrap_deprecated(old, new): + def _resolve(s): + return 'eventlet.'+s if '.' not in s else s + msg = '''\ +{old} is deprecated and will be removed in next version. Use {new} instead. +Autoupgrade: fgrep -rl '{old}' . |xargs -t sed --in-place='' -e 's/{old}/{new}/' +'''.format(old=_resolve(old), new=_resolve(new)) + + def wrapper(base): + klass = None + if inspect.isclass(base): + class klass(base): + pass + klass.__name__ = base.__name__ + klass.__module__ = base.__module__ + + @functools.wraps(base) + def wrapped(*a, **kw): + warnings.warn(msg, DeprecationWarning, stacklevel=5) + return base(*a, **kw) + + if klass is not None: + klass.__init__ = wrapped + return klass + + return wrapped + return wrapper diff --git a/venv/lib/python3.12/site-packages/eventlet/support/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/support/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..820e1c0ea8f1d4eb026138be66deb8dd2296758b GIT binary patch literal 3561 zcma)8OKjZ686LiNxx3U`wNl%uMwT6An`k$(oVapbIR@ew2I55v^#N~Tuv~H^QKHE0 z3|B8ph+GJW)EISAAkYSAkp$@>hI8ntD0*wJ?Lm%RSf-00K;46HTT4Jw_tJlcyDQ6a zkPOJ5!+HGA@Be51+R~Cn(Ej$9PbZJ)2>p})G@4THZ0|s413Ac%JXDOoQc)8Ba#0pv zrKrGH_S6}zs7XjhByqjIy_j^ARaDf8Jk{A?z58|0H{r)or)7j_7tyA=cjp>CmIU6K zC(mSxhJ<{@NdQ0NYw*(?!%416#TH+Mp3Zw;vP*JO`;n8Leior(tCIo#Z3|Mim;1SQ1xNXTrdp%#2qc)6Rle+^^(M9?iXooq#ax2O*xcW(7Mxz*ZPiw^Rwq6SocL=682l zo)^qv*LTRg>rY_Iwh3cm8_8L8g8lS`i+C=e(+rP;p0GMr<%g4m5^TY1TMQJKnE z?H3~eEQQS5j6(`P{6T&)m?3#P@Jb7LdlLM*ke?;~Y~FKA`Phcx+|bcHb3-y%w(MzZ zg0MV+B_RMa&no3|K*PLgy1pBlCYTMdC7~&lp&5{2z{`q2R#2^5)mpYPtyf3ZM>aEf zt83rJ$lD{E#=)9`jJ}$#Xx&?Fomc1A=d05CrA>YR=Q~Mc?0unx)D-ysZ%0SQ zK0efar>%<*bw6YWfc-Ceo&kP{!wcTwWNSj$_ObdS>1x<q-91F`mJM`^lGQ$s#yReq$@CFo*F?3BT2>#dP zNGe4-@gZ9wR~X-haV*d%Erf)bVPLY5(jJ(34&J(`t+w_x{fnZw7$J&l#YIg$TE}3F zwb#2cIB& zT)-_Cdi@wWZ>Vt0NULaBcH}4NC!-~K2{m*Se@%@)R<74w`%86A9hV$+RbN(?lrfam z3VVt+U&f8wehLT|xP)dUsyV=V5k|&D9I{H`p_E%pHPS05q5}SqSe&kLD0Gt&K*=mw z?3<%j*>p&m0(wF~%1JM~X3Fb_u86gU&nB8{DyISqhsdo2o|#rkicO8B01Sqq8FRjtrw55zv;U zbu^{M{YUANWH!0MislKpy9QR&c_0*~8sk&qveWpBzq>lDp*3Y(hRcfHxVkLMOVZ^O zU`{fdGL|F&hzzJw3-+U&8isCjoNpk4_zkHj^9uGb|E1XA6J zRme1>v}w)+PQ~MW!!&!=$tv1v?~d2N-3IMZna}sa zyZsuFcjOK9712_{W-J`(W4RF}e%)J#X{7(86_y>~bK4%W;l7@0?PkWF?9ye;v zqCPKYk;;2f=PUJm0RaCbo`U03vLbnUE>cTy+DFO^n_yfn#w#naoypj1&f?J5_iKI2 zE5L{y0`mC^x@UA>J->dw+JCEe=#SRljhF9q^;A3F8m*Sz9^EwJ;+0KCNyw%$+>4;Y zY#tqY1oqQsfJCYBif@NO;IT;K5*QC#eEeorI>gs;uhU^pjsg*u{0vOlV+mcvUuDTj zzIhtRf1!G`cdu#x$aG#kwSKC4>{bTv$WqHUs_G7c&u35fy?625i)&}D6t;T%-+TSt z*VoS6G7jELbzbdV@2z&O@UoYHfbSp3l6I`7DjD@Z z38ZB%zY6yZ<;~>}p8aU}!@-qTZ>lfUl1S5c(j8h)?JzRBRtgPmG>h4;n{B6@ALaZa z1vjD)M0pvA67kg*qI3S-*x0$kS#ceTjC$=9XLi&9rJE5t3r8!Qwd3Zw*4P{@o;}d27`y;peM?dKw{Zt)(pu#<^W;|gL r1YPUcPDt9JS_0{r+sU3A$)2iQ?bu8nsLtL<9$8Vh6Z@qu;kW+-jOteO literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/support/__pycache__/greendns.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/support/__pycache__/greendns.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb2f668d7f3c59686ef15097bb7a34c92ff57e06 GIT binary patch literal 41370 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/support/__pycache__/greenlets.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/support/__pycache__/greenlets.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..105ba533ec3b5b665be57655720d37d3c0ddb074 GIT binary patch literal 673 zcma)3L2J}N6rRb}n(l54i=@q=EcH@|u$kH|J&E=rt{@&fcnCt+CePh$(@e-auK=)44V zEL6qCqMCd_AqsJfJbaZkPXldIGwKbMCyRf8s`MKF;OaMU7XVMLJYAnnjO(~WO7+g) z-A~GG8UL>nYW2?EYlJ-gX1AV}W2f=Glz#E<)`r`6H=U)T6Gn_rc@#5u1{sTJQY`&$ z75XRxNMhhca|Aq?W*H>BXg{sGPYxnp+^+Z9&nac_s#0nBel_s@qLXI8Ae#a6;UI{T zH}oUAD4krsBxAY{B%twbPJ;1}^PJ2enUOf!C27vbH0iq!HVKP3^wMFlHyi;YpeS6~ zB5azbG~=Z9$=o!*)F1BCa2ms-%v4vD&&jAdel#$~Uyyx{%(-DnqbrQAG#p_#ht0XU zBF!~nu1T{e%-&&pZgr)#F06HF^@Y_x8py4I*czN!&*t_$X*_ zz26dvDB$$_uYZ;wL;!dwHjn9x*gScIGhY*dYbz+Ok*~;Z5u? z8?$^gdeg<|R#o4?T3=mTrx-ia*<0cv?C2#M5A=~+uhj@;$a2v2-s}13V-dnq7J5Fd03MZ@(|QFSDRLHx(9=a)oE^BsKZze*d*k zAQLuxs`&YqyB=q8xtBK}Xaurq^YYuoS+myb6w8oA;3bUeE;f&+ON8wZ=X}W~#)jpr z^Tapu-YFL|O_V^BC1@_lJgpJRvrkE#Sq^sjWrp-k-Ln0x^4D0|atCHNF%`)})92jm zQa0>`%et#GMyUwQi(RRT)Gty>s27p7^LBq+edVi+dRWR_Yz-rz@u1d_u}2j*?X}Ihodic;>(Al6YbHnt4nxT8#}~{`s(lBxU#ZHMP0q*vK*PZ?PD$*@Kl31 zju&QD6>~&;L6_+ay8@jEAu0+^#pLmq92eX@zU+Vp(O4&$-W&OPl>`))fu z)k;qt#HKp&Q=LR=x6+=NX-&)=BxVld>94MRcC8)HwBng>SAJOiZuR>Qe@WksXYR-6 z`>MItMD8Gwd%kMMSH1iw^-?>PYo&6Xo%UJ0_*~X?j&E10uaI{ dN&u?eIRKM?fXRm-{Z}Xq-+Y>m!#V$fe*n-*w_X4M literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/support/__pycache__/pylib.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/support/__pycache__/pylib.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c9ba3b8238f53350cb61282e6e50bd7ed032d10 GIT binary patch literal 673 zcmYjNzl#$=6rS1L{9wrno}7kAjzdIP4l{bThs|Lu4r61LM)r1x{BSqB%uEgkPFNve zYj^$&;{RY_t4&aejM3&QJ0aawzS-?KK&kUVT8_gVUATM zt2>aKp$J7dMhWg?taB^2`c{E<-;V4V>O0c%oj>}Mk5zrZD4oPq^un@D0X9T^S05pq zAptYjXKbb!0x-fK%J*036bZ*XjjJZxV1y5#5Ou?xqUV?T4LdH}8qFF(SuxCi%7)=K z2+GUJ68Tn-X)AtBRfNnaSMHNEn#S}Q6w2n3t6EYJywE9kKt(uZj824VJTBMs=;cVj zx~ip8``x|hkztxp5~lI7B;ha+LXs0YIU(`rm}F86)5(L*{e8kmg5J%7@I^47oKTPm z*f`@-T15L=iMJWw`YfMj$R+V zb?45_<@KGsJA1lp?k<{p^X6XO{pM^hTelW1GH;Q*``x**f;PynsCp(lNiZ0Ng;S6#HG1jEn~+;izO&g$9GLfcKc1O~=l$$?F5vm{>345~0G#W>9IHqc zcQ84F5JD2ckhBTWxfNM$D@VI+hxP>8jfIYK4Lfn ziJHqZFlwfO7?Kag{1u#nbj)Z_HrX0OvX6zVn$9UaU-8%7LGIRYmWa%f>HNp6ncfdf1mdYkVCUp+r<{b=5v z9K1PvefZX$IydLnn`vwEWM1Ez)wieh?X>mHxi)XynlQ|Xr6=n6KacciQLN{{R8k7k>daW literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/support/__pycache__/stacklesss.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/support/__pycache__/stacklesss.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2318f7d1c27facc07d978de9f4af5aaf85a532c GIT binary patch literal 3512 zcmai0OK;oQ6~31uDe4vZsd#M1rt_Mr535P0=>vld+UYnAf_RF$XaJ)CM-gdTiY%#@ zS02ltQ4L_AG~m`QW?>g7&{=dbK^F5Lx@{L$9Ml3U6ai*7-L;WdS@oPtk&zu2Jpj+W zJUsVx&iOvxzot@g0`j-N{Bz}gl#nm+B@m_8>25%0pO|EknADWYbdicyT9iZ^S&W!c zUM`YF1s>ImY>~yNMD7w(-Xf-AX-^~mOp7sLRlyqVv&My018dAmguY2L4)dfMQZ}(s zi7(V^wJPKKQkCg-yI5J)ZJsYYE?c&(*BX4KTA9`LAF=9VtD@&Crp}6{S+-35<04sMkFhHiMb-P`2ysiuu_o35MNLjd>04$tu#TcX?z~CbLnE&I+o*qzVDIpS zV1cLM>HZzmK51=(X>a{a#Zh2f z05Rf3crx&Fly{*k5l1QEsn3W*i^P%sOn*lydW18)b_Gw+qrkijh?$369(MgUTCoi>wY9WMcca_Uy^%w0@?hwhHrdu2r8g^ z9lrEoLIpffKn0SbjD)zTm zjsz$Qwm}^FNmSq}oR=dSgg7K9Ll_YjP=jQp`MIp>$)=Sz1v7YREss3pXI{hw5u1&O z^6;%QV6br!C}QDzjFrYgUlXi!iAtv$cHrbUrcV8|g5g zK8buAjP^rNO>&}=p^4+cYpua+M}u#>YI}TYC$@KWJKffjyXv;Oo7zt8q&wQ+u{Pe) z#-D4Goy6e5$e%KQ$g~pEo#fE&gY5@9A9m7X2P50RcI9?@*gYrVu$B)BjMsj^Z34Vc zs3=GqYoJ$z$WrmQ9i9<%KY`eiM=xfH-$D-SK$$V{w>m#V#?QEX5z}o%Q32? zx++y}`c#LG1$aUUkk-Z0e@%5jXp)z_+q=n{Zp=NRmPvf9;T*Loa~$SuY@T$p}3i>7zKzgf};TCJ9d=Xk27B zoAeaks9Clbe;|H=KZh4DVmE9r>i-jM;pg83MJn(l;vyg@gNZW2B7tkG>JMR8;MI$* z=8F{r3FZ`4cu{M$UWRKnyA57<;jtT_;9i*;I!;|`r7j(mj#Am?Tzl&3*89J?|H=Km zcuSt>WTu*PTOW4DE;r|%%cGs~>)2lWDwR1-&9qW8N2x0?vx?unM71AEs`2s9`9B)#dboe*eF?ob-r P`~C@mrVBp0IMaUtqqCJM literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/support/greendns.py b/venv/lib/python3.12/site-packages/eventlet/support/greendns.py new file mode 100644 index 0000000..6b5a6cb --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/support/greendns.py @@ -0,0 +1,929 @@ +'''greendns - non-blocking DNS support for Eventlet +''' + +# Portions of this code taken from the gogreen project: +# http://github.com/slideinc/gogreen +# +# Copyright (c) 2005-2010 Slide, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of the author nor the names of other +# 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 +# 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. +import re +import struct +import sys + +import eventlet +from eventlet import patcher +from eventlet.green import _socket_nodns +from eventlet.green import os +from eventlet.green import time +from eventlet.green import select +from eventlet.green import ssl +import six + + +def import_patched(module_name): + # Import cycle note: it's crucial to use _socket_nodns here because + # regular evenlet.green.socket imports *this* module and if we imported + # it back we'd end with an import cycle (socket -> greendns -> socket). + # We break this import cycle by providing a restricted socket module. + modules = { + 'select': select, + 'time': time, + 'os': os, + 'socket': _socket_nodns, + 'ssl': ssl, + } + return patcher.import_patched(module_name, **modules) + + +dns = import_patched('dns') + +# Handle rdtypes separately; we need fully it available as we patch the rest +dns.rdtypes = import_patched('dns.rdtypes') +dns.rdtypes.__all__.extend(['dnskeybase', 'dsbase', 'txtbase']) +for pkg in dns.rdtypes.__all__: + setattr(dns.rdtypes, pkg, import_patched('dns.rdtypes.' + pkg)) +for pkg in dns.rdtypes.IN.__all__: + setattr(dns.rdtypes.IN, pkg, import_patched('dns.rdtypes.IN.' + pkg)) +for pkg in dns.rdtypes.ANY.__all__: + setattr(dns.rdtypes.ANY, pkg, import_patched('dns.rdtypes.ANY.' + pkg)) + +for pkg in dns.__all__: + if pkg == 'rdtypes': + continue + setattr(dns, pkg, import_patched('dns.' + pkg)) +del import_patched + + +socket = _socket_nodns + +DNS_QUERY_TIMEOUT = 10.0 +HOSTS_TTL = 10.0 + +EAI_EAGAIN_ERROR = socket.gaierror(socket.EAI_AGAIN, 'Lookup timed out') +EAI_NONAME_ERROR = socket.gaierror(socket.EAI_NONAME, 'Name or service not known') +# EAI_NODATA was removed from RFC3493, it's now replaced with EAI_NONAME +# socket.EAI_NODATA is not defined on FreeBSD, probably on some other platforms too. +# https://lists.freebsd.org/pipermail/freebsd-ports/2003-October/005757.html +EAI_NODATA_ERROR = EAI_NONAME_ERROR +if (os.environ.get('EVENTLET_DEPRECATED_EAI_NODATA', '').lower() in ('1', 'y', 'yes') + and hasattr(socket, 'EAI_NODATA')): + EAI_NODATA_ERROR = socket.gaierror(socket.EAI_NODATA, 'No address associated with hostname') + + +def is_ipv4_addr(host): + """Return True if host is a valid IPv4 address""" + if not isinstance(host, six.string_types): + return False + try: + dns.ipv4.inet_aton(host) + except dns.exception.SyntaxError: + return False + else: + return True + + +def is_ipv6_addr(host): + """Return True if host is a valid IPv6 address""" + if not isinstance(host, six.string_types): + return False + host = host.split('%', 1)[0] + try: + dns.ipv6.inet_aton(host) + except dns.exception.SyntaxError: + return False + else: + return True + + +def is_ip_addr(host): + """Return True if host is a valid IPv4 or IPv6 address""" + return is_ipv4_addr(host) or is_ipv6_addr(host) + + +# NOTE(ralonsoh): in dnspython v2.0.0, "_compute_expiration" was replaced +# by "_compute_times". +if hasattr(dns.query, '_compute_expiration'): + def compute_expiration(query, timeout): + return query._compute_expiration(timeout) +else: + def compute_expiration(query, timeout): + return query._compute_times(timeout)[1] + + +class HostsAnswer(dns.resolver.Answer): + """Answer class for HostsResolver object""" + + def __init__(self, qname, rdtype, rdclass, rrset, raise_on_no_answer=True): + """Create a new answer + + :qname: A dns.name.Name instance of the query name + :rdtype: The rdatatype of the query + :rdclass: The rdataclass of the query + :rrset: The dns.rrset.RRset with the response, must have ttl attribute + :raise_on_no_answer: Whether to raise dns.resolver.NoAnswer if no + answer. + """ + self.response = None + self.qname = qname + self.rdtype = rdtype + self.rdclass = rdclass + self.canonical_name = qname + if not rrset and raise_on_no_answer: + raise dns.resolver.NoAnswer() + self.rrset = rrset + self.expiration = (time.time() + + rrset.ttl if hasattr(rrset, 'ttl') else 0) + + +class HostsResolver(object): + """Class to parse the hosts file + + Attributes + ---------- + + :fname: The filename of the hosts file in use. + :interval: The time between checking for hosts file modification + """ + + LINES_RE = re.compile(r""" + \s* # Leading space + ([^\r\n#]*?) # The actual match, non-greedy so as not to include trailing space + \s* # Trailing space + (?:[#][^\r\n]+)? # Comments + (?:$|[\r\n]+) # EOF or newline + """, re.VERBOSE) + + def __init__(self, fname=None, interval=HOSTS_TTL): + self._v4 = {} # name -> ipv4 + self._v6 = {} # name -> ipv6 + self._aliases = {} # name -> canonical_name + self.interval = interval + self.fname = fname + if fname is None: + if os.name == 'posix': + self.fname = '/etc/hosts' + elif os.name == 'nt': + self.fname = os.path.expandvars( + r'%SystemRoot%\system32\drivers\etc\hosts') + self._last_load = 0 + if self.fname: + self._load() + + def _readlines(self): + """Read the contents of the hosts file + + Return list of lines, comment lines and empty lines are + excluded. + + Note that this performs disk I/O so can be blocking. + """ + try: + with open(self.fname, 'rb') as fp: + fdata = fp.read() + except (IOError, OSError): + return [] + + udata = fdata.decode(errors='ignore') + + return six.moves.filter(None, self.LINES_RE.findall(udata)) + + def _load(self): + """Load hosts file + + This will unconditionally (re)load the data from the hosts + file. + """ + lines = self._readlines() + self._v4.clear() + self._v6.clear() + self._aliases.clear() + for line in lines: + parts = line.split() + if len(parts) < 2: + continue + ip = parts.pop(0) + if is_ipv4_addr(ip): + ipmap = self._v4 + elif is_ipv6_addr(ip): + if ip.startswith('fe80'): + # Do not use link-local addresses, OSX stores these here + continue + ipmap = self._v6 + else: + continue + cname = parts.pop(0).lower() + ipmap[cname] = ip + for alias in parts: + alias = alias.lower() + ipmap[alias] = ip + self._aliases[alias] = cname + self._last_load = time.time() + + def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, + tcp=False, source=None, raise_on_no_answer=True): + """Query the hosts file + + The known rdtypes are dns.rdatatype.A, dns.rdatatype.AAAA and + dns.rdatatype.CNAME. + + The ``rdclass`` parameter must be dns.rdataclass.IN while the + ``tcp`` and ``source`` parameters are ignored. + + Return a HostAnswer instance or raise a dns.resolver.NoAnswer + exception. + """ + now = time.time() + if self._last_load + self.interval < now: + self._load() + rdclass = dns.rdataclass.IN + if isinstance(qname, six.string_types): + name = qname + qname = dns.name.from_text(qname) + else: + name = str(qname) + name = name.lower() + rrset = dns.rrset.RRset(qname, rdclass, rdtype) + rrset.ttl = self._last_load + self.interval - now + if rdclass == dns.rdataclass.IN and rdtype == dns.rdatatype.A: + addr = self._v4.get(name) + if not addr and qname.is_absolute(): + addr = self._v4.get(name[:-1]) + if addr: + rrset.add(dns.rdtypes.IN.A.A(rdclass, rdtype, addr)) + elif rdclass == dns.rdataclass.IN and rdtype == dns.rdatatype.AAAA: + addr = self._v6.get(name) + if not addr and qname.is_absolute(): + addr = self._v6.get(name[:-1]) + if addr: + rrset.add(dns.rdtypes.IN.AAAA.AAAA(rdclass, rdtype, addr)) + elif rdclass == dns.rdataclass.IN and rdtype == dns.rdatatype.CNAME: + cname = self._aliases.get(name) + if not cname and qname.is_absolute(): + cname = self._aliases.get(name[:-1]) + if cname: + rrset.add(dns.rdtypes.ANY.CNAME.CNAME( + rdclass, rdtype, dns.name.from_text(cname))) + return HostsAnswer(qname, rdtype, rdclass, rrset, raise_on_no_answer) + + def getaliases(self, hostname): + """Return a list of all the aliases of a given cname""" + # Due to the way store aliases this is a bit inefficient, this + # clearly was an afterthought. But this is only used by + # gethostbyname_ex so it's probably fine. + aliases = [] + if hostname in self._aliases: + cannon = self._aliases[hostname] + else: + cannon = hostname + aliases.append(cannon) + for alias, cname in six.iteritems(self._aliases): + if cannon == cname: + aliases.append(alias) + aliases.remove(hostname) + return aliases + + +class ResolverProxy(object): + """Resolver class which can also use /etc/hosts + + Initialise with a HostsResolver instance in order for it to also + use the hosts file. + """ + + def __init__(self, hosts_resolver=None, filename='/etc/resolv.conf'): + """Initialise the resolver proxy + + :param hosts_resolver: An instance of HostsResolver to use. + + :param filename: The filename containing the resolver + configuration. The default value is correct for both UNIX + and Windows, on Windows it will result in the configuration + being read from the Windows registry. + """ + self._hosts = hosts_resolver + self._filename = filename + # NOTE(dtantsur): we cannot create a resolver here since this code is + # executed on eventlet import. In an environment without DNS, creating + # a Resolver will fail making eventlet unusable at all. See + # https://github.com/eventlet/eventlet/issues/736 for details. + self._cached_resolver = None + + @property + def _resolver(self): + if self._cached_resolver is None: + self.clear() + return self._cached_resolver + + @_resolver.setter + def _resolver(self, value): + self._cached_resolver = value + + def clear(self): + self._resolver = dns.resolver.Resolver(filename=self._filename) + self._resolver.cache = dns.resolver.LRUCache() + + def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, + tcp=False, source=None, raise_on_no_answer=True, + _hosts_rdtypes=(dns.rdatatype.A, dns.rdatatype.AAAA), + use_network=True): + """Query the resolver, using /etc/hosts if enabled. + + Behavior: + 1. if hosts is enabled and contains answer, return it now + 2. query nameservers for qname if use_network is True + 3. if qname did not contain dots, pretend it was top-level domain, + query "foobar." and append to previous result + """ + result = [None, None, 0] + + if qname is None: + qname = '0.0.0.0' + if isinstance(qname, six.string_types): + qname = dns.name.from_text(qname, None) + + def step(fun, *args, **kwargs): + try: + a = fun(*args, **kwargs) + except Exception as e: + result[1] = e + return False + if a.rrset is not None and len(a.rrset): + if result[0] is None: + result[0] = a + else: + result[0].rrset.union_update(a.rrset) + result[2] += len(a.rrset) + return True + + def end(): + if result[0] is not None: + if raise_on_no_answer and result[2] == 0: + raise dns.resolver.NoAnswer + return result[0] + if result[1] is not None: + if raise_on_no_answer or not isinstance(result[1], dns.resolver.NoAnswer): + raise result[1] + raise dns.resolver.NXDOMAIN(qnames=(qname,)) + + if (self._hosts and (rdclass == dns.rdataclass.IN) and (rdtype in _hosts_rdtypes)): + if step(self._hosts.query, qname, rdtype, raise_on_no_answer=False): + if (result[0] is not None) or (result[1] is not None) or (not use_network): + return end() + + # Main query + step(self._resolver.query, qname, rdtype, rdclass, tcp, source, raise_on_no_answer=False) + + # `resolv.conf` docs say unqualified names must resolve from search (or local) domain. + # However, common OS `getaddrinfo()` implementations append trailing dot (e.g. `db -> db.`) + # and ask nameservers, as if top-level domain was queried. + # This step follows established practice. + # https://github.com/nameko/nameko/issues/392 + # https://github.com/eventlet/eventlet/issues/363 + if len(qname) == 1: + step(self._resolver.query, qname.concatenate(dns.name.root), + rdtype, rdclass, tcp, source, raise_on_no_answer=False) + + return end() + + def getaliases(self, hostname): + """Return a list of all the aliases of a given hostname""" + if self._hosts: + aliases = self._hosts.getaliases(hostname) + else: + aliases = [] + while True: + try: + ans = self._resolver.query(hostname, dns.rdatatype.CNAME) + except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): + break + else: + aliases.extend(str(rr.target) for rr in ans.rrset) + hostname = ans[0].target + return aliases + + +resolver = ResolverProxy(hosts_resolver=HostsResolver()) + + +def resolve(name, family=socket.AF_INET, raises=True, _proxy=None, + use_network=True): + """Resolve a name for a given family using the global resolver proxy. + + This method is called by the global getaddrinfo() function. If use_network + is False, only resolution via hosts file will be performed. + + Return a dns.resolver.Answer instance. If there is no answer it's + rrset will be emtpy. + """ + if family == socket.AF_INET: + rdtype = dns.rdatatype.A + elif family == socket.AF_INET6: + rdtype = dns.rdatatype.AAAA + else: + raise socket.gaierror(socket.EAI_FAMILY, + 'Address family not supported') + + if _proxy is None: + _proxy = resolver + try: + try: + return _proxy.query(name, rdtype, raise_on_no_answer=raises, + use_network=use_network) + except dns.resolver.NXDOMAIN: + if not raises: + return HostsAnswer(dns.name.Name(name), + rdtype, dns.rdataclass.IN, None, False) + raise + except dns.exception.Timeout: + raise EAI_EAGAIN_ERROR + except dns.exception.DNSException: + raise EAI_NODATA_ERROR + + +def resolve_cname(host): + """Return the canonical name of a hostname""" + try: + ans = resolver.query(host, dns.rdatatype.CNAME) + except dns.resolver.NoAnswer: + return host + except dns.exception.Timeout: + raise EAI_EAGAIN_ERROR + except dns.exception.DNSException: + raise EAI_NODATA_ERROR + else: + return str(ans[0].target) + + +def getaliases(host): + """Return a list of for aliases for the given hostname + + This method does translate the dnspython exceptions into + socket.gaierror exceptions. If no aliases are available an empty + list will be returned. + """ + try: + return resolver.getaliases(host) + except dns.exception.Timeout: + raise EAI_EAGAIN_ERROR + except dns.exception.DNSException: + raise EAI_NODATA_ERROR + + +def _getaddrinfo_lookup(host, family, flags): + """Resolve a hostname to a list of addresses + + Helper function for getaddrinfo. + """ + if flags & socket.AI_NUMERICHOST: + raise EAI_NONAME_ERROR + addrs = [] + if family == socket.AF_UNSPEC: + err = None + for use_network in [False, True]: + for qfamily in [socket.AF_INET6, socket.AF_INET]: + try: + answer = resolve(host, qfamily, False, use_network=use_network) + except socket.gaierror as e: + if e.errno not in (socket.EAI_AGAIN, EAI_NONAME_ERROR.errno, EAI_NODATA_ERROR.errno): + raise + err = e + else: + if answer.rrset: + addrs.extend(rr.address for rr in answer.rrset) + if addrs: + break + if err is not None and not addrs: + raise err + elif family == socket.AF_INET6 and flags & socket.AI_V4MAPPED: + answer = resolve(host, socket.AF_INET6, False) + if answer.rrset: + addrs = [rr.address for rr in answer.rrset] + if not addrs or flags & socket.AI_ALL: + answer = resolve(host, socket.AF_INET, False) + if answer.rrset: + addrs = ['::ffff:' + rr.address for rr in answer.rrset] + else: + answer = resolve(host, family, False) + if answer.rrset: + addrs = [rr.address for rr in answer.rrset] + return str(answer.qname), addrs + + +def getaddrinfo(host, port, family=0, socktype=0, proto=0, flags=0): + """Replacement for Python's socket.getaddrinfo + + This does the A and AAAA lookups asynchronously after which it + calls the OS' getaddrinfo(3) using the AI_NUMERICHOST flag. This + flag ensures getaddrinfo(3) does not use the network itself and + allows us to respect all the other arguments like the native OS. + """ + if isinstance(host, six.string_types): + host = host.encode('idna').decode('ascii') + if host is not None and not is_ip_addr(host): + qname, addrs = _getaddrinfo_lookup(host, family, flags) + else: + qname = host + addrs = [host] + aiflags = (flags | socket.AI_NUMERICHOST) & (0xffff ^ socket.AI_CANONNAME) + res = [] + err = None + for addr in addrs: + try: + ai = socket.getaddrinfo(addr, port, family, + socktype, proto, aiflags) + except socket.error as e: + if flags & socket.AI_ADDRCONFIG: + err = e + continue + raise + res.extend(ai) + if not res: + if err: + raise err + raise socket.gaierror(socket.EAI_NONAME, 'No address found') + if flags & socket.AI_CANONNAME: + if not is_ip_addr(qname): + qname = resolve_cname(qname).encode('ascii').decode('idna') + ai = res[0] + res[0] = (ai[0], ai[1], ai[2], qname, ai[4]) + return res + + +def gethostbyname(hostname): + """Replacement for Python's socket.gethostbyname""" + if is_ipv4_addr(hostname): + return hostname + rrset = resolve(hostname) + return rrset[0].address + + +def gethostbyname_ex(hostname): + """Replacement for Python's socket.gethostbyname_ex""" + if is_ipv4_addr(hostname): + return (hostname, [], [hostname]) + ans = resolve(hostname) + aliases = getaliases(hostname) + addrs = [rr.address for rr in ans.rrset] + qname = str(ans.qname) + if qname[-1] == '.': + qname = qname[:-1] + return (qname, aliases, addrs) + + +def getnameinfo(sockaddr, flags): + """Replacement for Python's socket.getnameinfo. + + Currently only supports IPv4. + """ + try: + host, port = sockaddr + except (ValueError, TypeError): + if not isinstance(sockaddr, tuple): + del sockaddr # to pass a stdlib test that is + # hyper-careful about reference counts + raise TypeError('getnameinfo() argument 1 must be a tuple') + else: + # must be ipv6 sockaddr, pretending we don't know how to resolve it + raise EAI_NONAME_ERROR + + if (flags & socket.NI_NAMEREQD) and (flags & socket.NI_NUMERICHOST): + # Conflicting flags. Punt. + raise EAI_NONAME_ERROR + + if is_ipv4_addr(host): + try: + rrset = resolver.query( + dns.reversename.from_address(host), dns.rdatatype.PTR) + if len(rrset) > 1: + raise socket.error('sockaddr resolved to multiple addresses') + host = rrset[0].target.to_text(omit_final_dot=True) + except dns.exception.Timeout: + if flags & socket.NI_NAMEREQD: + raise EAI_EAGAIN_ERROR + except dns.exception.DNSException: + if flags & socket.NI_NAMEREQD: + raise EAI_NONAME_ERROR + else: + try: + rrset = resolver.query(host) + if len(rrset) > 1: + raise socket.error('sockaddr resolved to multiple addresses') + if flags & socket.NI_NUMERICHOST: + host = rrset[0].address + except dns.exception.Timeout: + raise EAI_EAGAIN_ERROR + except dns.exception.DNSException: + raise socket.gaierror( + (socket.EAI_NODATA, 'No address associated with hostname')) + + if not (flags & socket.NI_NUMERICSERV): + proto = (flags & socket.NI_DGRAM) and 'udp' or 'tcp' + port = socket.getservbyport(port, proto) + + return (host, port) + + +def _net_read(sock, count, expiration): + """coro friendly replacement for dns.query._net_read + Read the specified number of bytes from sock. Keep trying until we + either get the desired amount, or we hit EOF. + A Timeout exception will be raised if the operation is not completed + by the expiration time. + """ + s = bytearray() + while count > 0: + try: + n = sock.recv(count) + except socket.timeout: + # Q: Do we also need to catch coro.CoroutineSocketWake and pass? + if expiration - time.time() <= 0.0: + raise dns.exception.Timeout + eventlet.sleep(0.01) + continue + if n == b'': + raise EOFError + count = count - len(n) + s += n + return s + + +def _net_write(sock, data, expiration): + """coro friendly replacement for dns.query._net_write + Write the specified data to the socket. + A Timeout exception will be raised if the operation is not completed + by the expiration time. + """ + current = 0 + l = len(data) + while current < l: + try: + current += sock.send(data[current:]) + except socket.timeout: + # Q: Do we also need to catch coro.CoroutineSocketWake and pass? + if expiration - time.time() <= 0.0: + raise dns.exception.Timeout + + +# Test if raise_on_truncation is an argument we should handle. +# It was newly added in dnspython 2.0 +try: + dns.message.from_wire("", raise_on_truncation=True) +except dns.message.ShortHeader: + _handle_raise_on_truncation = True +except TypeError: + # Argument error, there is no argument "raise_on_truncation" + _handle_raise_on_truncation = False + + +def udp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, + af=None, source=None, source_port=0, ignore_unexpected=False, + one_rr_per_rrset=False, ignore_trailing=False, + raise_on_truncation=False, sock=None): + """coro friendly replacement for dns.query.udp + Return the response obtained after sending a query via UDP. + + @param q: the query + @type q: dns.message.Message + @param where: where to send the message + @type where: string containing an IPv4 or IPv6 address + @param timeout: The number of seconds to wait before the query times out. + If None, the default, wait forever. + @type timeout: float + @param port: The port to which to send the message. The default is 53. + @type port: int + @param af: the address family to use. The default is None, which + causes the address family to use to be inferred from the form of of where. + If the inference attempt fails, AF_INET is used. + @type af: int + @rtype: dns.message.Message object + @param source: source address. The default is the IPv4 wildcard address. + @type source: string + @param source_port: The port from which to send the message. + The default is 0. + @type source_port: int + @param ignore_unexpected: If True, ignore responses from unexpected + sources. The default is False. + @type ignore_unexpected: bool + @param one_rr_per_rrset: If True, put each RR into its own + RRset. + @type one_rr_per_rrset: bool + @param ignore_trailing: If True, ignore trailing + junk at end of the received message. + @type ignore_trailing: bool + @param raise_on_truncation: If True, raise an exception if + the TC bit is set. + @type raise_on_truncation: bool + @param sock: the socket to use for the + query. If None, the default, a socket is created. Note that + if a socket is provided, it must be a nonblocking datagram socket, + and the source and source_port are ignored. + @type sock: socket.socket | None""" + + wire = q.to_wire() + if af is None: + try: + af = dns.inet.af_for_address(where) + except: + af = dns.inet.AF_INET + if af == dns.inet.AF_INET: + destination = (where, port) + if source is not None: + source = (source, source_port) + elif af == dns.inet.AF_INET6: + # Purge any stray zeroes in source address. When doing the tuple comparison + # below, we need to always ensure both our target and where we receive replies + # from are compared with all zeroes removed so that we don't erroneously fail. + # e.g. ('00::1', 53, 0, 0) != ('::1', 53, 0, 0) + where_trunc = dns.ipv6.inet_ntoa(dns.ipv6.inet_aton(where)) + destination = (where_trunc, port, 0, 0) + if source is not None: + source = (source, source_port, 0, 0) + + if sock: + s = sock + else: + s = socket.socket(af, socket.SOCK_DGRAM) + s.settimeout(timeout) + try: + expiration = compute_expiration(dns.query, timeout) + if source is not None: + s.bind(source) + while True: + try: + s.sendto(wire, destination) + break + except socket.timeout: + # Q: Do we also need to catch coro.CoroutineSocketWake and pass? + if expiration - time.time() <= 0.0: + raise dns.exception.Timeout + eventlet.sleep(0.01) + continue + + tried = False + while True: + # If we've tried to receive at least once, check to see if our + # timer expired + if tried and (expiration - time.time() <= 0.0): + raise dns.exception.Timeout + # Sleep if we are retrying the operation due to a bad source + # address or a socket timeout. + if tried: + eventlet.sleep(0.01) + tried = True + + try: + (wire, from_address) = s.recvfrom(65535) + except socket.timeout: + # Q: Do we also need to catch coro.CoroutineSocketWake and pass? + continue + if dns.inet.af_for_address(from_address[0]) == dns.inet.AF_INET6: + # Purge all possible zeroes for ipv6 to match above logic + addr = from_address[0] + addr = dns.ipv6.inet_ntoa(dns.ipv6.inet_aton(addr)) + from_address = (addr, from_address[1], from_address[2], from_address[3]) + if from_address == destination: + break + if not ignore_unexpected: + raise dns.query.UnexpectedSource( + 'got a response from %s instead of %s' + % (from_address, destination)) + finally: + s.close() + + if _handle_raise_on_truncation: + r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + raise_on_truncation=raise_on_truncation) + else: + r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing) + if not q.is_response(r): + raise dns.query.BadResponse() + return r + + +def tcp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, + af=None, source=None, source_port=0, + one_rr_per_rrset=False, ignore_trailing=False, sock=None): + """coro friendly replacement for dns.query.tcp + Return the response obtained after sending a query via TCP. + + @param q: the query + @type q: dns.message.Message object + @param where: where to send the message + @type where: string containing an IPv4 or IPv6 address + @param timeout: The number of seconds to wait before the query times out. + If None, the default, wait forever. + @type timeout: float + @param port: The port to which to send the message. The default is 53. + @type port: int + @param af: the address family to use. The default is None, which + causes the address family to use to be inferred from the form of of where. + If the inference attempt fails, AF_INET is used. + @type af: int + @rtype: dns.message.Message object + @param source: source address. The default is the IPv4 wildcard address. + @type source: string + @param source_port: The port from which to send the message. + The default is 0. + @type source_port: int + @type ignore_unexpected: bool + @param one_rr_per_rrset: If True, put each RR into its own + RRset. + @type one_rr_per_rrset: bool + @param ignore_trailing: If True, ignore trailing + junk at end of the received message. + @type ignore_trailing: bool + @param sock: the socket to use for the + query. If None, the default, a socket is created. Note that + if a socket is provided, it must be a nonblocking datagram socket, + and the source and source_port are ignored. + @type sock: socket.socket | None""" + + wire = q.to_wire() + if af is None: + try: + af = dns.inet.af_for_address(where) + except: + af = dns.inet.AF_INET + if af == dns.inet.AF_INET: + destination = (where, port) + if source is not None: + source = (source, source_port) + elif af == dns.inet.AF_INET6: + destination = (where, port, 0, 0) + if source is not None: + source = (source, source_port, 0, 0) + if sock: + s = sock + else: + s = socket.socket(af, socket.SOCK_STREAM) + s.settimeout(timeout) + try: + expiration = compute_expiration(dns.query, timeout) + if source is not None: + s.bind(source) + while True: + try: + s.connect(destination) + break + except socket.timeout: + # Q: Do we also need to catch coro.CoroutineSocketWake and pass? + if expiration - time.time() <= 0.0: + raise dns.exception.Timeout + eventlet.sleep(0.01) + continue + + l = len(wire) + # copying the wire into tcpmsg is inefficient, but lets us + # avoid writev() or doing a short write that would get pushed + # onto the net + tcpmsg = struct.pack("!H", l) + wire + _net_write(s, tcpmsg, expiration) + ldata = _net_read(s, 2, expiration) + (l,) = struct.unpack("!H", ldata) + wire = bytes(_net_read(s, l, expiration)) + finally: + s.close() + r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing) + if not q.is_response(r): + raise dns.query.BadResponse() + return r + + +def reset(): + resolver.clear() + +# Install our coro-friendly replacements for the tcp and udp query methods. +dns.query.tcp = tcp +dns.query.udp = udp diff --git a/venv/lib/python3.12/site-packages/eventlet/support/greenlets.py b/venv/lib/python3.12/site-packages/eventlet/support/greenlets.py new file mode 100644 index 0000000..d4e1793 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/support/greenlets.py @@ -0,0 +1,8 @@ +import distutils.version + +import greenlet +getcurrent = greenlet.greenlet.getcurrent +GreenletExit = greenlet.greenlet.GreenletExit +preserves_excinfo = (distutils.version.LooseVersion(greenlet.__version__) + >= distutils.version.LooseVersion('0.3.2')) +greenlet = greenlet.greenlet diff --git a/venv/lib/python3.12/site-packages/eventlet/support/psycopg2_patcher.py b/venv/lib/python3.12/site-packages/eventlet/support/psycopg2_patcher.py new file mode 100644 index 0000000..2f4034a --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/support/psycopg2_patcher.py @@ -0,0 +1,55 @@ +"""A wait callback to allow psycopg2 cooperation with eventlet. + +Use `make_psycopg_green()` to enable eventlet support in Psycopg. +""" + +# Copyright (C) 2010 Daniele Varrazzo +# and licensed under the MIT license: +# +# 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. + +import psycopg2 +from psycopg2 import extensions + +import eventlet.hubs + + +def make_psycopg_green(): + """Configure Psycopg to be used with eventlet in non-blocking way.""" + if not hasattr(extensions, 'set_wait_callback'): + raise ImportError( + "support for coroutines not available in this Psycopg version (%s)" + % psycopg2.__version__) + + extensions.set_wait_callback(eventlet_wait_callback) + + +def eventlet_wait_callback(conn, timeout=-1): + """A wait callback useful to allow eventlet to work with Psycopg.""" + while 1: + state = conn.poll() + if state == extensions.POLL_OK: + break + elif state == extensions.POLL_READ: + eventlet.hubs.trampoline(conn.fileno(), read=True) + elif state == extensions.POLL_WRITE: + eventlet.hubs.trampoline(conn.fileno(), write=True) + else: + raise psycopg2.OperationalError( + "Bad result from poll: %r" % state) diff --git a/venv/lib/python3.12/site-packages/eventlet/support/pylib.py b/venv/lib/python3.12/site-packages/eventlet/support/pylib.py new file mode 100644 index 0000000..fdb0682 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/support/pylib.py @@ -0,0 +1,12 @@ +from py.magic import greenlet + +import sys +import types + + +def emulate(): + module = types.ModuleType('greenlet') + sys.modules['greenlet'] = module + module.greenlet = greenlet + module.getcurrent = greenlet.getcurrent + module.GreenletExit = greenlet.GreenletExit diff --git a/venv/lib/python3.12/site-packages/eventlet/support/stacklesspypys.py b/venv/lib/python3.12/site-packages/eventlet/support/stacklesspypys.py new file mode 100644 index 0000000..fe3638a --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/support/stacklesspypys.py @@ -0,0 +1,12 @@ +from stackless import greenlet + +import sys +import types + + +def emulate(): + module = types.ModuleType('greenlet') + sys.modules['greenlet'] = module + module.greenlet = greenlet + module.getcurrent = greenlet.getcurrent + module.GreenletExit = greenlet.GreenletExit diff --git a/venv/lib/python3.12/site-packages/eventlet/support/stacklesss.py b/venv/lib/python3.12/site-packages/eventlet/support/stacklesss.py new file mode 100644 index 0000000..4d19c5b --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/support/stacklesss.py @@ -0,0 +1,84 @@ +""" +Support for using stackless python. Broken and riddled with print statements +at the moment. Please fix it! +""" + +import sys +import types + +import stackless + +caller = None +coro_args = {} +tasklet_to_greenlet = {} + + +def getcurrent(): + return tasklet_to_greenlet[stackless.getcurrent()] + + +class FirstSwitch(object): + def __init__(self, gr): + self.gr = gr + + def __call__(self, *args, **kw): + # print("first call", args, kw) + gr = self.gr + del gr.switch + run, gr.run = gr.run, None + t = stackless.tasklet(run) + gr.t = t + tasklet_to_greenlet[t] = gr + t.setup(*args, **kw) + t.run() + + +class greenlet(object): + def __init__(self, run=None, parent=None): + self.dead = False + if parent is None: + parent = getcurrent() + + self.parent = parent + if run is not None: + self.run = run + + self.switch = FirstSwitch(self) + + def switch(self, *args): + # print("switch", args) + global caller + caller = stackless.getcurrent() + coro_args[self] = args + self.t.insert() + stackless.schedule() + if caller is not self.t: + caller.remove() + rval = coro_args[self] + return rval + + def run(self): + pass + + def __bool__(self): + return self.run is None and not self.dead + + +class GreenletExit(Exception): + pass + + +def emulate(): + module = types.ModuleType('greenlet') + sys.modules['greenlet'] = module + module.greenlet = greenlet + module.getcurrent = getcurrent + module.GreenletExit = GreenletExit + + caller = stackless.getcurrent() + tasklet_to_greenlet[caller] = None + main_coro = greenlet() + tasklet_to_greenlet[caller] = main_coro + main_coro.t = caller + del main_coro.switch # It's already running + coro_args[main_coro] = None diff --git a/venv/lib/python3.12/site-packages/eventlet/timeout.py b/venv/lib/python3.12/site-packages/eventlet/timeout.py new file mode 100644 index 0000000..4ab893e --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/timeout.py @@ -0,0 +1,184 @@ +# Copyright (c) 2009-2010 Denis Bilenko, denis.bilenko at gmail com +# Copyright (c) 2010 Eventlet Contributors (see AUTHORS) +# and licensed under the MIT license: +# +# 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. + +import functools +import inspect + +import eventlet +from eventlet.support import greenlets as greenlet +from eventlet.hubs import get_hub + +__all__ = ['Timeout', 'with_timeout', 'wrap_is_timeout', 'is_timeout'] + +_MISSING = object() + +# deriving from BaseException so that "except Exception as e" doesn't catch +# Timeout exceptions. + + +class Timeout(BaseException): + """Raises *exception* in the current greenthread after *timeout* seconds. + + When *exception* is omitted or ``None``, the :class:`Timeout` instance + itself is raised. If *seconds* is None, the timer is not scheduled, and is + only useful if you're planning to raise it directly. + + Timeout objects are context managers, and so can be used in with statements. + When used in a with statement, if *exception* is ``False``, the timeout is + still raised, but the context manager suppresses it, so the code outside the + with-block won't see it. + """ + + def __init__(self, seconds=None, exception=None): + self.seconds = seconds + self.exception = exception + self.timer = None + self.start() + + def start(self): + """Schedule the timeout. This is called on construction, so + it should not be called explicitly, unless the timer has been + canceled.""" + assert not self.pending, \ + '%r is already started; to restart it, cancel it first' % self + if self.seconds is None: # "fake" timeout (never expires) + self.timer = None + elif self.exception is None or isinstance(self.exception, bool): # timeout that raises self + self.timer = get_hub().schedule_call_global( + self.seconds, greenlet.getcurrent().throw, self) + else: # regular timeout with user-provided exception + self.timer = get_hub().schedule_call_global( + self.seconds, greenlet.getcurrent().throw, self.exception) + return self + + @property + def pending(self): + """True if the timeout is scheduled to be raised.""" + if self.timer is not None: + return self.timer.pending + else: + return False + + def cancel(self): + """If the timeout is pending, cancel it. If not using + Timeouts in ``with`` statements, always call cancel() in a + ``finally`` after the block of code that is getting timed out. + If not canceled, the timeout will be raised later on, in some + unexpected section of the application.""" + if self.timer is not None: + self.timer.cancel() + self.timer = None + + def __repr__(self): + classname = self.__class__.__name__ + if self.pending: + pending = ' pending' + else: + pending = '' + if self.exception is None: + exception = '' + else: + exception = ' exception=%r' % self.exception + return '<%s at %s seconds=%s%s%s>' % ( + classname, hex(id(self)), self.seconds, exception, pending) + + def __str__(self): + """ + >>> raise Timeout # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + Timeout + """ + if self.seconds is None: + return '' + if self.seconds == 1: + suffix = '' + else: + suffix = 's' + if self.exception is None or self.exception is True: + return '%s second%s' % (self.seconds, suffix) + elif self.exception is False: + return '%s second%s (silent)' % (self.seconds, suffix) + else: + return '%s second%s (%s)' % (self.seconds, suffix, self.exception) + + def __enter__(self): + if self.timer is None: + self.start() + return self + + def __exit__(self, typ, value, tb): + self.cancel() + if value is self and self.exception is False: + return True + + @property + def is_timeout(self): + return True + + +def with_timeout(seconds, function, *args, **kwds): + """Wrap a call to some (yielding) function with a timeout; if the called + function fails to return before the timeout, cancel it and return a flag + value. + """ + timeout_value = kwds.pop("timeout_value", _MISSING) + timeout = Timeout(seconds) + try: + try: + return function(*args, **kwds) + except Timeout as ex: + if ex is timeout and timeout_value is not _MISSING: + return timeout_value + raise + finally: + timeout.cancel() + + +def wrap_is_timeout(base): + '''Adds `.is_timeout=True` attribute to objects returned by `base()`. + + When `base` is class, attribute is added as read-only property. Returns `base`. + Otherwise, it returns a function that sets attribute on result of `base()` call. + + Wrappers make best effort to be transparent. + ''' + if inspect.isclass(base): + base.is_timeout = property(lambda _: True) + return base + + @functools.wraps(base) + def fun(*args, **kwargs): + ex = base(*args, **kwargs) + ex.is_timeout = True + return ex + return fun + + +if isinstance(__builtins__, dict): # seen when running tests on py310, but HOW?? + _timeout_err = __builtins__.get('TimeoutError', Timeout) +else: + _timeout_err = getattr(__builtins__, 'TimeoutError', Timeout) + + +def is_timeout(obj): + return bool(getattr(obj, 'is_timeout', False)) or isinstance(obj, _timeout_err) diff --git a/venv/lib/python3.12/site-packages/eventlet/tpool.py b/venv/lib/python3.12/site-packages/eventlet/tpool.py new file mode 100644 index 0000000..af5e895 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/tpool.py @@ -0,0 +1,343 @@ +# Copyright (c) 2007-2009, Linden Research, Inc. +# Copyright (c) 2007, IBM Corp. +# +# 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. + +import atexit +try: + import _imp as imp +except ImportError: + import imp +import os +import sys +import traceback + +import eventlet +from eventlet import event, greenio, greenthread, patcher, timeout +import six + +__all__ = ['execute', 'Proxy', 'killall', 'set_num_threads'] + + +EXC_CLASSES = (Exception, timeout.Timeout) +SYS_EXCS = (GeneratorExit, KeyboardInterrupt, SystemExit) + +QUIET = True + +socket = patcher.original('socket') +threading = patcher.original('threading') +if six.PY2: + Queue_module = patcher.original('Queue') +if six.PY3: + Queue_module = patcher.original('queue') + +Empty = Queue_module.Empty +Queue = Queue_module.Queue + +_bytetosend = b' ' +_coro = None +_nthreads = int(os.environ.get('EVENTLET_THREADPOOL_SIZE', 20)) +_reqq = _rspq = None +_rsock = _wsock = None +_setup_already = False +_threads = [] + + +def tpool_trampoline(): + global _rspq + while True: + try: + _c = _rsock.recv(1) + assert _c + # FIXME: this is probably redundant since using sockets instead of pipe now + except ValueError: + break # will be raised when pipe is closed + while not _rspq.empty(): + try: + (e, rv) = _rspq.get(block=False) + e.send(rv) + e = rv = None + except Empty: + pass + + +def tworker(): + global _rspq + while True: + try: + msg = _reqq.get() + except AttributeError: + return # can't get anything off of a dud queue + if msg is None: + return + (e, meth, args, kwargs) = msg + rv = None + try: + rv = meth(*args, **kwargs) + except SYS_EXCS: + raise + except EXC_CLASSES: + rv = sys.exc_info() + if sys.version_info >= (3, 4): + traceback.clear_frames(rv[1].__traceback__) + if six.PY2: + sys.exc_clear() + # test_leakage_from_tracebacks verifies that the use of + # exc_info does not lead to memory leaks + _rspq.put((e, rv)) + msg = meth = args = kwargs = e = rv = None + _wsock.sendall(_bytetosend) + + +def execute(meth, *args, **kwargs): + """ + Execute *meth* in a Python thread, blocking the current coroutine/ + greenthread until the method completes. + + The primary use case for this is to wrap an object or module that is not + amenable to monkeypatching or any of the other tricks that Eventlet uses + to achieve cooperative yielding. With tpool, you can force such objects to + cooperate with green threads by sticking them in native threads, at the cost + of some overhead. + """ + setup() + # if already in tpool, don't recurse into the tpool + # also, call functions directly if we're inside an import lock, because + # if meth does any importing (sadly common), it will hang + my_thread = threading.current_thread() + if my_thread in _threads or imp.lock_held() or _nthreads == 0: + return meth(*args, **kwargs) + + e = event.Event() + _reqq.put((e, meth, args, kwargs)) + + rv = e.wait() + if isinstance(rv, tuple) \ + and len(rv) == 3 \ + and isinstance(rv[1], EXC_CLASSES): + (c, e, tb) = rv + if not QUIET: + traceback.print_exception(c, e, tb) + traceback.print_stack() + six.reraise(c, e, tb) + return rv + + +def proxy_call(autowrap, f, *args, **kwargs): + """ + Call a function *f* and returns the value. If the type of the return value + is in the *autowrap* collection, then it is wrapped in a :class:`Proxy` + object before return. + + Normally *f* will be called in the threadpool with :func:`execute`; if the + keyword argument "nonblocking" is set to ``True``, it will simply be + executed directly. This is useful if you have an object which has methods + that don't need to be called in a separate thread, but which return objects + that should be Proxy wrapped. + """ + if kwargs.pop('nonblocking', False): + rv = f(*args, **kwargs) + else: + rv = execute(f, *args, **kwargs) + if isinstance(rv, autowrap): + return Proxy(rv, autowrap) + else: + return rv + + +class Proxy(object): + """ + a simple proxy-wrapper of any object that comes with a + methods-only interface, in order to forward every method + invocation onto a thread in the native-thread pool. A key + restriction is that the object's methods should not switch + greenlets or use Eventlet primitives, since they are in a + different thread from the main hub, and therefore might behave + unexpectedly. This is for running native-threaded code + only. + + It's common to want to have some of the attributes or return + values also wrapped in Proxy objects (for example, database + connection objects produce cursor objects which also should be + wrapped in Proxy objects to remain nonblocking). *autowrap*, if + supplied, is a collection of types; if an attribute or return + value matches one of those types (via isinstance), it will be + wrapped in a Proxy. *autowrap_names* is a collection + of strings, which represent the names of attributes that should be + wrapped in Proxy objects when accessed. + """ + + def __init__(self, obj, autowrap=(), autowrap_names=()): + self._obj = obj + self._autowrap = autowrap + self._autowrap_names = autowrap_names + + def __getattr__(self, attr_name): + f = getattr(self._obj, attr_name) + if not hasattr(f, '__call__'): + if isinstance(f, self._autowrap) or attr_name in self._autowrap_names: + return Proxy(f, self._autowrap) + return f + + def doit(*args, **kwargs): + result = proxy_call(self._autowrap, f, *args, **kwargs) + if attr_name in self._autowrap_names and not isinstance(result, Proxy): + return Proxy(result) + return result + return doit + + # the following are a buncha methods that the python interpeter + # doesn't use getattr to retrieve and therefore have to be defined + # explicitly + def __getitem__(self, key): + return proxy_call(self._autowrap, self._obj.__getitem__, key) + + def __setitem__(self, key, value): + return proxy_call(self._autowrap, self._obj.__setitem__, key, value) + + def __deepcopy__(self, memo=None): + return proxy_call(self._autowrap, self._obj.__deepcopy__, memo) + + def __copy__(self, memo=None): + return proxy_call(self._autowrap, self._obj.__copy__, memo) + + def __call__(self, *a, **kw): + if '__call__' in self._autowrap_names: + return Proxy(proxy_call(self._autowrap, self._obj, *a, **kw)) + else: + return proxy_call(self._autowrap, self._obj, *a, **kw) + + def __enter__(self): + return proxy_call(self._autowrap, self._obj.__enter__) + + def __exit__(self, *exc): + return proxy_call(self._autowrap, self._obj.__exit__, *exc) + + # these don't go through a proxy call, because they're likely to + # be called often, and are unlikely to be implemented on the + # wrapped object in such a way that they would block + def __eq__(self, rhs): + return self._obj == rhs + + def __hash__(self): + return self._obj.__hash__() + + def __repr__(self): + return self._obj.__repr__() + + def __str__(self): + return self._obj.__str__() + + def __len__(self): + return len(self._obj) + + def __nonzero__(self): + return bool(self._obj) + # Python3 + __bool__ = __nonzero__ + + def __iter__(self): + it = iter(self._obj) + if it == self._obj: + return self + else: + return Proxy(it) + + def next(self): + return proxy_call(self._autowrap, next, self._obj) + # Python3 + __next__ = next + + +def setup(): + global _rsock, _wsock, _coro, _setup_already, _rspq, _reqq + if _setup_already: + return + else: + _setup_already = True + + assert _nthreads >= 0, "Can't specify negative number of threads" + if _nthreads == 0: + import warnings + warnings.warn("Zero threads in tpool. All tpool.execute calls will\ + execute in main thread. Check the value of the environment \ + variable EVENTLET_THREADPOOL_SIZE.", RuntimeWarning) + _reqq = Queue(maxsize=-1) + _rspq = Queue(maxsize=-1) + + # connected socket pair + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.bind(('127.0.0.1', 0)) + sock.listen(1) + csock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + csock.connect(sock.getsockname()) + csock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) + _wsock, _addr = sock.accept() + _wsock.settimeout(None) + _wsock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) + sock.close() + _rsock = greenio.GreenSocket(csock) + _rsock.settimeout(None) + + for i in six.moves.range(_nthreads): + t = threading.Thread(target=tworker, + name="tpool_thread_%s" % i) + t.daemon = True + t.start() + _threads.append(t) + + _coro = greenthread.spawn_n(tpool_trampoline) + # This yield fixes subtle error with GreenSocket.__del__ + eventlet.sleep(0) + + +# Avoid ResourceWarning unclosed socket on Python3.2+ +@atexit.register +def killall(): + global _setup_already, _rspq, _rsock, _wsock + if not _setup_already: + return + + # This yield fixes freeze in some scenarios + eventlet.sleep(0) + + for thr in _threads: + _reqq.put(None) + for thr in _threads: + thr.join() + del _threads[:] + + # return any remaining results + while (_rspq is not None) and not _rspq.empty(): + try: + (e, rv) = _rspq.get(block=False) + e.send(rv) + e = rv = None + except Empty: + pass + + if _coro is not None: + greenthread.kill(_coro) + if _rsock is not None: + _rsock.close() + _rsock = None + if _wsock is not None: + _wsock.close() + _wsock = None + _rspq = None + _setup_already = False + + +def set_num_threads(nthreads): + global _nthreads + _nthreads = nthreads diff --git a/venv/lib/python3.12/site-packages/eventlet/websocket.py b/venv/lib/python3.12/site-packages/eventlet/websocket.py new file mode 100644 index 0000000..01245b8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/websocket.py @@ -0,0 +1,864 @@ +import base64 +import codecs +import collections +import errno +from random import Random +from socket import error as SocketError +import string +import struct +import sys +import time + +import zlib + +try: + from hashlib import md5, sha1 +except ImportError: # pragma NO COVER + from md5 import md5 + from sha import sha as sha1 + +from eventlet import semaphore +from eventlet import wsgi +from eventlet.green import socket +from eventlet.support import get_errno +import six + +# Python 2's utf8 decoding is more lenient than we'd like +# In order to pass autobahn's testsuite we need stricter validation +# if available... +for _mod in ('wsaccel.utf8validator', 'autobahn.utf8validator'): + # autobahn has it's own python-based validator. in newest versions + # this prefers to use wsaccel, a cython based implementation, if available. + # wsaccel may also be installed w/out autobahn, or with a earlier version. + try: + utf8validator = __import__(_mod, {}, {}, ['']) + except ImportError: + utf8validator = None + else: + break + +ACCEPTABLE_CLIENT_ERRORS = set((errno.ECONNRESET, errno.EPIPE)) +DEFAULT_MAX_FRAME_LENGTH = 8 << 20 + +__all__ = ["WebSocketWSGI", "WebSocket"] +PROTOCOL_GUID = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11' +VALID_CLOSE_STATUS = set( + list(range(1000, 1004)) + + list(range(1007, 1012)) + + # 3000-3999: reserved for use by libraries, frameworks, + # and applications + list(range(3000, 4000)) + + # 4000-4999: reserved for private use and thus can't + # be registered + list(range(4000, 5000)) +) + + +class BadRequest(Exception): + def __init__(self, status='400 Bad Request', body=None, headers=None): + super(Exception, self).__init__() + self.status = status + self.body = body + self.headers = headers + + +class WebSocketWSGI(object): + """Wraps a websocket handler function in a WSGI application. + + Use it like this:: + + @websocket.WebSocketWSGI + def my_handler(ws): + from_browser = ws.wait() + ws.send("from server") + + The single argument to the function will be an instance of + :class:`WebSocket`. To close the socket, simply return from the + function. Note that the server will log the websocket request at + the time of closure. + + An optional argument max_frame_length can be given, which will set the + maximum incoming *uncompressed* payload length of a frame. By default, this + is set to 8MiB. Note that excessive values here might create a DOS attack + vector. + """ + + def __init__(self, handler, max_frame_length=DEFAULT_MAX_FRAME_LENGTH): + self.handler = handler + self.protocol_version = None + self.support_legacy_versions = True + self.supported_protocols = [] + self.origin_checker = None + self.max_frame_length = max_frame_length + + @classmethod + def configured(cls, + handler=None, + supported_protocols=None, + origin_checker=None, + support_legacy_versions=False): + def decorator(handler): + inst = cls(handler) + inst.support_legacy_versions = support_legacy_versions + inst.origin_checker = origin_checker + if supported_protocols: + inst.supported_protocols = supported_protocols + return inst + if handler is None: + return decorator + return decorator(handler) + + def __call__(self, environ, start_response): + http_connection_parts = [ + part.strip() + for part in environ.get('HTTP_CONNECTION', '').lower().split(',')] + if not ('upgrade' in http_connection_parts and + environ.get('HTTP_UPGRADE', '').lower() == 'websocket'): + # need to check a few more things here for true compliance + start_response('400 Bad Request', [('Connection', 'close')]) + return [] + + try: + if 'HTTP_SEC_WEBSOCKET_VERSION' in environ: + ws = self._handle_hybi_request(environ) + elif self.support_legacy_versions: + ws = self._handle_legacy_request(environ) + else: + raise BadRequest() + except BadRequest as e: + status = e.status + body = e.body or b'' + headers = e.headers or [] + start_response(status, + [('Connection', 'close'), ] + headers) + return [body] + + try: + self.handler(ws) + except socket.error as e: + if get_errno(e) not in ACCEPTABLE_CLIENT_ERRORS: + raise + # Make sure we send the closing frame + ws._send_closing_frame(True) + # use this undocumented feature of eventlet.wsgi to ensure that it + # doesn't barf on the fact that we didn't call start_response + wsgi.WSGI_LOCAL.already_handled = True + return [] + + def _handle_legacy_request(self, environ): + if 'eventlet.input' in environ: + sock = environ['eventlet.input'].get_socket() + elif 'gunicorn.socket' in environ: + sock = environ['gunicorn.socket'] + else: + raise Exception('No eventlet.input or gunicorn.socket present in environ.') + + if 'HTTP_SEC_WEBSOCKET_KEY1' in environ: + self.protocol_version = 76 + if 'HTTP_SEC_WEBSOCKET_KEY2' not in environ: + raise BadRequest() + else: + self.protocol_version = 75 + + if self.protocol_version == 76: + key1 = self._extract_number(environ['HTTP_SEC_WEBSOCKET_KEY1']) + key2 = self._extract_number(environ['HTTP_SEC_WEBSOCKET_KEY2']) + # There's no content-length header in the request, but it has 8 + # bytes of data. + environ['wsgi.input'].content_length = 8 + key3 = environ['wsgi.input'].read(8) + key = struct.pack(">II", key1, key2) + key3 + response = md5(key).digest() + + # Start building the response + scheme = 'ws' + if environ.get('wsgi.url_scheme') == 'https': + scheme = 'wss' + location = '%s://%s%s%s' % ( + scheme, + environ.get('HTTP_HOST'), + environ.get('SCRIPT_NAME'), + environ.get('PATH_INFO') + ) + qs = environ.get('QUERY_STRING') + if qs is not None: + location += '?' + qs + if self.protocol_version == 75: + handshake_reply = ( + b"HTTP/1.1 101 Web Socket Protocol Handshake\r\n" + b"Upgrade: WebSocket\r\n" + b"Connection: Upgrade\r\n" + b"WebSocket-Origin: " + six.b(environ.get('HTTP_ORIGIN')) + b"\r\n" + b"WebSocket-Location: " + six.b(location) + b"\r\n\r\n" + ) + elif self.protocol_version == 76: + handshake_reply = ( + b"HTTP/1.1 101 WebSocket Protocol Handshake\r\n" + b"Upgrade: WebSocket\r\n" + b"Connection: Upgrade\r\n" + b"Sec-WebSocket-Origin: " + six.b(environ.get('HTTP_ORIGIN')) + b"\r\n" + b"Sec-WebSocket-Protocol: " + + six.b(environ.get('HTTP_SEC_WEBSOCKET_PROTOCOL', 'default')) + b"\r\n" + b"Sec-WebSocket-Location: " + six.b(location) + b"\r\n" + b"\r\n" + response + ) + else: # pragma NO COVER + raise ValueError("Unknown WebSocket protocol version.") + sock.sendall(handshake_reply) + return WebSocket(sock, environ, self.protocol_version) + + def _parse_extension_header(self, header): + if header is None: + return None + res = {} + for ext in header.split(","): + parts = ext.split(";") + config = {} + for part in parts[1:]: + key_val = part.split("=") + if len(key_val) == 1: + config[key_val[0].strip().lower()] = True + else: + config[key_val[0].strip().lower()] = key_val[1].strip().strip('"').lower() + res.setdefault(parts[0].strip().lower(), []).append(config) + return res + + def _negotiate_permessage_deflate(self, extensions): + if not extensions: + return None + deflate = extensions.get("permessage-deflate") + if deflate is None: + return None + for config in deflate: + # We'll evaluate each config in the client's preferred order and pick + # the first that we can support. + want_config = { + # These are bool options, we can support both + "server_no_context_takeover": config.get("server_no_context_takeover", False), + "client_no_context_takeover": config.get("client_no_context_takeover", False) + } + # These are either bool OR int options. True means the client can accept a value + # for the option, a number means the client wants that specific value. + max_wbits = min(zlib.MAX_WBITS, 15) + mwb = config.get("server_max_window_bits") + if mwb is not None: + if mwb is True: + want_config["server_max_window_bits"] = max_wbits + else: + want_config["server_max_window_bits"] = \ + int(config.get("server_max_window_bits", max_wbits)) + if not (8 <= want_config["server_max_window_bits"] <= 15): + continue + mwb = config.get("client_max_window_bits") + if mwb is not None: + if mwb is True: + want_config["client_max_window_bits"] = max_wbits + else: + want_config["client_max_window_bits"] = \ + int(config.get("client_max_window_bits", max_wbits)) + if not (8 <= want_config["client_max_window_bits"] <= 15): + continue + return want_config + return None + + def _format_extension_header(self, parsed_extensions): + if not parsed_extensions: + return None + parts = [] + for name, config in parsed_extensions.items(): + ext_parts = [six.b(name)] + for key, value in config.items(): + if value is False: + pass + elif value is True: + ext_parts.append(six.b(key)) + else: + ext_parts.append(six.b("%s=%s" % (key, str(value)))) + parts.append(b"; ".join(ext_parts)) + return b", ".join(parts) + + def _handle_hybi_request(self, environ): + if 'eventlet.input' in environ: + sock = environ['eventlet.input'].get_socket() + elif 'gunicorn.socket' in environ: + sock = environ['gunicorn.socket'] + else: + raise Exception('No eventlet.input or gunicorn.socket present in environ.') + + hybi_version = environ['HTTP_SEC_WEBSOCKET_VERSION'] + if hybi_version not in ('8', '13', ): + raise BadRequest(status='426 Upgrade Required', + headers=[('Sec-WebSocket-Version', '8, 13')]) + self.protocol_version = int(hybi_version) + if 'HTTP_SEC_WEBSOCKET_KEY' not in environ: + # That's bad. + raise BadRequest() + origin = environ.get( + 'HTTP_ORIGIN', + (environ.get('HTTP_SEC_WEBSOCKET_ORIGIN', '') + if self.protocol_version <= 8 else '')) + if self.origin_checker is not None: + if not self.origin_checker(environ.get('HTTP_HOST'), origin): + raise BadRequest(status='403 Forbidden') + protocols = environ.get('HTTP_SEC_WEBSOCKET_PROTOCOL', None) + negotiated_protocol = None + if protocols: + for p in (i.strip() for i in protocols.split(',')): + if p in self.supported_protocols: + negotiated_protocol = p + break + + key = environ['HTTP_SEC_WEBSOCKET_KEY'] + response = base64.b64encode(sha1(six.b(key) + PROTOCOL_GUID).digest()) + handshake_reply = [b"HTTP/1.1 101 Switching Protocols", + b"Upgrade: websocket", + b"Connection: Upgrade", + b"Sec-WebSocket-Accept: " + response] + if negotiated_protocol: + handshake_reply.append(b"Sec-WebSocket-Protocol: " + six.b(negotiated_protocol)) + + parsed_extensions = {} + extensions = self._parse_extension_header(environ.get("HTTP_SEC_WEBSOCKET_EXTENSIONS")) + + deflate = self._negotiate_permessage_deflate(extensions) + if deflate is not None: + parsed_extensions["permessage-deflate"] = deflate + + formatted_ext = self._format_extension_header(parsed_extensions) + if formatted_ext is not None: + handshake_reply.append(b"Sec-WebSocket-Extensions: " + formatted_ext) + + sock.sendall(b'\r\n'.join(handshake_reply) + b'\r\n\r\n') + return RFC6455WebSocket(sock, environ, self.protocol_version, + protocol=negotiated_protocol, + extensions=parsed_extensions, + max_frame_length=self.max_frame_length) + + def _extract_number(self, value): + """ + Utility function which, given a string like 'g98sd 5[]221@1', will + return 9852211. Used to parse the Sec-WebSocket-Key headers. + """ + out = "" + spaces = 0 + for char in value: + if char in string.digits: + out += char + elif char == " ": + spaces += 1 + return int(out) // spaces + + +class WebSocket(object): + """A websocket object that handles the details of + serialization/deserialization to the socket. + + The primary way to interact with a :class:`WebSocket` object is to + call :meth:`send` and :meth:`wait` in order to pass messages back + and forth with the browser. Also available are the following + properties: + + path + The path value of the request. This is the same as the WSGI PATH_INFO variable, + but more convenient. + protocol + The value of the Websocket-Protocol header. + origin + The value of the 'Origin' header. + environ + The full WSGI environment for this request. + + """ + + def __init__(self, sock, environ, version=76): + """ + :param socket: The eventlet socket + :type socket: :class:`eventlet.greenio.GreenSocket` + :param environ: The wsgi environment + :param version: The WebSocket spec version to follow (default is 76) + """ + self.log = environ.get('wsgi.errors', sys.stderr) + self.log_context = 'server={shost}/{spath} client={caddr}:{cport}'.format( + shost=environ.get('HTTP_HOST'), + spath=environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', ''), + caddr=environ.get('REMOTE_ADDR'), cport=environ.get('REMOTE_PORT'), + ) + self.socket = sock + self.origin = environ.get('HTTP_ORIGIN') + self.protocol = environ.get('HTTP_WEBSOCKET_PROTOCOL') + self.path = environ.get('PATH_INFO') + self.environ = environ + self.version = version + self.websocket_closed = False + self._buf = b"" + self._msgs = collections.deque() + self._sendlock = semaphore.Semaphore() + + def _pack_message(self, message): + """Pack the message inside ``00`` and ``FF`` + + As per the dataframing section (5.3) for the websocket spec + """ + if isinstance(message, six.text_type): + message = message.encode('utf-8') + elif not isinstance(message, six.binary_type): + message = six.b(str(message)) + packed = b"\x00" + message + b"\xFF" + return packed + + def _parse_messages(self): + """ Parses for messages in the buffer *buf*. It is assumed that + the buffer contains the start character for a message, but that it + may contain only part of the rest of the message. + + Returns an array of messages, and the buffer remainder that + didn't contain any full messages.""" + msgs = [] + end_idx = 0 + buf = self._buf + while buf: + frame_type = six.indexbytes(buf, 0) + if frame_type == 0: + # Normal message. + end_idx = buf.find(b"\xFF") + if end_idx == -1: # pragma NO COVER + break + msgs.append(buf[1:end_idx].decode('utf-8', 'replace')) + buf = buf[end_idx + 1:] + elif frame_type == 255: + # Closing handshake. + assert six.indexbytes(buf, 1) == 0, "Unexpected closing handshake: %r" % buf + self.websocket_closed = True + break + else: + raise ValueError("Don't understand how to parse this type of message: %r" % buf) + self._buf = buf + return msgs + + def send(self, message): + """Send a message to the browser. + + *message* should be convertable to a string; unicode objects should be + encodable as utf-8. Raises socket.error with errno of 32 + (broken pipe) if the socket has already been closed by the client.""" + packed = self._pack_message(message) + # if two greenthreads are trying to send at the same time + # on the same socket, sendlock prevents interleaving and corruption + self._sendlock.acquire() + try: + self.socket.sendall(packed) + finally: + self._sendlock.release() + + def wait(self): + """Waits for and deserializes messages. + + Returns a single message; the oldest not yet processed. If the client + has already closed the connection, returns None. This is different + from normal socket behavior because the empty string is a valid + websocket message.""" + while not self._msgs: + # Websocket might be closed already. + if self.websocket_closed: + return None + # no parsed messages, must mean buf needs more data + delta = self.socket.recv(8096) + if delta == b'': + return None + self._buf += delta + msgs = self._parse_messages() + self._msgs.extend(msgs) + return self._msgs.popleft() + + def _send_closing_frame(self, ignore_send_errors=False): + """Sends the closing frame to the client, if required.""" + if self.version == 76 and not self.websocket_closed: + try: + self.socket.sendall(b"\xff\x00") + except SocketError: + # Sometimes, like when the remote side cuts off the connection, + # we don't care about this. + if not ignore_send_errors: # pragma NO COVER + raise + self.websocket_closed = True + + def close(self): + """Forcibly close the websocket; generally it is preferable to + return from the handler method.""" + try: + self._send_closing_frame(True) + self.socket.shutdown(True) + except SocketError as e: + if e.errno != errno.ENOTCONN: + self.log.write('{ctx} socket shutdown error: {e}'.format(ctx=self.log_context, e=e)) + finally: + self.socket.close() + + +class ConnectionClosedError(Exception): + pass + + +class FailedConnectionError(Exception): + def __init__(self, status, message): + super(FailedConnectionError, self).__init__(status, message) + self.message = message + self.status = status + + +class ProtocolError(ValueError): + pass + + +class RFC6455WebSocket(WebSocket): + def __init__(self, sock, environ, version=13, protocol=None, client=False, extensions=None, + max_frame_length=DEFAULT_MAX_FRAME_LENGTH): + super(RFC6455WebSocket, self).__init__(sock, environ, version) + self.iterator = self._iter_frames() + self.client = client + self.protocol = protocol + self.extensions = extensions or {} + + self._deflate_enc = None + self._deflate_dec = None + self.max_frame_length = max_frame_length + self._remote_close_data = None + + class UTF8Decoder(object): + def __init__(self): + if utf8validator: + self.validator = utf8validator.Utf8Validator() + else: + self.validator = None + decoderclass = codecs.getincrementaldecoder('utf8') + self.decoder = decoderclass() + + def reset(self): + if self.validator: + self.validator.reset() + self.decoder.reset() + + def decode(self, data, final=False): + if self.validator: + valid, eocp, c_i, t_i = self.validator.validate(data) + if not valid: + raise ValueError('Data is not valid unicode') + return self.decoder.decode(data, final) + + def _get_permessage_deflate_enc(self): + options = self.extensions.get("permessage-deflate") + if options is None: + return None + + def _make(): + return zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, + -options.get("client_max_window_bits" if self.client + else "server_max_window_bits", + zlib.MAX_WBITS)) + + if options.get("client_no_context_takeover" if self.client + else "server_no_context_takeover"): + # This option means we have to make a new one every time + return _make() + else: + if self._deflate_enc is None: + self._deflate_enc = _make() + return self._deflate_enc + + def _get_permessage_deflate_dec(self, rsv1): + options = self.extensions.get("permessage-deflate") + if options is None or not rsv1: + return None + + def _make(): + return zlib.decompressobj(-options.get("server_max_window_bits" if self.client + else "client_max_window_bits", + zlib.MAX_WBITS)) + + if options.get("server_no_context_takeover" if self.client + else "client_no_context_takeover"): + # This option means we have to make a new one every time + return _make() + else: + if self._deflate_dec is None: + self._deflate_dec = _make() + return self._deflate_dec + + def _get_bytes(self, numbytes): + data = b'' + while len(data) < numbytes: + d = self.socket.recv(numbytes - len(data)) + if not d: + raise ConnectionClosedError() + data = data + d + return data + + class Message(object): + def __init__(self, opcode, max_frame_length, decoder=None, decompressor=None): + self.decoder = decoder + self.data = [] + self.finished = False + self.opcode = opcode + self.decompressor = decompressor + self.max_frame_length = max_frame_length + + def push(self, data, final=False): + self.finished = final + self.data.append(data) + + def getvalue(self): + data = b"".join(self.data) + if not self.opcode & 8 and self.decompressor: + data = self.decompressor.decompress(data + b"\x00\x00\xff\xff", self.max_frame_length) + if self.decompressor.unconsumed_tail: + raise FailedConnectionError( + 1009, + "Incoming compressed frame exceeds length limit of {} bytes.".format(self.max_frame_length)) + + if self.decoder: + data = self.decoder.decode(data, self.finished) + return data + + @staticmethod + def _apply_mask(data, mask, length=None, offset=0): + if length is None: + length = len(data) + cnt = range(length) + return b''.join(six.int2byte(six.indexbytes(data, i) ^ mask[(offset + i) % 4]) for i in cnt) + + def _handle_control_frame(self, opcode, data): + if opcode == 8: # connection close + self._remote_close_data = data + if not data: + status = 1000 + elif len(data) > 1: + status = struct.unpack_from('!H', data)[0] + if not status or status not in VALID_CLOSE_STATUS: + raise FailedConnectionError( + 1002, + "Unexpected close status code.") + try: + data = self.UTF8Decoder().decode(data[2:], True) + except (UnicodeDecodeError, ValueError): + raise FailedConnectionError( + 1002, + "Close message data should be valid UTF-8.") + else: + status = 1002 + self.close(close_data=(status, '')) + raise ConnectionClosedError() + elif opcode == 9: # ping + self.send(data, control_code=0xA) + elif opcode == 0xA: # pong + pass + else: + raise FailedConnectionError( + 1002, "Unknown control frame received.") + + def _iter_frames(self): + fragmented_message = None + try: + while True: + message = self._recv_frame(message=fragmented_message) + if message.opcode & 8: + self._handle_control_frame( + message.opcode, message.getvalue()) + continue + if fragmented_message and message is not fragmented_message: + raise RuntimeError('Unexpected message change.') + fragmented_message = message + if message.finished: + data = fragmented_message.getvalue() + fragmented_message = None + yield data + except FailedConnectionError: + exc_typ, exc_val, exc_tb = sys.exc_info() + self.close(close_data=(exc_val.status, exc_val.message)) + except ConnectionClosedError: + return + except Exception: + self.close(close_data=(1011, 'Internal Server Error')) + raise + + def _recv_frame(self, message=None): + recv = self._get_bytes + + # Unpacking the frame described in Section 5.2 of RFC6455 + # (https://tools.ietf.org/html/rfc6455#section-5.2) + header = recv(2) + a, b = struct.unpack('!BB', header) + finished = a >> 7 == 1 + rsv123 = a >> 4 & 7 + rsv1 = rsv123 & 4 + if rsv123: + if rsv1 and "permessage-deflate" not in self.extensions: + # must be zero - unless it's compressed then rsv1 is true + raise FailedConnectionError( + 1002, + "RSV1, RSV2, RSV3: MUST be 0 unless an extension is" + " negotiated that defines meanings for non-zero values.") + opcode = a & 15 + if opcode not in (0, 1, 2, 8, 9, 0xA): + raise FailedConnectionError(1002, "Unknown opcode received.") + masked = b & 128 == 128 + if not masked and not self.client: + raise FailedConnectionError(1002, "A client MUST mask all frames" + " that it sends to the server") + length = b & 127 + if opcode & 8: + if not finished: + raise FailedConnectionError(1002, "Control frames must not" + " be fragmented.") + if length > 125: + raise FailedConnectionError( + 1002, + "All control frames MUST have a payload length of 125" + " bytes or less") + elif opcode and message: + raise FailedConnectionError( + 1002, + "Received a non-continuation opcode within" + " fragmented message.") + elif not opcode and not message: + raise FailedConnectionError( + 1002, + "Received continuation opcode with no previous" + " fragments received.") + if length == 126: + length = struct.unpack('!H', recv(2))[0] + elif length == 127: + length = struct.unpack('!Q', recv(8))[0] + + if length > self.max_frame_length: + raise FailedConnectionError(1009, "Incoming frame of {} bytes is above length limit of {} bytes.".format( + length, self.max_frame_length)) + if masked: + mask = struct.unpack('!BBBB', recv(4)) + received = 0 + if not message or opcode & 8: + decoder = self.UTF8Decoder() if opcode == 1 else None + decompressor = self._get_permessage_deflate_dec(rsv1) + message = self.Message(opcode, self.max_frame_length, decoder=decoder, decompressor=decompressor) + if not length: + message.push(b'', final=finished) + else: + while received < length: + d = self.socket.recv(length - received) + if not d: + raise ConnectionClosedError() + dlen = len(d) + if masked: + d = self._apply_mask(d, mask, length=dlen, offset=received) + received = received + dlen + try: + message.push(d, final=finished) + except (UnicodeDecodeError, ValueError): + raise FailedConnectionError( + 1007, "Text data must be valid utf-8") + return message + + def _pack_message(self, message, masked=False, + continuation=False, final=True, control_code=None): + is_text = False + if isinstance(message, six.text_type): + message = message.encode('utf-8') + is_text = True + + compress_bit = 0 + compressor = self._get_permessage_deflate_enc() + # Control frames are identified by opcodes where the most significant + # bit of the opcode is 1. Currently defined opcodes for control frames + # include 0x8 (Close), 0x9 (Ping), and 0xA (Pong). Opcodes 0xB-0xF are + # reserved for further control frames yet to be defined. + # https://datatracker.ietf.org/doc/html/rfc6455#section-5.5 + is_control_frame = (control_code or 0) & 8 + # An endpoint MUST NOT set the "Per-Message Compressed" bit of control + # frames and non-first fragments of a data message. An endpoint + # receiving such a frame MUST _Fail the WebSocket Connection_. + # https://datatracker.ietf.org/doc/html/rfc7692#section-6.1 + if message and compressor and not is_control_frame: + message = compressor.compress(message) + message += compressor.flush(zlib.Z_SYNC_FLUSH) + assert message[-4:] == b"\x00\x00\xff\xff" + message = message[:-4] + compress_bit = 1 << 6 + + length = len(message) + if not length: + # no point masking empty data + masked = False + if control_code: + if control_code not in (8, 9, 0xA): + raise ProtocolError('Unknown control opcode.') + if continuation or not final: + raise ProtocolError('Control frame cannot be a fragment.') + if length > 125: + raise ProtocolError('Control frame data too large (>125).') + header = struct.pack('!B', control_code | 1 << 7) + else: + opcode = 0 if continuation else ((1 if is_text else 2) | compress_bit) + header = struct.pack('!B', opcode | (1 << 7 if final else 0)) + lengthdata = 1 << 7 if masked else 0 + if length > 65535: + lengthdata = struct.pack('!BQ', lengthdata | 127, length) + elif length > 125: + lengthdata = struct.pack('!BH', lengthdata | 126, length) + else: + lengthdata = struct.pack('!B', lengthdata | length) + if masked: + # NOTE: RFC6455 states: + # A server MUST NOT mask any frames that it sends to the client + rand = Random(time.time()) + mask = [rand.getrandbits(8) for _ in six.moves.xrange(4)] + message = RFC6455WebSocket._apply_mask(message, mask, length) + maskdata = struct.pack('!BBBB', *mask) + else: + maskdata = b'' + + return b''.join((header, lengthdata, maskdata, message)) + + def wait(self): + for i in self.iterator: + return i + + def _send(self, frame): + self._sendlock.acquire() + try: + self.socket.sendall(frame) + finally: + self._sendlock.release() + + def send(self, message, **kw): + kw['masked'] = self.client + payload = self._pack_message(message, **kw) + self._send(payload) + + def _send_closing_frame(self, ignore_send_errors=False, close_data=None): + if self.version in (8, 13) and not self.websocket_closed: + if close_data is not None: + status, msg = close_data + if isinstance(msg, six.text_type): + msg = msg.encode('utf-8') + data = struct.pack('!H', status) + msg + else: + data = '' + try: + self.send(data, control_code=8) + except SocketError: + # Sometimes, like when the remote side cuts off the connection, + # we don't care about this. + if not ignore_send_errors: # pragma NO COVER + raise + self.websocket_closed = True + + def close(self, close_data=None): + """Forcibly close the websocket; generally it is preferable to + return from the handler method.""" + try: + self._send_closing_frame(close_data=close_data, ignore_send_errors=True) + self.socket.shutdown(socket.SHUT_WR) + except SocketError as e: + if e.errno != errno.ENOTCONN: + self.log.write('{ctx} socket shutdown error: {e}'.format(ctx=self.log_context, e=e)) + finally: + self.socket.close() diff --git a/venv/lib/python3.12/site-packages/eventlet/wsgi.py b/venv/lib/python3.12/site-packages/eventlet/wsgi.py new file mode 100644 index 0000000..7ef0254 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/wsgi.py @@ -0,0 +1,1031 @@ +import errno +import os +import sys +import time +import traceback +import types +import warnings + +import eventlet +from eventlet import greenio +from eventlet import support +from eventlet.corolocal import local +from eventlet.green import BaseHTTPServer +from eventlet.green import socket +import six +from six.moves import urllib + + +DEFAULT_MAX_SIMULTANEOUS_REQUESTS = 1024 +DEFAULT_MAX_HTTP_VERSION = 'HTTP/1.1' +MAX_REQUEST_LINE = 8192 +MAX_HEADER_LINE = 8192 +MAX_TOTAL_HEADER_SIZE = 65536 +MINIMUM_CHUNK_SIZE = 4096 +# %(client_port)s is also available +DEFAULT_LOG_FORMAT = ('%(client_ip)s - - [%(date_time)s] "%(request_line)s"' + ' %(status_code)s %(body_length)s %(wall_seconds).6f') +RESPONSE_414 = b'''HTTP/1.0 414 Request URI Too Long\r\n\ +Connection: close\r\n\ +Content-Length: 0\r\n\r\n''' +is_accepting = True + +STATE_IDLE = 'idle' +STATE_REQUEST = 'request' +STATE_CLOSE = 'close' + +__all__ = ['server', 'format_date_time'] + +# Weekday and month names for HTTP date/time formatting; always English! +_weekdayname = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] +_monthname = [None, # Dummy so we can use 1-based month numbers + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + + +def format_date_time(timestamp): + """Formats a unix timestamp into an HTTP standard string.""" + year, month, day, hh, mm, ss, wd, _y, _z = time.gmtime(timestamp) + return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % ( + _weekdayname[wd], day, _monthname[month], year, hh, mm, ss + ) + + +def addr_to_host_port(addr): + host = 'unix' + port = '' + if isinstance(addr, tuple): + host = addr[0] + port = addr[1] + return (host, port) + + +# Collections of error codes to compare against. Not all attributes are set +# on errno module on all platforms, so some are literals :( +BAD_SOCK = set((errno.EBADF, 10053)) +BROKEN_SOCK = set((errno.EPIPE, errno.ECONNRESET)) + + +class ChunkReadError(ValueError): + pass + + +WSGI_LOCAL = local() + + +class Input(object): + + def __init__(self, + rfile, + content_length, + sock, + wfile=None, + wfile_line=None, + chunked_input=False): + + self.rfile = rfile + self._sock = sock + if content_length is not None: + content_length = int(content_length) + self.content_length = content_length + + self.wfile = wfile + self.wfile_line = wfile_line + + self.position = 0 + self.chunked_input = chunked_input + self.chunk_length = -1 + + # (optional) headers to send with a "100 Continue" response. Set by + # calling set_hundred_continue_respose_headers() on env['wsgi.input'] + self.hundred_continue_headers = None + self.is_hundred_continue_response_sent = False + + # handle_one_response should give us a ref to the response state so we + # know whether we can still send the 100 Continue; until then, though, + # we're flying blind + self.headers_sent = None + + def send_hundred_continue_response(self): + if self.headers_sent: + # To late; application has already started sending data back + # to the client + # TODO: maybe log a warning if self.hundred_continue_headers + # is not None? + return + + towrite = [] + + # 100 Continue status line + towrite.append(self.wfile_line) + + # Optional headers + if self.hundred_continue_headers is not None: + # 100 Continue headers + for header in self.hundred_continue_headers: + towrite.append(six.b('%s: %s\r\n' % header)) + + # Blank line + towrite.append(b'\r\n') + + self.wfile.writelines(towrite) + self.wfile.flush() + + # Reinitialize chunk_length (expect more data) + self.chunk_length = -1 + + @property + def should_send_hundred_continue(self): + return self.wfile is not None and not self.is_hundred_continue_response_sent + + def _do_read(self, reader, length=None): + if self.should_send_hundred_continue: + # 100 Continue response + self.send_hundred_continue_response() + self.is_hundred_continue_response_sent = True + if (self.content_length is not None) and ( + length is None or length > self.content_length - self.position): + length = self.content_length - self.position + if not length: + return b'' + try: + read = reader(length) + except greenio.SSL.ZeroReturnError: + read = b'' + self.position += len(read) + return read + + def _chunked_read(self, rfile, length=None, use_readline=False): + if self.should_send_hundred_continue: + # 100 Continue response + self.send_hundred_continue_response() + self.is_hundred_continue_response_sent = True + try: + if length == 0: + return b"" + + if length and length < 0: + length = None + + if use_readline: + reader = self.rfile.readline + else: + reader = self.rfile.read + + response = [] + while self.chunk_length != 0: + maxreadlen = self.chunk_length - self.position + if length is not None and length < maxreadlen: + maxreadlen = length + + if maxreadlen > 0: + data = reader(maxreadlen) + if not data: + self.chunk_length = 0 + raise IOError("unexpected end of file while parsing chunked data") + + datalen = len(data) + response.append(data) + + self.position += datalen + if self.chunk_length == self.position: + rfile.readline() + + if length is not None: + length -= datalen + if length == 0: + break + if use_readline and data[-1:] == b"\n": + break + else: + try: + self.chunk_length = int(rfile.readline().split(b";", 1)[0], 16) + except ValueError as err: + raise ChunkReadError(err) + self.position = 0 + if self.chunk_length == 0: + rfile.readline() + except greenio.SSL.ZeroReturnError: + pass + return b''.join(response) + + def read(self, length=None): + if self.chunked_input: + return self._chunked_read(self.rfile, length) + return self._do_read(self.rfile.read, length) + + def readline(self, size=None): + if self.chunked_input: + return self._chunked_read(self.rfile, size, True) + else: + return self._do_read(self.rfile.readline, size) + + def readlines(self, hint=None): + if self.chunked_input: + lines = [] + for line in iter(self.readline, b''): + lines.append(line) + if hint and hint > 0: + hint -= len(line) + if hint <= 0: + break + return lines + else: + return self._do_read(self.rfile.readlines, hint) + + def __iter__(self): + return iter(self.read, b'') + + def get_socket(self): + return self._sock + + def set_hundred_continue_response_headers(self, headers, + capitalize_response_headers=True): + # Response headers capitalization (default) + # CONTent-TYpe: TExt/PlaiN -> Content-Type: TExt/PlaiN + # Per HTTP RFC standard, header name is case-insensitive. + # Please, fix your client to ignore header case if possible. + if capitalize_response_headers: + headers = [ + ('-'.join([x.capitalize() for x in key.split('-')]), value) + for key, value in headers] + self.hundred_continue_headers = headers + + def discard(self, buffer_size=16 << 10): + while self.read(buffer_size): + pass + + +class HeaderLineTooLong(Exception): + pass + + +class HeadersTooLarge(Exception): + pass + + +def get_logger(log, debug): + if callable(getattr(log, 'info', None)) \ + and callable(getattr(log, 'debug', None)): + return log + else: + return LoggerFileWrapper(log or sys.stderr, debug) + + +class LoggerNull(object): + def __init__(self): + pass + + def error(self, msg, *args, **kwargs): + pass + + def info(self, msg, *args, **kwargs): + pass + + def debug(self, msg, *args, **kwargs): + pass + + def write(self, msg, *args): + pass + + +class LoggerFileWrapper(LoggerNull): + def __init__(self, log, debug): + self.log = log + self._debug = debug + + def error(self, msg, *args, **kwargs): + self.write(msg, *args) + + def info(self, msg, *args, **kwargs): + self.write(msg, *args) + + def debug(self, msg, *args, **kwargs): + if self._debug: + self.write(msg, *args) + + def write(self, msg, *args): + msg = msg + '\n' + if args: + msg = msg % args + self.log.write(msg) + + +class FileObjectForHeaders(object): + + def __init__(self, fp): + self.fp = fp + self.total_header_size = 0 + + def readline(self, size=-1): + sz = size + if size < 0: + sz = MAX_HEADER_LINE + rv = self.fp.readline(sz) + if len(rv) >= MAX_HEADER_LINE: + raise HeaderLineTooLong() + self.total_header_size += len(rv) + if self.total_header_size > MAX_TOTAL_HEADER_SIZE: + raise HeadersTooLarge() + return rv + + +class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): + protocol_version = 'HTTP/1.1' + minimum_chunk_size = MINIMUM_CHUNK_SIZE + capitalize_response_headers = True + + # https://github.com/eventlet/eventlet/issues/295 + # Stdlib default is 0 (unbuffered), but then `wfile.writelines()` looses data + # so before going back to unbuffered, remove any usage of `writelines`. + wbufsize = 16 << 10 + + def __init__(self, conn_state, server): + self.request = conn_state[1] + self.client_address = conn_state[0] + self.conn_state = conn_state + self.server = server + self.setup() + try: + self.handle() + finally: + self.finish() + + def setup(self): + # overriding SocketServer.setup to correctly handle SSL.Connection objects + conn = self.connection = self.request + + # TCP_QUICKACK is a better alternative to disabling Nagle's algorithm + # https://news.ycombinator.com/item?id=10607422 + if getattr(socket, 'TCP_QUICKACK', None): + try: + conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, True) + except socket.error: + pass + + try: + self.rfile = conn.makefile('rb', self.rbufsize) + self.wfile = conn.makefile('wb', self.wbufsize) + except (AttributeError, NotImplementedError): + if hasattr(conn, 'send') and hasattr(conn, 'recv'): + # it's an SSL.Connection + self.rfile = socket._fileobject(conn, "rb", self.rbufsize) + self.wfile = socket._fileobject(conn, "wb", self.wbufsize) + else: + # it's a SSLObject, or a martian + raise NotImplementedError( + '''eventlet.wsgi doesn't support sockets of type {0}'''.format(type(conn))) + + def handle(self): + self.close_connection = True + + while True: + self.handle_one_request() + if self.conn_state[2] == STATE_CLOSE: + self.close_connection = 1 + if self.close_connection: + break + + def _read_request_line(self): + if self.rfile.closed: + self.close_connection = 1 + return '' + + try: + return self.rfile.readline(self.server.url_length_limit) + except greenio.SSL.ZeroReturnError: + pass + except socket.error as e: + last_errno = support.get_errno(e) + if last_errno in BROKEN_SOCK: + self.server.log.debug('({0}) connection reset by peer {1!r}'.format( + self.server.pid, + self.client_address)) + elif last_errno not in BAD_SOCK: + raise + return '' + + def handle_one_request(self): + if self.server.max_http_version: + self.protocol_version = self.server.max_http_version + + self.raw_requestline = self._read_request_line() + if not self.raw_requestline: + self.close_connection = 1 + return + if len(self.raw_requestline) >= self.server.url_length_limit: + self.wfile.write(RESPONSE_414) + self.close_connection = 1 + return + + orig_rfile = self.rfile + try: + self.rfile = FileObjectForHeaders(self.rfile) + if not self.parse_request(): + return + except HeaderLineTooLong: + self.wfile.write( + b"HTTP/1.0 400 Header Line Too Long\r\n" + b"Connection: close\r\nContent-length: 0\r\n\r\n") + self.close_connection = 1 + return + except HeadersTooLarge: + self.wfile.write( + b"HTTP/1.0 400 Headers Too Large\r\n" + b"Connection: close\r\nContent-length: 0\r\n\r\n") + self.close_connection = 1 + return + finally: + self.rfile = orig_rfile + + content_length = self.headers.get('content-length') + if content_length is not None: + try: + if int(content_length) < 0: + raise ValueError + except ValueError: + # Negative, or not an int at all + self.wfile.write( + b"HTTP/1.0 400 Bad Request\r\n" + b"Connection: close\r\nContent-length: 0\r\n\r\n") + self.close_connection = 1 + return + + self.environ = self.get_environ() + self.application = self.server.app + try: + self.server.outstanding_requests += 1 + try: + self.handle_one_response() + except socket.error as e: + # Broken pipe, connection reset by peer + if support.get_errno(e) not in BROKEN_SOCK: + raise + finally: + self.server.outstanding_requests -= 1 + + def handle_one_response(self): + start = time.time() + headers_set = [] + headers_sent = [] + # Grab the request input now; app may try to replace it in the environ + request_input = self.environ['eventlet.input'] + # Push the headers-sent state into the Input so it won't send a + # 100 Continue response if we've already started a response. + request_input.headers_sent = headers_sent + + wfile = self.wfile + result = None + use_chunked = [False] + length = [0] + status_code = [200] + + def write(data): + towrite = [] + if not headers_set: + raise AssertionError("write() before start_response()") + elif not headers_sent: + status, response_headers = headers_set + headers_sent.append(1) + header_list = [header[0].lower() for header in response_headers] + towrite.append(six.b('%s %s\r\n' % (self.protocol_version, status))) + for header in response_headers: + towrite.append(six.b('%s: %s\r\n' % header)) + + # send Date header? + if 'date' not in header_list: + towrite.append(six.b('Date: %s\r\n' % (format_date_time(time.time()),))) + + client_conn = self.headers.get('Connection', '').lower() + send_keep_alive = False + if self.close_connection == 0 and \ + self.server.keepalive and (client_conn == 'keep-alive' or + (self.request_version == 'HTTP/1.1' and + not client_conn == 'close')): + # only send keep-alives back to clients that sent them, + # it's redundant for 1.1 connections + send_keep_alive = (client_conn == 'keep-alive') + self.close_connection = 0 + else: + self.close_connection = 1 + + if 'content-length' not in header_list: + if self.request_version == 'HTTP/1.1': + use_chunked[0] = True + towrite.append(b'Transfer-Encoding: chunked\r\n') + elif 'content-length' not in header_list: + # client is 1.0 and therefore must read to EOF + self.close_connection = 1 + + if self.close_connection: + towrite.append(b'Connection: close\r\n') + elif send_keep_alive: + towrite.append(b'Connection: keep-alive\r\n') + towrite.append(b'\r\n') + # end of header writing + + if use_chunked[0]: + # Write the chunked encoding + towrite.append(six.b("%x" % (len(data),)) + b"\r\n" + data + b"\r\n") + else: + towrite.append(data) + wfile.writelines(towrite) + wfile.flush() + length[0] = length[0] + sum(map(len, towrite)) + + def start_response(status, response_headers, exc_info=None): + status_code[0] = status.split()[0] + if exc_info: + try: + if headers_sent: + # Re-raise original exception if headers sent + six.reraise(exc_info[0], exc_info[1], exc_info[2]) + finally: + # Avoid dangling circular ref + exc_info = None + + # Response headers capitalization + # CONTent-TYpe: TExt/PlaiN -> Content-Type: TExt/PlaiN + # Per HTTP RFC standard, header name is case-insensitive. + # Please, fix your client to ignore header case if possible. + if self.capitalize_response_headers: + if six.PY2: + def cap(x): + return x.capitalize() + else: + def cap(x): + return x.encode('latin1').capitalize().decode('latin1') + + response_headers = [ + ('-'.join([cap(x) for x in key.split('-')]), value) + for key, value in response_headers] + + headers_set[:] = [status, response_headers] + return write + + try: + try: + WSGI_LOCAL.already_handled = False + result = self.application(self.environ, start_response) + + # Set content-length if possible + if headers_set and not headers_sent and hasattr(result, '__len__'): + # We've got a complete final response + if 'Content-Length' not in [h for h, _v in headers_set[1]]: + headers_set[1].append(('Content-Length', str(sum(map(len, result))))) + if request_input.should_send_hundred_continue: + # We've got a complete final response, and never sent a 100 Continue. + # There's no chance we'll need to read the body as we stream out the + # response, so we can be nice and send a Connection: close header. + self.close_connection = 1 + + towrite = [] + towrite_size = 0 + just_written_size = 0 + minimum_write_chunk_size = int(self.environ.get( + 'eventlet.minimum_write_chunk_size', self.minimum_chunk_size)) + for data in result: + if len(data) == 0: + continue + if isinstance(data, six.text_type): + data = data.encode('ascii') + + towrite.append(data) + towrite_size += len(data) + if towrite_size >= minimum_write_chunk_size: + write(b''.join(towrite)) + towrite = [] + just_written_size = towrite_size + towrite_size = 0 + if WSGI_LOCAL.already_handled: + self.close_connection = 1 + return + if towrite: + just_written_size = towrite_size + write(b''.join(towrite)) + if not headers_sent or (use_chunked[0] and just_written_size): + write(b'') + except Exception: + self.close_connection = 1 + tb = traceback.format_exc() + self.server.log.info(tb) + if not headers_sent: + err_body = six.b(tb) if self.server.debug else b'' + start_response("500 Internal Server Error", + [('Content-type', 'text/plain'), + ('Content-length', len(err_body))]) + write(err_body) + finally: + if hasattr(result, 'close'): + result.close() + if request_input.should_send_hundred_continue: + # We just sent the final response, no 100 Continue. Client may or + # may not have started to send a body, and if we keep the connection + # open we've seen clients either + # * send a body, then start a new request + # * skip the body and go straight to a new request + # Looks like the most broadly compatible option is to close the + # connection and let the client retry. + # https://curl.se/mail/lib-2004-08/0002.html + # Note that we likely *won't* send a Connection: close header at this point + self.close_connection = 1 + + if (request_input.chunked_input or + request_input.position < (request_input.content_length or 0)): + # Read and discard body if connection is going to be reused + if self.close_connection == 0: + try: + request_input.discard() + except ChunkReadError as e: + self.close_connection = 1 + self.server.log.error(( + 'chunked encoding error while discarding request body.' + + ' client={0} request="{1}" error="{2}"').format( + self.get_client_address()[0], self.requestline, e, + )) + except IOError as e: + self.close_connection = 1 + self.server.log.error(( + 'I/O error while discarding request body.' + + ' client={0} request="{1}" error="{2}"').format( + self.get_client_address()[0], self.requestline, e, + )) + finish = time.time() + + for hook, args, kwargs in self.environ['eventlet.posthooks']: + hook(self.environ, *args, **kwargs) + + if self.server.log_output: + client_host, client_port = self.get_client_address() + + self.server.log.info(self.server.log_format % { + 'client_ip': client_host, + 'client_port': client_port, + 'date_time': self.log_date_time_string(), + 'request_line': self.requestline, + 'status_code': status_code[0], + 'body_length': length[0], + 'wall_seconds': finish - start, + }) + + def get_client_address(self): + host, port = addr_to_host_port(self.client_address) + + if self.server.log_x_forwarded_for: + forward = self.headers.get('X-Forwarded-For', '').replace(' ', '') + if forward: + host = forward + ',' + host + return (host, port) + + def get_environ(self): + env = self.server.get_environ() + env['REQUEST_METHOD'] = self.command + env['SCRIPT_NAME'] = '' + + pq = self.path.split('?', 1) + env['RAW_PATH_INFO'] = pq[0] + if six.PY2: + env['PATH_INFO'] = urllib.parse.unquote(pq[0]) + else: + env['PATH_INFO'] = urllib.parse.unquote(pq[0], encoding='latin1') + if len(pq) > 1: + env['QUERY_STRING'] = pq[1] + + ct = self.headers.get('content-type') + if ct is None: + try: + ct = self.headers.type + except AttributeError: + ct = self.headers.get_content_type() + env['CONTENT_TYPE'] = ct + + length = self.headers.get('content-length') + if length: + env['CONTENT_LENGTH'] = length + env['SERVER_PROTOCOL'] = 'HTTP/1.0' + + sockname = self.request.getsockname() + server_addr = addr_to_host_port(sockname) + env['SERVER_NAME'] = server_addr[0] + env['SERVER_PORT'] = str(server_addr[1]) + client_addr = addr_to_host_port(self.client_address) + env['REMOTE_ADDR'] = client_addr[0] + env['REMOTE_PORT'] = str(client_addr[1]) + env['GATEWAY_INTERFACE'] = 'CGI/1.1' + + try: + headers = self.headers.headers + except AttributeError: + headers = self.headers._headers + else: + headers = [h.split(':', 1) for h in headers] + + env['headers_raw'] = headers_raw = tuple((k, v.strip(' \t\n\r')) for k, v in headers) + for k, v in headers_raw: + k = k.replace('-', '_').upper() + if k in ('CONTENT_TYPE', 'CONTENT_LENGTH'): + # These do not get the HTTP_ prefix and were handled above + continue + envk = 'HTTP_' + k + if envk in env: + env[envk] += ',' + v + else: + env[envk] = v + + if env.get('HTTP_EXPECT', '').lower() == '100-continue': + wfile = self.wfile + wfile_line = b'HTTP/1.1 100 Continue\r\n' + else: + wfile = None + wfile_line = None + chunked = env.get('HTTP_TRANSFER_ENCODING', '').lower() == 'chunked' + env['wsgi.input'] = env['eventlet.input'] = Input( + self.rfile, length, self.connection, wfile=wfile, wfile_line=wfile_line, + chunked_input=chunked) + env['eventlet.posthooks'] = [] + + return env + + def finish(self): + try: + BaseHTTPServer.BaseHTTPRequestHandler.finish(self) + except socket.error as e: + # Broken pipe, connection reset by peer + if support.get_errno(e) not in BROKEN_SOCK: + raise + greenio.shutdown_safe(self.connection) + self.connection.close() + + def handle_expect_100(self): + return True + + +class Server(BaseHTTPServer.HTTPServer): + + def __init__(self, + socket, + address, + app, + log=None, + environ=None, + max_http_version=None, + protocol=HttpProtocol, + minimum_chunk_size=None, + log_x_forwarded_for=True, + keepalive=True, + log_output=True, + log_format=DEFAULT_LOG_FORMAT, + url_length_limit=MAX_REQUEST_LINE, + debug=True, + socket_timeout=None, + capitalize_response_headers=True): + + self.outstanding_requests = 0 + self.socket = socket + self.address = address + self.log = LoggerNull() + if log_output: + self.log = get_logger(log, debug) + self.app = app + self.keepalive = keepalive + self.environ = environ + self.max_http_version = max_http_version + self.protocol = protocol + self.pid = os.getpid() + self.minimum_chunk_size = minimum_chunk_size + self.log_x_forwarded_for = log_x_forwarded_for + self.log_output = log_output + self.log_format = log_format + self.url_length_limit = url_length_limit + self.debug = debug + self.socket_timeout = socket_timeout + self.capitalize_response_headers = capitalize_response_headers + + if not self.capitalize_response_headers: + warnings.warn("""capitalize_response_headers is disabled. + Please, make sure you know what you are doing. + HTTP headers names are case-insensitive per RFC standard. + Most likely, you need to fix HTTP parsing in your client software.""", + DeprecationWarning, stacklevel=3) + + def get_environ(self): + d = { + 'wsgi.errors': sys.stderr, + 'wsgi.version': (1, 0), + 'wsgi.multithread': True, + 'wsgi.multiprocess': False, + 'wsgi.run_once': False, + 'wsgi.url_scheme': 'http', + } + # detect secure socket + if hasattr(self.socket, 'do_handshake'): + d['wsgi.url_scheme'] = 'https' + d['HTTPS'] = 'on' + if self.environ is not None: + d.update(self.environ) + return d + + def process_request(self, conn_state): + # The actual request handling takes place in __init__, so we need to + # set minimum_chunk_size before __init__ executes and we don't want to modify + # class variable + proto = new(self.protocol) + if self.minimum_chunk_size is not None: + proto.minimum_chunk_size = self.minimum_chunk_size + proto.capitalize_response_headers = self.capitalize_response_headers + try: + proto.__init__(conn_state, self) + except socket.timeout: + # Expected exceptions are not exceptional + conn_state[1].close() + # similar to logging "accepted" in server() + self.log.debug('({0}) timed out {1!r}'.format(self.pid, conn_state[0])) + + def log_message(self, message): + raise AttributeError('''\ +eventlet.wsgi.server.log_message was deprecated and deleted. +Please use server.log.info instead.''') + + +try: + new = types.InstanceType +except AttributeError: + new = lambda cls: cls.__new__(cls) + + +try: + import ssl + ACCEPT_EXCEPTIONS = (socket.error, ssl.SSLError) + ACCEPT_ERRNO = set((errno.EPIPE, errno.EBADF, errno.ECONNRESET, + ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_SSL)) +except ImportError: + ACCEPT_EXCEPTIONS = (socket.error,) + ACCEPT_ERRNO = set((errno.EPIPE, errno.EBADF, errno.ECONNRESET)) + + +def socket_repr(sock): + scheme = 'http' + if hasattr(sock, 'do_handshake'): + scheme = 'https' + + name = sock.getsockname() + if sock.family == socket.AF_INET: + hier_part = '//{0}:{1}'.format(*name) + elif sock.family == socket.AF_INET6: + hier_part = '//[{0}]:{1}'.format(*name[:2]) + elif sock.family == socket.AF_UNIX: + hier_part = name + else: + hier_part = repr(name) + + return scheme + ':' + hier_part + + +def server(sock, site, + log=None, + environ=None, + max_size=None, + max_http_version=DEFAULT_MAX_HTTP_VERSION, + protocol=HttpProtocol, + server_event=None, + minimum_chunk_size=None, + log_x_forwarded_for=True, + custom_pool=None, + keepalive=True, + log_output=True, + log_format=DEFAULT_LOG_FORMAT, + url_length_limit=MAX_REQUEST_LINE, + debug=True, + socket_timeout=None, + capitalize_response_headers=True): + """Start up a WSGI server handling requests from the supplied server + socket. This function loops forever. The *sock* object will be + closed after server exits, but the underlying file descriptor will + remain open, so if you have a dup() of *sock*, it will remain usable. + + .. warning:: + + At the moment :func:`server` will always wait for active connections to finish before + exiting, even if there's an exception raised inside it + (*all* exceptions are handled the same way, including :class:`greenlet.GreenletExit` + and those inheriting from `BaseException`). + + While this may not be an issue normally, when it comes to long running HTTP connections + (like :mod:`eventlet.websocket`) it will become problematic and calling + :meth:`~eventlet.greenthread.GreenThread.wait` on a thread that runs the server may hang, + even after using :meth:`~eventlet.greenthread.GreenThread.kill`, as long + as there are active connections. + + :param sock: Server socket, must be already bound to a port and listening. + :param site: WSGI application function. + :param log: logging.Logger instance or file-like object that logs should be written to. + If a Logger instance is supplied, messages are sent to the INFO log level. + If not specified, sys.stderr is used. + :param environ: Additional parameters that go into the environ dictionary of every request. + :param max_size: Maximum number of client connections opened at any time by this server. + Default is 1024. + :param max_http_version: Set to "HTTP/1.0" to make the server pretend it only supports HTTP 1.0. + This can help with applications or clients that don't behave properly using HTTP 1.1. + :param protocol: Protocol class. Deprecated. + :param server_event: Used to collect the Server object. Deprecated. + :param minimum_chunk_size: Minimum size in bytes for http chunks. This can be used to improve + performance of applications which yield many small strings, though + using it technically violates the WSGI spec. This can be overridden + on a per request basis by setting environ['eventlet.minimum_write_chunk_size']. + :param log_x_forwarded_for: If True (the default), logs the contents of the x-forwarded-for + header in addition to the actual client ip address in the 'client_ip' field of the + log line. + :param custom_pool: A custom GreenPool instance which is used to spawn client green threads. + If this is supplied, max_size is ignored. + :param keepalive: If set to False, disables keepalives on the server; all connections will be + closed after serving one request. + :param log_output: A Boolean indicating if the server will log data or not. + :param log_format: A python format string that is used as the template to generate log lines. + The following values can be formatted into it: client_ip, date_time, request_line, + status_code, body_length, wall_seconds. The default is a good example of how to + use it. + :param url_length_limit: A maximum allowed length of the request url. If exceeded, 414 error + is returned. + :param debug: True if the server should send exception tracebacks to the clients on 500 errors. + If False, the server will respond with empty bodies. + :param socket_timeout: Timeout for client connections' socket operations. Default None means + wait forever. + :param capitalize_response_headers: Normalize response headers' names to Foo-Bar. + Default is True. + """ + serv = Server( + sock, sock.getsockname(), + site, log, + environ=environ, + max_http_version=max_http_version, + protocol=protocol, + minimum_chunk_size=minimum_chunk_size, + log_x_forwarded_for=log_x_forwarded_for, + keepalive=keepalive, + log_output=log_output, + log_format=log_format, + url_length_limit=url_length_limit, + debug=debug, + socket_timeout=socket_timeout, + capitalize_response_headers=capitalize_response_headers, + ) + if server_event is not None: + warnings.warn( + 'eventlet.wsgi.Server() server_event kwarg is deprecated and will be removed soon', + DeprecationWarning, stacklevel=2) + server_event.send(serv) + if max_size is None: + max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS + if custom_pool is not None: + pool = custom_pool + else: + pool = eventlet.GreenPool(max_size) + + if not (hasattr(pool, 'spawn') and hasattr(pool, 'waitall')): + raise AttributeError('''\ +eventlet.wsgi.Server pool must provide methods: `spawn`, `waitall`. +If unsure, use eventlet.GreenPool.''') + + # [addr, socket, state] + connections = {} + + def _clean_connection(_, conn): + connections.pop(conn[0], None) + conn[2] = STATE_CLOSE + greenio.shutdown_safe(conn[1]) + conn[1].close() + + try: + serv.log.info('({0}) wsgi starting up on {1}'.format(serv.pid, socket_repr(sock))) + while is_accepting: + try: + client_socket, client_addr = sock.accept() + client_socket.settimeout(serv.socket_timeout) + serv.log.debug('({0}) accepted {1!r}'.format(serv.pid, client_addr)) + connections[client_addr] = connection = [client_addr, client_socket, STATE_IDLE] + (pool.spawn(serv.process_request, connection) + .link(_clean_connection, connection)) + except ACCEPT_EXCEPTIONS as e: + if support.get_errno(e) not in ACCEPT_ERRNO: + raise + except (KeyboardInterrupt, SystemExit): + serv.log.info('wsgi exiting') + break + finally: + for cs in six.itervalues(connections): + prev_state = cs[2] + cs[2] = STATE_CLOSE + if prev_state == STATE_IDLE: + greenio.shutdown_safe(cs[1]) + pool.waitall() + serv.log.info('({0}) wsgi exited, is_accepting={1}'.format(serv.pid, is_accepting)) + try: + # NOTE: It's not clear whether we want this to leave the + # socket open or close it. Use cases like Spawning want + # the underlying fd to remain open, but if we're going + # that far we might as well not bother closing sock at + # all. + sock.close() + except socket.error as e: + if support.get_errno(e) not in BROKEN_SOCK: + traceback.print_exc() diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/__init__.py b/venv/lib/python3.12/site-packages/eventlet/zipkin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81edc809993ead72e559adbb7abd1872981cb6ef GIT binary patch literal 177 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/greenthread.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/greenthread.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ff714a2062cf8f39f06c94558ad4dcc3ad657d9 GIT binary patch literal 1623 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/log.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/log.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b0bc0baa7d71a5053f92c749d848754c4534cad GIT binary patch literal 977 zcmcIi&x_MQ6n>LV({8n`T5J(S7ev_F+V}%JiB}J5!Hb7pLYmCB>844TObd}76c)Vd zsrxT1{V%-g!JI769er`)~c{1mL^e zq@pCo@&y`Gu)rb?bmXcyWT7=L`)MXc83Ige^WC9a1OCkvf857Gu-q?@{8RRxxxSb`gS(1}L2#2Iy zr%^@MJ#P1G*KqW{;aU#UbuKGJC4VGz!*xAj2-|a`LXU}OEC`Gqt1A%=n6n$HoS7cC z0!eOr`|OF<_l8U}J*OLLX5SDZ)W*ymYmVL3{802g_kQc{JuR>WyX_n19_9)(h9SaX zLVIcZd$y}NUa#ecQCas5VfLAo@Z#sO$QEjF1K|}M)7k+&*!@Cp9@E@{R_C<(cD$(V z%xgQJYqySQV~Js4N=QZ0l)WtlyFJH znqh9_t~g#szdZZRQTv-?eJjosmD49{t)49YFn{EaYs%+}yXG?OM$NPQ!=Y!59QKIU y@Kk=wpowsjA%uK|(hskp+sw?S zaurY`tyDMw$8zL~@?Z1{mxw@-l~zccxCN!Bp7_4mXVMBXyPo;k^Z7U5+dofCj1d^W z+<(**1w#J9oBqi4!13n*?va25mLM&wW?6`HfOEB+!Fj;>THatAuwAnaE&wjn3I-QN zu~n*-EK-4AFcy?LR;?VAJET@gE6(`ec$i~lc2nyF*ITL)!aClNoJYDTISb%6FX9F+ zEWyolwJ_NVqw69B5#R3ViwojBT*tq|bdP8Y7BhCpjjR`qeaX#o9}U-? z{9cdAB6__4{8^HFy-kptIGfx~PR{7bv&p7bYB@Rf9jHyOC5cFBD~`5!N)x92Ce<<3 zlKGsbaa+>QZho*#8Aydb(_tLBV`CJ?V!|YA(PpglBE9L$aD&r;aUi10pHLPAk}E~$ z172rs0U{Txm)~?3;8(rqj1nZVe2M^{jl6pG_3FZjcZ_!Hpwl|UbCF)7@5Qm;ETUl) z7%ig)4eL}ldDjq0Z-qj1u{j$J7%_Z65ilOPA{yQmPW}hxL>K!zIeV zF-AfS?P9X$xPZ=s5mZ5aU{-YNh5E4&+>f<%hPIrdhA>~EaRixF^{NZ~0~8%HyCVTc zQ;hCwPFZ5zNGzkQ=$p!vSdCf1?+^sczo%bp4~e=TAD7He?jaS+jhYwxz{8lKUI zOE*cgDIT#6!PmRTcNIMkr&ZumAHsBpJib7tF8mPu`2MfwozC^n%Htv_RPL_qu6+IF zf&KEKT}IqlJ+vnu*faa~%z-`kPmz>o4$I@;SH7uyJARahyJO%YK2H6CoyT;lKLLz( zs!iNEHAcD$y3maOksJ22|K>we&%$TE7bv)St}8z}Zw~s`@aa(J6dF7x2%4Lf+ELff z7}a4yn8cBINLKJgtoTnvPNBqe_~`i{R8IwJ4t8FJOI?NO4#7~Y?yh#$MgYWQZr`3e zuwOj1OLuSV-spUU03!Clp5C{of3s)JDQk{>yE5o=;eDW8Y5*&Yqj#OLY_gw2Za0Uz z+58L(ve{FTZm$$fabRjmR9nvCG_dbU`K?wQv;}`hz62ZisZkD0M|sP#9ujK)7ax*W a%-=gI67R|%i*Fz2U$CZ+-zZpfhU#CBoX?#A literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/wsgi.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/zipkin/__pycache__/wsgi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84fbe8238db14dcbd1445311ab548c2540b94d1d GIT binary patch literal 3785 zcmbUjU2hx5ahE)jN0AcsWy_{SSGMHJv}Mtf)w)GpE4C#|4kT5Og**@~4kzAOB1ImV zy^~uyB+3*5${-JMfC9FG0(PEaLw(3YenDTG2QidE;=&dM>OT0*kPHOKQ)lkDM_G1L zGz;A9?9ANm%X-~!*_hx)?pSAX+#f(4h&$PwcGJ$v?6O0FePUUvhl^Wp3wC2=_7S6GF zr{dir@h%scC5ms0D5BcE?Y6n`9>ov5UZoA-5hVcds1gL&m#5M87bvtCrKa#(eJu&I zTh1k;E;DdzX8FCDWodcl`g_je*39B`R4{{c)63Gz^404z(#>fzfU8?eR~K>7jf4DdcN9A3(GK&pvuH)l!=?s8HfDes$-DgT=^ z!@5AEZ#PfjbCLd_{et`qz7L;^t{=2t$A8_=ZFF(o|6y-8|A*_v=b})crO^Yv?&XrL znfv;8v`C9Cg%sV!QO->r=U8jz@~}5agieAGJcw3mK zPC9G{DR>Ir?Pk0beFyA1<%l>4XgBOE9Hc-Cu7bND6nuYiZ8zhoC?449$Bw9j6h!V% zh{smko^b>m#5l{*xBfB8xvg1ogPvopHOIJ>aN;ul)RuxlJH6@o#YQ|l0 z-7s=vn=BdaX9DbGhLT#+knC*~*^tcuh)G!{X<1D*ee0^Ms7yEgYpP-6%m=?QEE^1S z+zhT}vuXYV!xT5W)>KVpvY|Hj1+7F$w{&5qXdy{$&Ga$#{Y_Oj%m8ezXA>KiNnP<| zZX>BpgD%%LlW92J>{vxa;zpSM6|@>9OkX0K$;g^wcIfJBW_08>4N2CttRWl8tj4fj zne9m2Q1j9qIlZZxLQXc;%|QvYPp&03IW4WjR%ulN(z%5vD7dgMG8iFkNcSY->4IM{f=C@!foEJ)2QuiEMf`A4{yuhLMlm zQMEgiL|U*pSy$J*I+)kyee^UCT)^>=$FXyg49&DT#Ge zd7B07Je-Vb>I;x1CDIUlHKDH}^z91A8=>%n$w!wTUas|zSNg}l9If_GRYUKT<{BNx z9}GVleK=YRU#NsHeA!Hzi)_vni#H#;XnW4nK<&& zO~k%^@G40l;Dgtp{bNA6PgD}83W>WE3eDcFxBv=yH}r4cEju3)&9Z6zkGR#0v2)xf zP!x$|F%aa*Q@Z$+n(aJ+BuufWi;V!2Va3q#|G`0q^$apjvQk^R+u1y7j8l-WW2Tl! zJ#w}zJQKqVYu#%P14oxMa6lK5g8A@J=mwNqM)NKM^|r?MJsgS@ZF<{I8*m=(gU(r8 zvv+zqwQ>XOL(@zzgVfAsn&z669W<60+C7}^0?>gz;L~% zuMzD2WZ|QQkAGFB^+2#})IujKp_5++P6DqU>b3l_81uE`VJ?DuQ$H=|KQnWY0xC=k~om&kcAL|uz7u=3!G5vn%Ch8qf-jv;6V0qX_S0zU^t$M*wVxv#DC0i;Q^>%V zi={(3@rS#h1!!Yqz4buoO*POLmOthJIruznHC>q+=rwB?&Qmmn9qQ1KoJVV(FpP_+ z*S;lG%BqqK&w{r#G|45FHL_5i&7QY31$GhS4j5^AtoqC5fIN#V49F6B9y)rjSPh+q zaymM}4!vdgj++nedluBOz4Vc(|} z_Ey~Cg)p@8kPE&OFWbp{g)IRWQ%3&{fR}Dc={KbP-{kn;$&bEuQ5yMHpmb`V0C*{q zU}s6J4~^7@CM!dewV}Dn(A*9g-18o%XI}j*K#!DfzalX04Y=vCy`YD7?sdB9O}ZbX Jbb(se{vYq3{Y?M> literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/__init__.py b/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3add952fb0857f808ee5f3d39aa13728fc89f12 GIT binary patch literal 185 zcmX@j%ge<81ZNK2%K*`jK?FMZ%mNgd&QQsq$>_I|p@<2{`wUX^D?&dbKQ~oBIX@?< zQa?E(v81F@zbrMcOg|?xNxz`7BqKl1SkKT%zc{lbRkt89IXf{uwOBtDsH7w(wM4%v zvmiS&Pd~mSqbM`2L_a=0GcU6wK3=b&@)w6qZhlH>PO4oIE6^fFAT9C%vZ*U7wh`zw3*vMYQ H1{4DTp)^MZ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/__pycache__/constants.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/__pycache__/constants.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2bad75d7ac8b740e38ea5bcc808005a5b1e97054 GIT binary patch literal 422 zcmYk2ze~eF6vyu_ON-s5wRduTQr2;%YnHCV$4y%**;NLkFbp}0Xl@^OS> z?BJL-CZRZ(N%k~Kq!aZXP*RZV% zk7Y7U6B8@Ep@=8oa76@U!F%SCYRZ6zr?O)&64wC7D~0GFswyR5g)+LwM^?`s4Xobb zpXpoOp)v+me`xi~PHBqHEt&xV&3ziAF{54@jdSV+E)eOIB~u!OW18hMNRyq;);1NP zWSg1mO<;RL8H~t?NqQM(lQ5y9pEXO{O?g2~gme=rI$1ui@5iZs9kyr=$ d)G)?hs|fEsq1_kMep_i>8PEUa+Tbl1{073rd#eBd literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/__pycache__/ttypes.cpython-312.pyc b/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/__pycache__/ttypes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e827c0fd5e7e1be25ec6e7fca0132b93c6a6c38 GIT binary patch literal 22647 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�sUAnQuonrD;rkdP7}ONR6$FG~7lz%rF9CT6O5f_UlKF)N`3P(5APfm?v=m?Bw{# zguPnb!J6KC&GwOV#;Lf4%P!@wOuh2JPT}yK7uluUdBK@tpCEPU_TJtce=}h(*J^J4 zaP?*XCI6KdQfwon$~Av?>bogca$DEk`v{KkX?sc1UXrj&F7D)e3j42Z{%G6Gw(Iac zg{O%;^u{mmb2zp(vU!IeO|en+x?$E<&6ihU+iSeh=ZO%~SPp=f*M#F~u5cNVt> z4&HK9%vmAnbKUoC3{RZ4S0(LL3A^-_1gtqxx$&=7e^U2x-HlL+{kCbBKKK&Ys_YPc0v-7A4xzNHd<_S;f<;Sy_kV}Y6N2<_!mglOhSBj; z2A`tf$53!Yd=_kHs(S{g$^jM!I0|8;`+Sr@^0-&3z}>%p!YcZdD*P<&{=xoV!BX7q zQCk!3EWr)LEZ}w-IS>9|A0r?}3JHGVC6<8*?gTnuC$L!E3B->PY*XCq8HawKkGlw8 zA6PG?AY1cv;dDF!A8}E?VzAI-A3ut<|6jn`u^%p;VPs-yHhb92xwE9zMD(Y z+mh7N|4FU=4{FPt#Yz|cyJg*7R|U=7rhwet{vu7UyiFm!d(6^BE|A{swFJop(!0-E n_gd(ccPOO4pF8BF>B3A-!JpK98ABoeoNXzfeqO+|lv)1|Tngrz literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/constants.py b/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/constants.py new file mode 100644 index 0000000..3e04f77 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/constants.py @@ -0,0 +1,14 @@ +# +# Autogenerated by Thrift Compiler (0.8.0) +# +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING +# +# + +from thrift.Thrift import TType, TMessageType, TException +from ttypes import * + +CLIENT_SEND = "cs" +CLIENT_RECV = "cr" +SERVER_SEND = "ss" +SERVER_RECV = "sr" diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/ttypes.py b/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/ttypes.py new file mode 100644 index 0000000..418911f --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/zipkin/_thrift/zipkinCore/ttypes.py @@ -0,0 +1,452 @@ +# +# Autogenerated by Thrift Compiler (0.8.0) +# +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING +# +# + +from thrift.Thrift import TType, TMessageType, TException + +from thrift.transport import TTransport +from thrift.protocol import TBinaryProtocol, TProtocol +try: + from thrift.protocol import fastbinary +except: + fastbinary = None + + +class AnnotationType: + BOOL = 0 + BYTES = 1 + I16 = 2 + I32 = 3 + I64 = 4 + DOUBLE = 5 + STRING = 6 + + _VALUES_TO_NAMES = { + 0: "BOOL", + 1: "BYTES", + 2: "I16", + 3: "I32", + 4: "I64", + 5: "DOUBLE", + 6: "STRING", + } + + _NAMES_TO_VALUES = { + "BOOL": 0, + "BYTES": 1, + "I16": 2, + "I32": 3, + "I64": 4, + "DOUBLE": 5, + "STRING": 6, + } + + +class Endpoint: + """ + Attributes: + - ipv4 + - port + - service_name + """ + + thrift_spec = ( + None, # 0 + (1, TType.I32, 'ipv4', None, None, ), # 1 + (2, TType.I16, 'port', None, None, ), # 2 + (3, TType.STRING, 'service_name', None, None, ), # 3 + ) + + def __init__(self, ipv4=None, port=None, service_name=None,): + self.ipv4 = ipv4 + self.port = port + self.service_name = service_name + + def read(self, iprot): + if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None: + fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec)) + return + iprot.readStructBegin() + while True: + (fname, ftype, fid) = iprot.readFieldBegin() + if ftype == TType.STOP: + break + if fid == 1: + if ftype == TType.I32: + self.ipv4 = iprot.readI32(); + else: + iprot.skip(ftype) + elif fid == 2: + if ftype == TType.I16: + self.port = iprot.readI16(); + else: + iprot.skip(ftype) + elif fid == 3: + if ftype == TType.STRING: + self.service_name = iprot.readString(); + else: + iprot.skip(ftype) + else: + iprot.skip(ftype) + iprot.readFieldEnd() + iprot.readStructEnd() + + def write(self, oprot): + if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None: + oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec))) + return + oprot.writeStructBegin('Endpoint') + if self.ipv4 is not None: + oprot.writeFieldBegin('ipv4', TType.I32, 1) + oprot.writeI32(self.ipv4) + oprot.writeFieldEnd() + if self.port is not None: + oprot.writeFieldBegin('port', TType.I16, 2) + oprot.writeI16(self.port) + oprot.writeFieldEnd() + if self.service_name is not None: + oprot.writeFieldBegin('service_name', TType.STRING, 3) + oprot.writeString(self.service_name) + oprot.writeFieldEnd() + oprot.writeFieldStop() + oprot.writeStructEnd() + + def validate(self): + return + + + def __repr__(self): + L = ['%s=%r' % (key, value) + for key, value in self.__dict__.iteritems()] + return '%s(%s)' % (self.__class__.__name__, ', '.join(L)) + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.__dict__ == other.__dict__ + + def __ne__(self, other): + return not (self == other) + +class Annotation: + """ + Attributes: + - timestamp + - value + - host + """ + + thrift_spec = ( + None, # 0 + (1, TType.I64, 'timestamp', None, None, ), # 1 + (2, TType.STRING, 'value', None, None, ), # 2 + (3, TType.STRUCT, 'host', (Endpoint, Endpoint.thrift_spec), None, ), # 3 + ) + + def __init__(self, timestamp=None, value=None, host=None,): + self.timestamp = timestamp + self.value = value + self.host = host + + def read(self, iprot): + if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None: + fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec)) + return + iprot.readStructBegin() + while True: + (fname, ftype, fid) = iprot.readFieldBegin() + if ftype == TType.STOP: + break + if fid == 1: + if ftype == TType.I64: + self.timestamp = iprot.readI64(); + else: + iprot.skip(ftype) + elif fid == 2: + if ftype == TType.STRING: + self.value = iprot.readString(); + else: + iprot.skip(ftype) + elif fid == 3: + if ftype == TType.STRUCT: + self.host = Endpoint() + self.host.read(iprot) + else: + iprot.skip(ftype) + else: + iprot.skip(ftype) + iprot.readFieldEnd() + iprot.readStructEnd() + + def write(self, oprot): + if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None: + oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec))) + return + oprot.writeStructBegin('Annotation') + if self.timestamp is not None: + oprot.writeFieldBegin('timestamp', TType.I64, 1) + oprot.writeI64(self.timestamp) + oprot.writeFieldEnd() + if self.value is not None: + oprot.writeFieldBegin('value', TType.STRING, 2) + oprot.writeString(self.value) + oprot.writeFieldEnd() + if self.host is not None: + oprot.writeFieldBegin('host', TType.STRUCT, 3) + self.host.write(oprot) + oprot.writeFieldEnd() + oprot.writeFieldStop() + oprot.writeStructEnd() + + def validate(self): + return + + + def __repr__(self): + L = ['%s=%r' % (key, value) + for key, value in self.__dict__.iteritems()] + return '%s(%s)' % (self.__class__.__name__, ', '.join(L)) + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.__dict__ == other.__dict__ + + def __ne__(self, other): + return not (self == other) + +class BinaryAnnotation: + """ + Attributes: + - key + - value + - annotation_type + - host + """ + + thrift_spec = ( + None, # 0 + (1, TType.STRING, 'key', None, None, ), # 1 + (2, TType.STRING, 'value', None, None, ), # 2 + (3, TType.I32, 'annotation_type', None, None, ), # 3 + (4, TType.STRUCT, 'host', (Endpoint, Endpoint.thrift_spec), None, ), # 4 + ) + + def __init__(self, key=None, value=None, annotation_type=None, host=None,): + self.key = key + self.value = value + self.annotation_type = annotation_type + self.host = host + + def read(self, iprot): + if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None: + fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec)) + return + iprot.readStructBegin() + while True: + (fname, ftype, fid) = iprot.readFieldBegin() + if ftype == TType.STOP: + break + if fid == 1: + if ftype == TType.STRING: + self.key = iprot.readString(); + else: + iprot.skip(ftype) + elif fid == 2: + if ftype == TType.STRING: + self.value = iprot.readString(); + else: + iprot.skip(ftype) + elif fid == 3: + if ftype == TType.I32: + self.annotation_type = iprot.readI32(); + else: + iprot.skip(ftype) + elif fid == 4: + if ftype == TType.STRUCT: + self.host = Endpoint() + self.host.read(iprot) + else: + iprot.skip(ftype) + else: + iprot.skip(ftype) + iprot.readFieldEnd() + iprot.readStructEnd() + + def write(self, oprot): + if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None: + oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec))) + return + oprot.writeStructBegin('BinaryAnnotation') + if self.key is not None: + oprot.writeFieldBegin('key', TType.STRING, 1) + oprot.writeString(self.key) + oprot.writeFieldEnd() + if self.value is not None: + oprot.writeFieldBegin('value', TType.STRING, 2) + oprot.writeString(self.value) + oprot.writeFieldEnd() + if self.annotation_type is not None: + oprot.writeFieldBegin('annotation_type', TType.I32, 3) + oprot.writeI32(self.annotation_type) + oprot.writeFieldEnd() + if self.host is not None: + oprot.writeFieldBegin('host', TType.STRUCT, 4) + self.host.write(oprot) + oprot.writeFieldEnd() + oprot.writeFieldStop() + oprot.writeStructEnd() + + def validate(self): + return + + + def __repr__(self): + L = ['%s=%r' % (key, value) + for key, value in self.__dict__.iteritems()] + return '%s(%s)' % (self.__class__.__name__, ', '.join(L)) + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.__dict__ == other.__dict__ + + def __ne__(self, other): + return not (self == other) + +class Span: + """ + Attributes: + - trace_id + - name + - id + - parent_id + - annotations + - binary_annotations + """ + + thrift_spec = ( + None, # 0 + (1, TType.I64, 'trace_id', None, None, ), # 1 + None, # 2 + (3, TType.STRING, 'name', None, None, ), # 3 + (4, TType.I64, 'id', None, None, ), # 4 + (5, TType.I64, 'parent_id', None, None, ), # 5 + (6, TType.LIST, 'annotations', (TType.STRUCT,(Annotation, Annotation.thrift_spec)), None, ), # 6 + None, # 7 + (8, TType.LIST, 'binary_annotations', (TType.STRUCT,(BinaryAnnotation, BinaryAnnotation.thrift_spec)), None, ), # 8 + ) + + def __init__(self, trace_id=None, name=None, id=None, parent_id=None, annotations=None, binary_annotations=None,): + self.trace_id = trace_id + self.name = name + self.id = id + self.parent_id = parent_id + self.annotations = annotations + self.binary_annotations = binary_annotations + + def read(self, iprot): + if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None: + fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec)) + return + iprot.readStructBegin() + while True: + (fname, ftype, fid) = iprot.readFieldBegin() + if ftype == TType.STOP: + break + if fid == 1: + if ftype == TType.I64: + self.trace_id = iprot.readI64(); + else: + iprot.skip(ftype) + elif fid == 3: + if ftype == TType.STRING: + self.name = iprot.readString(); + else: + iprot.skip(ftype) + elif fid == 4: + if ftype == TType.I64: + self.id = iprot.readI64(); + else: + iprot.skip(ftype) + elif fid == 5: + if ftype == TType.I64: + self.parent_id = iprot.readI64(); + else: + iprot.skip(ftype) + elif fid == 6: + if ftype == TType.LIST: + self.annotations = [] + (_etype3, _size0) = iprot.readListBegin() + for _i4 in xrange(_size0): + _elem5 = Annotation() + _elem5.read(iprot) + self.annotations.append(_elem5) + iprot.readListEnd() + else: + iprot.skip(ftype) + elif fid == 8: + if ftype == TType.LIST: + self.binary_annotations = [] + (_etype9, _size6) = iprot.readListBegin() + for _i10 in xrange(_size6): + _elem11 = BinaryAnnotation() + _elem11.read(iprot) + self.binary_annotations.append(_elem11) + iprot.readListEnd() + else: + iprot.skip(ftype) + else: + iprot.skip(ftype) + iprot.readFieldEnd() + iprot.readStructEnd() + + def write(self, oprot): + if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None: + oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec))) + return + oprot.writeStructBegin('Span') + if self.trace_id is not None: + oprot.writeFieldBegin('trace_id', TType.I64, 1) + oprot.writeI64(self.trace_id) + oprot.writeFieldEnd() + if self.name is not None: + oprot.writeFieldBegin('name', TType.STRING, 3) + oprot.writeString(self.name) + oprot.writeFieldEnd() + if self.id is not None: + oprot.writeFieldBegin('id', TType.I64, 4) + oprot.writeI64(self.id) + oprot.writeFieldEnd() + if self.parent_id is not None: + oprot.writeFieldBegin('parent_id', TType.I64, 5) + oprot.writeI64(self.parent_id) + oprot.writeFieldEnd() + if self.annotations is not None: + oprot.writeFieldBegin('annotations', TType.LIST, 6) + oprot.writeListBegin(TType.STRUCT, len(self.annotations)) + for iter12 in self.annotations: + iter12.write(oprot) + oprot.writeListEnd() + oprot.writeFieldEnd() + if self.binary_annotations is not None: + oprot.writeFieldBegin('binary_annotations', TType.LIST, 8) + oprot.writeListBegin(TType.STRUCT, len(self.binary_annotations)) + for iter13 in self.binary_annotations: + iter13.write(oprot) + oprot.writeListEnd() + oprot.writeFieldEnd() + oprot.writeFieldStop() + oprot.writeStructEnd() + + def validate(self): + return + + + def __repr__(self): + L = ['%s=%r' % (key, value) + for key, value in self.__dict__.iteritems()] + return '%s(%s)' % (self.__class__.__name__, ', '.join(L)) + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.__dict__ == other.__dict__ + + def __ne__(self, other): + return not (self == other) diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/api.py b/venv/lib/python3.12/site-packages/eventlet/zipkin/api.py new file mode 100644 index 0000000..cd03ec0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/zipkin/api.py @@ -0,0 +1,186 @@ +import os +import sys +import time +import struct +import socket +import random + +from eventlet.green import threading +from eventlet.zipkin._thrift.zipkinCore import ttypes +from eventlet.zipkin._thrift.zipkinCore.constants import SERVER_SEND + + +client = None +_tls = threading.local() # thread local storage + + +def put_annotation(msg, endpoint=None): + """ This is annotation API. + You can add your own annotation from in your code. + Annotation is recorded with timestamp automatically. + e.g.) put_annotation('cache hit for %s' % request) + + :param msg: String message + :param endpoint: host info + """ + if is_sample(): + a = ZipkinDataBuilder.build_annotation(msg, endpoint) + trace_data = get_trace_data() + trace_data.add_annotation(a) + + +def put_key_value(key, value, endpoint=None): + """ This is binary annotation API. + You can add your own key-value extra information from in your code. + Key-value doesn't have a time component. + e.g.) put_key_value('http.uri', '/hoge/index.html') + + :param key: String + :param value: String + :param endpoint: host info + """ + if is_sample(): + b = ZipkinDataBuilder.build_binary_annotation(key, value, endpoint) + trace_data = get_trace_data() + trace_data.add_binary_annotation(b) + + +def is_tracing(): + """ Return whether the current thread is tracking or not """ + return hasattr(_tls, 'trace_data') + + +def is_sample(): + """ Return whether it should record trace information + for the request or not + """ + return is_tracing() and _tls.trace_data.sampled + + +def get_trace_data(): + if is_tracing(): + return _tls.trace_data + + +def set_trace_data(trace_data): + _tls.trace_data = trace_data + + +def init_trace_data(): + if is_tracing(): + del _tls.trace_data + + +def _uniq_id(): + """ + Create a random 64-bit signed integer appropriate + for use as trace and span IDs. + XXX: By experimentation zipkin has trouble recording traces with ids + larger than (2 ** 56) - 1 + """ + return random.randint(0, (2 ** 56) - 1) + + +def generate_trace_id(): + return _uniq_id() + + +def generate_span_id(): + return _uniq_id() + + +class TraceData(object): + + END_ANNOTATION = SERVER_SEND + + def __init__(self, name, trace_id, span_id, parent_id, sampled, endpoint): + """ + :param name: RPC name (String) + :param trace_id: int + :param span_id: int + :param parent_id: int or None + :param sampled: lets the downstream servers know + if I should record trace data for the request (bool) + :param endpoint: zipkin._thrift.zipkinCore.ttypes.EndPoint + """ + self.name = name + self.trace_id = trace_id + self.span_id = span_id + self.parent_id = parent_id + self.sampled = sampled + self.endpoint = endpoint + self.annotations = [] + self.bannotations = [] + self._done = False + + def add_annotation(self, annotation): + if annotation.host is None: + annotation.host = self.endpoint + if not self._done: + self.annotations.append(annotation) + if annotation.value == self.END_ANNOTATION: + self.flush() + + def add_binary_annotation(self, bannotation): + if bannotation.host is None: + bannotation.host = self.endpoint + if not self._done: + self.bannotations.append(bannotation) + + def flush(self): + span = ZipkinDataBuilder.build_span(name=self.name, + trace_id=self.trace_id, + span_id=self.span_id, + parent_id=self.parent_id, + annotations=self.annotations, + bannotations=self.bannotations) + client.send_to_collector(span) + self.annotations = [] + self.bannotations = [] + self._done = True + + +class ZipkinDataBuilder: + @staticmethod + def build_span(name, trace_id, span_id, parent_id, + annotations, bannotations): + return ttypes.Span( + name=name, + trace_id=trace_id, + id=span_id, + parent_id=parent_id, + annotations=annotations, + binary_annotations=bannotations + ) + + @staticmethod + def build_annotation(value, endpoint=None): + if isinstance(value, unicode): + value = value.encode('utf-8') + return ttypes.Annotation(time.time() * 1000 * 1000, + str(value), endpoint) + + @staticmethod + def build_binary_annotation(key, value, endpoint=None): + annotation_type = ttypes.AnnotationType.STRING + return ttypes.BinaryAnnotation(key, value, annotation_type, endpoint) + + @staticmethod + def build_endpoint(ipv4=None, port=None, service_name=None): + if ipv4 is not None: + ipv4 = ZipkinDataBuilder._ipv4_to_int(ipv4) + if service_name is None: + service_name = ZipkinDataBuilder._get_script_name() + return ttypes.Endpoint( + ipv4=ipv4, + port=port, + service_name=service_name + ) + + @staticmethod + def _ipv4_to_int(ipv4): + return struct.unpack('!i', socket.inet_aton(ipv4))[0] + + @staticmethod + def _get_script_name(): + return os.path.basename(sys.argv[0]) diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/client.py b/venv/lib/python3.12/site-packages/eventlet/zipkin/client.py new file mode 100644 index 0000000..3e629be --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/zipkin/client.py @@ -0,0 +1,56 @@ +import base64 +import warnings + +from scribe import scribe +from thrift.transport import TTransport, TSocket +from thrift.protocol import TBinaryProtocol + +from eventlet import GreenPile + + +CATEGORY = 'zipkin' + + +class ZipkinClient(object): + + def __init__(self, host='127.0.0.1', port=9410): + """ + :param host: zipkin collector IP address (default '127.0.0.1') + :param port: zipkin collector port (default 9410) + """ + self.host = host + self.port = port + self.pile = GreenPile(1) + self._connect() + + def _connect(self): + socket = TSocket.TSocket(self.host, self.port) + self.transport = TTransport.TFramedTransport(socket) + protocol = TBinaryProtocol.TBinaryProtocol(self.transport, + False, False) + self.scribe_client = scribe.Client(protocol) + try: + self.transport.open() + except TTransport.TTransportException as e: + warnings.warn(e.message) + + def _build_message(self, thrift_obj): + trans = TTransport.TMemoryBuffer() + protocol = TBinaryProtocol.TBinaryProtocolAccelerated(trans=trans) + thrift_obj.write(protocol) + return base64.b64encode(trans.getvalue()) + + def send_to_collector(self, span): + self.pile.spawn(self._send, span) + + def _send(self, span): + log_entry = scribe.LogEntry(CATEGORY, self._build_message(span)) + try: + self.scribe_client.Log([log_entry]) + except Exception as e: + msg = 'ZipkinClient send error %s' % str(e) + warnings.warn(msg) + self._connect() + + def close(self): + self.transport.close() diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/greenthread.py b/venv/lib/python3.12/site-packages/eventlet/zipkin/greenthread.py new file mode 100644 index 0000000..37e12d6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/zipkin/greenthread.py @@ -0,0 +1,33 @@ +from eventlet import greenthread + +from eventlet.zipkin import api + + +__original_init__ = greenthread.GreenThread.__init__ +__original_main__ = greenthread.GreenThread.main + + +def _patched__init(self, parent): + # parent thread saves current TraceData from tls to self + if api.is_tracing(): + self.trace_data = api.get_trace_data() + + __original_init__(self, parent) + + +def _patched_main(self, function, args, kwargs): + # child thread inherits TraceData + if hasattr(self, 'trace_data'): + api.set_trace_data(self.trace_data) + + __original_main__(self, function, args, kwargs) + + +def patch(): + greenthread.GreenThread.__init__ = _patched__init + greenthread.GreenThread.main = _patched_main + + +def unpatch(): + greenthread.GreenThread.__init__ = __original_init__ + greenthread.GreenThread.main = __original_main__ diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/http.py b/venv/lib/python3.12/site-packages/eventlet/zipkin/http.py new file mode 100644 index 0000000..3ab5925 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/zipkin/http.py @@ -0,0 +1,61 @@ +import warnings + +import six +from eventlet.green import httplib +from eventlet.zipkin import api + + +# see https://twitter.github.io/zipkin/Instrumenting.html +HDR_TRACE_ID = 'X-B3-TraceId' +HDR_SPAN_ID = 'X-B3-SpanId' +HDR_PARENT_SPAN_ID = 'X-B3-ParentSpanId' +HDR_SAMPLED = 'X-B3-Sampled' + + +if six.PY2: + __org_endheaders__ = httplib.HTTPConnection.endheaders + __org_begin__ = httplib.HTTPResponse.begin + + def _patched_endheaders(self): + if api.is_tracing(): + trace_data = api.get_trace_data() + new_span_id = api.generate_span_id() + self.putheader(HDR_TRACE_ID, hex_str(trace_data.trace_id)) + self.putheader(HDR_SPAN_ID, hex_str(new_span_id)) + self.putheader(HDR_PARENT_SPAN_ID, hex_str(trace_data.span_id)) + self.putheader(HDR_SAMPLED, int(trace_data.sampled)) + api.put_annotation('Client Send') + + __org_endheaders__(self) + + def _patched_begin(self): + __org_begin__(self) + + if api.is_tracing(): + api.put_annotation('Client Recv (%s)' % self.status) + + +def patch(): + if six.PY2: + httplib.HTTPConnection.endheaders = _patched_endheaders + httplib.HTTPResponse.begin = _patched_begin + if six.PY3: + warnings.warn("Since current Python thrift release \ + doesn't support Python 3, eventlet.zipkin.http \ + doesn't also support Python 3 (http.client)") + + +def unpatch(): + if six.PY2: + httplib.HTTPConnection.endheaders = __org_endheaders__ + httplib.HTTPResponse.begin = __org_begin__ + if six.PY3: + pass + + +def hex_str(n): + """ + Thrift uses a binary representation of trace and span ids + HTTP headers use a hexadecimal representation of the same + """ + return '%0.16x' % (n,) diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/log.py b/venv/lib/python3.12/site-packages/eventlet/zipkin/log.py new file mode 100644 index 0000000..b7f9d32 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/zipkin/log.py @@ -0,0 +1,19 @@ +import logging + +from eventlet.zipkin import api + + +__original_handle__ = logging.Logger.handle + + +def _patched_handle(self, record): + __original_handle__(self, record) + api.put_annotation(record.getMessage()) + + +def patch(): + logging.Logger.handle = _patched_handle + + +def unpatch(): + logging.Logger.handle = __original_handle__ diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/patcher.py b/venv/lib/python3.12/site-packages/eventlet/zipkin/patcher.py new file mode 100644 index 0000000..8e7d8ad --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/zipkin/patcher.py @@ -0,0 +1,41 @@ +from eventlet.zipkin import http +from eventlet.zipkin import wsgi +from eventlet.zipkin import greenthread +from eventlet.zipkin import log +from eventlet.zipkin import api +from eventlet.zipkin.client import ZipkinClient + + +def enable_trace_patch(host='127.0.0.1', port=9410, + trace_app_log=False, sampling_rate=1.0): + """ Apply monkey patch to trace your WSGI application. + + :param host: Scribe daemon IP address (default: '127.0.0.1') + :param port: Scribe daemon port (default: 9410) + :param trace_app_log: A Boolean indicating if the tracer will trace + application log together or not. This facility assume that + your application uses python standard logging library. + (default: False) + :param sampling_rate: A Float value (0.0~1.0) that indicates + the tracing frequency. If you specify 1.0, all request + are traced (and sent to Zipkin collecotr). + If you specify 0.1, only 1/10 requests are traced. (default: 1.0) + """ + api.client = ZipkinClient(host, port) + + # monkey patch for adding tracing facility + wsgi.patch(sampling_rate) + http.patch() + greenthread.patch() + + # monkey patch for capturing application log + if trace_app_log: + log.patch() + + +def disable_trace_patch(): + http.unpatch() + wsgi.unpatch() + greenthread.unpatch() + log.unpatch() + api.client.close() diff --git a/venv/lib/python3.12/site-packages/eventlet/zipkin/wsgi.py b/venv/lib/python3.12/site-packages/eventlet/zipkin/wsgi.py new file mode 100644 index 0000000..3d52911 --- /dev/null +++ b/venv/lib/python3.12/site-packages/eventlet/zipkin/wsgi.py @@ -0,0 +1,78 @@ +import random + +from eventlet import wsgi +from eventlet.zipkin import api +from eventlet.zipkin._thrift.zipkinCore.constants import \ + SERVER_RECV, SERVER_SEND +from eventlet.zipkin.http import \ + HDR_TRACE_ID, HDR_SPAN_ID, HDR_PARENT_SPAN_ID, HDR_SAMPLED + + +_sampler = None +__original_handle_one_response__ = wsgi.HttpProtocol.handle_one_response + + +def _patched_handle_one_response(self): + api.init_trace_data() + trace_id = int_or_none(self.headers.getheader(HDR_TRACE_ID)) + span_id = int_or_none(self.headers.getheader(HDR_SPAN_ID)) + parent_id = int_or_none(self.headers.getheader(HDR_PARENT_SPAN_ID)) + sampled = bool_or_none(self.headers.getheader(HDR_SAMPLED)) + if trace_id is None: # front-end server + trace_id = span_id = api.generate_trace_id() + parent_id = None + sampled = _sampler.sampling() + ip, port = self.request.getsockname()[:2] + ep = api.ZipkinDataBuilder.build_endpoint(ip, port) + trace_data = api.TraceData(name=self.command, + trace_id=trace_id, + span_id=span_id, + parent_id=parent_id, + sampled=sampled, + endpoint=ep) + api.set_trace_data(trace_data) + api.put_annotation(SERVER_RECV) + api.put_key_value('http.uri', self.path) + + __original_handle_one_response__(self) + + if api.is_sample(): + api.put_annotation(SERVER_SEND) + + +class Sampler(object): + def __init__(self, sampling_rate): + self.sampling_rate = sampling_rate + + def sampling(self): + # avoid generating unneeded random numbers + if self.sampling_rate == 1.0: + return True + r = random.random() + if r < self.sampling_rate: + return True + return False + + +def int_or_none(val): + if val is None: + return None + return int(val, 16) + + +def bool_or_none(val): + if val == '1': + return True + if val == '0': + return False + return None + + +def patch(sampling_rate): + global _sampler + _sampler = Sampler(sampling_rate) + wsgi.HttpProtocol.handle_one_response = _patched_handle_one_response + + +def unpatch(): + wsgi.HttpProtocol.handle_one_response = __original_handle_one_response__ diff --git a/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/LICENSE.rst b/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/LICENSE.rst new file mode 100644 index 0000000..9d227a0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/LICENSE.rst @@ -0,0 +1,28 @@ +Copyright 2010 Pallets + +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/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/METADATA b/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/METADATA new file mode 100644 index 0000000..d7c3145 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/METADATA @@ -0,0 +1,116 @@ +Metadata-Version: 2.1 +Name: Flask +Version: 2.3.3 +Summary: A simple framework for building complex web applications. +Maintainer-email: Pallets +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Framework :: Flask +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Requires-Dist: Werkzeug>=2.3.7 +Requires-Dist: Jinja2>=3.1.2 +Requires-Dist: itsdangerous>=2.1.2 +Requires-Dist: click>=8.1.3 +Requires-Dist: blinker>=1.6.2 +Requires-Dist: importlib-metadata>=3.6.0; python_version < '3.10' +Requires-Dist: asgiref>=3.2 ; extra == "async" +Requires-Dist: python-dotenv ; extra == "dotenv" +Project-URL: Changes, https://flask.palletsprojects.com/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://flask.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/ +Project-URL: Source Code, https://github.com/pallets/flask/ +Provides-Extra: async +Provides-Extra: dotenv + +Flask +===== + +Flask is a lightweight `WSGI`_ web application framework. It is designed +to make getting started quick and easy, with the ability to scale up to +complex applications. It began as a simple wrapper around `Werkzeug`_ +and `Jinja`_ and has become one of the most popular Python web +application frameworks. + +Flask offers suggestions, but doesn't enforce any dependencies or +project layout. It is up to the developer to choose the tools and +libraries they want to use. There are many extensions provided by the +community that make adding new functionality easy. + +.. _WSGI: https://wsgi.readthedocs.io/ +.. _Werkzeug: https://werkzeug.palletsprojects.com/ +.. _Jinja: https://jinja.palletsprojects.com/ + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U Flask + +.. _pip: https://pip.pypa.io/en/stable/getting-started/ + + +A Simple Example +---------------- + +.. code-block:: python + + # save this as app.py + from flask import Flask + + app = Flask(__name__) + + @app.route("/") + def hello(): + return "Hello, World!" + +.. code-block:: text + + $ flask run + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) + + +Contributing +------------ + +For guidance on setting up a development environment and how to make a +contribution to Flask, see the `contributing guidelines`_. + +.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst + + +Donate +------ + +The Pallets organization develops and supports Flask and the libraries +it uses. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://flask.palletsprojects.com/ +- Changes: https://flask.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/Flask/ +- Source Code: https://github.com/pallets/flask/ +- Issue Tracker: https://github.com/pallets/flask/issues/ +- Chat: https://discord.gg/pallets + diff --git a/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/RECORD b/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/RECORD new file mode 100644 index 0000000..6d552e6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/RECORD @@ -0,0 +1,53 @@ +../../../bin/flask,sha256=5r679Tr-1HxPytSa0r-bh3tM-MSKtbwJmqkj02NQUqM,227 +flask-2.3.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +flask-2.3.3.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 +flask-2.3.3.dist-info/METADATA,sha256=-BtXVsnPe7lNA3mcFZHJfsVIiVin1A8LUstChm8qiHo,3588 +flask-2.3.3.dist-info/RECORD,, +flask-2.3.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +flask-2.3.3.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 +flask-2.3.3.dist-info/entry_points.txt,sha256=bBP7hTOS5fz9zLtC7sPofBZAlMkEvBxu7KqS6l5lvc4,40 +flask/__init__.py,sha256=xq09XNKP-Y-fdv6BeGH7RlFaY006tUA3o_llGcl-dno,3731 +flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30 +flask/__pycache__/__init__.cpython-312.pyc,, +flask/__pycache__/__main__.cpython-312.pyc,, +flask/__pycache__/app.cpython-312.pyc,, +flask/__pycache__/blueprints.cpython-312.pyc,, +flask/__pycache__/cli.cpython-312.pyc,, +flask/__pycache__/config.cpython-312.pyc,, +flask/__pycache__/ctx.cpython-312.pyc,, +flask/__pycache__/debughelpers.cpython-312.pyc,, +flask/__pycache__/globals.cpython-312.pyc,, +flask/__pycache__/helpers.cpython-312.pyc,, +flask/__pycache__/logging.cpython-312.pyc,, +flask/__pycache__/scaffold.cpython-312.pyc,, +flask/__pycache__/sessions.cpython-312.pyc,, +flask/__pycache__/signals.cpython-312.pyc,, +flask/__pycache__/templating.cpython-312.pyc,, +flask/__pycache__/testing.cpython-312.pyc,, +flask/__pycache__/typing.cpython-312.pyc,, +flask/__pycache__/views.cpython-312.pyc,, +flask/__pycache__/wrappers.cpython-312.pyc,, +flask/app.py,sha256=ht3Qx9U9z0I1qUfLoS7bYhJcubdpk-i54eHq37LDlN8,87620 +flask/blueprints.py,sha256=ZpVrwa8UY-YnVDsX_1K10XQjDwCUp7Qn2hmKln5icEQ,24332 +flask/cli.py,sha256=PDwZCfPagi5GUzb-D6dEN7y20gWiVAg3ejRnxBKNHPA,33821 +flask/config.py,sha256=YZSZ-xpFj1iW1B1Kj1iDhpc5s7pHncloiRLqXhsU7Hs,12856 +flask/ctx.py,sha256=x2kGzUXtPzVyi2YSKrU_PV1AvtxTmh2iRdriJRTSPGM,14841 +flask/debughelpers.py,sha256=BR0xkd-sAyFuFW07D6NfrqNwSZxk1IrkG5n8zem-3sw,5547 +flask/globals.py,sha256=KUzVvSPh8v28kUasVDi_aQKB9hI2jZSYQHqaDU2P414,2945 +flask/helpers.py,sha256=uVhMwhhfwgjBt8b--zIZTjkfBRK28yPpmNhgVzhP444,25106 +flask/json/__init__.py,sha256=pdtpoK2b0b1u7Sxbx3feM7VWhsI20l1yGAvbYWxaxvc,5572 +flask/json/__pycache__/__init__.cpython-312.pyc,, +flask/json/__pycache__/provider.cpython-312.pyc,, +flask/json/__pycache__/tag.cpython-312.pyc,, +flask/json/provider.py,sha256=Os0frb8oGfyWKL-TDxb0Uy-MY6gDhPdJkRaUl5xAOXI,7637 +flask/json/tag.py,sha256=ihb7QWrNEr0YC3KD4TolZbftgSPCuLk7FAvK49huYC0,8871 +flask/logging.py,sha256=lArx2Bq9oTtUJ-DnZL9t88xU2zytzp4UWSM9Bd72NDQ,2327 +flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +flask/scaffold.py,sha256=ALGHLcy2qSbJ7ENd1H8dOnq5VDgH5XSFsOkDelcOKV8,33217 +flask/sessions.py,sha256=rFH2QKXG24dEazkKGxAHqUpAUh_30hDHrddhVYgAcY0,14169 +flask/signals.py,sha256=s1H4yKjf3c5dgVr41V6sJpE9dLJvmTJMYuK0rkqx3sw,1146 +flask/templating.py,sha256=XdP2hMFnZ5FCZOG7HUaLjC2VC-b4uHSWlDjwv_1p3qc,7503 +flask/testing.py,sha256=h7AinggrMgGzKlDN66VfB0JjWW4Z1U_OD6FyjqBNiYM,10017 +flask/typing.py,sha256=4Lj-YTxUoYvPYofC9GKu-1o0Ht8lyjp9z3I336J13_o,3005 +flask/views.py,sha256=V5hOGZLx0Bn99QGcM6mh5x_uM-MypVT0-RysEFU84jc,6789 +flask/wrappers.py,sha256=PhMp3teK3SnEmIdog59cO_DHiZ9Btn0qI1EifrTdwP8,5709 diff --git a/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/REQUESTED b/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/WHEEL b/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/WHEEL new file mode 100644 index 0000000..3b5e64b --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/entry_points.txt b/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/entry_points.txt new file mode 100644 index 0000000..eec6733 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask-2.3.3.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +flask=flask.cli:main + diff --git a/venv/lib/python3.12/site-packages/flask/__init__.py b/venv/lib/python3.12/site-packages/flask/__init__.py new file mode 100644 index 0000000..bdca1b0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/__init__.py @@ -0,0 +1,102 @@ +from . import json as json +from .app import Flask as Flask +from .app import Request as Request +from .app import Response as Response +from .blueprints import Blueprint as Blueprint +from .config import Config as Config +from .ctx import after_this_request as after_this_request +from .ctx import copy_current_request_context as copy_current_request_context +from .ctx import has_app_context as has_app_context +from .ctx import has_request_context as has_request_context +from .globals import current_app as current_app +from .globals import g as g +from .globals import request as request +from .globals import session as session +from .helpers import abort as abort +from .helpers import flash as flash +from .helpers import get_flashed_messages as get_flashed_messages +from .helpers import get_template_attribute as get_template_attribute +from .helpers import make_response as make_response +from .helpers import redirect as redirect +from .helpers import send_file as send_file +from .helpers import send_from_directory as send_from_directory +from .helpers import stream_with_context as stream_with_context +from .helpers import url_for as url_for +from .json import jsonify as jsonify +from .signals import appcontext_popped as appcontext_popped +from .signals import appcontext_pushed as appcontext_pushed +from .signals import appcontext_tearing_down as appcontext_tearing_down +from .signals import before_render_template as before_render_template +from .signals import got_request_exception as got_request_exception +from .signals import message_flashed as message_flashed +from .signals import request_finished as request_finished +from .signals import request_started as request_started +from .signals import request_tearing_down as request_tearing_down +from .signals import template_rendered as template_rendered +from .templating import render_template as render_template +from .templating import render_template_string as render_template_string +from .templating import stream_template as stream_template +from .templating import stream_template_string as stream_template_string + +__version__ = "2.3.3" + + +def __getattr__(name): + if name == "_app_ctx_stack": + import warnings + from .globals import __app_ctx_stack + + warnings.warn( + "'_app_ctx_stack' is deprecated and will be removed in Flask 2.4.", + DeprecationWarning, + stacklevel=2, + ) + return __app_ctx_stack + + if name == "_request_ctx_stack": + import warnings + from .globals import __request_ctx_stack + + warnings.warn( + "'_request_ctx_stack' is deprecated and will be removed in Flask 2.4.", + DeprecationWarning, + stacklevel=2, + ) + return __request_ctx_stack + + if name == "escape": + import warnings + from markupsafe import escape + + warnings.warn( + "'flask.escape' is deprecated and will be removed in Flask 2.4. Import" + " 'markupsafe.escape' instead.", + DeprecationWarning, + stacklevel=2, + ) + return escape + + if name == "Markup": + import warnings + from markupsafe import Markup + + warnings.warn( + "'flask.Markup' is deprecated and will be removed in Flask 2.4. Import" + " 'markupsafe.Markup' instead.", + DeprecationWarning, + stacklevel=2, + ) + return Markup + + if name == "signals_available": + import warnings + + warnings.warn( + "'signals_available' is deprecated and will be removed in Flask 2.4." + " Signals are always available", + DeprecationWarning, + stacklevel=2, + ) + return True + + raise AttributeError(name) diff --git a/venv/lib/python3.12/site-packages/flask/__main__.py b/venv/lib/python3.12/site-packages/flask/__main__.py new file mode 100644 index 0000000..4e28416 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/__main__.py @@ -0,0 +1,3 @@ +from .cli import main + +main() diff --git a/venv/lib/python3.12/site-packages/flask/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f061d0d1611afcfda9a44a0c2b74d9e1b83534a GIT binary patch literal 3223 zcmb`JNpB<77RPUSDi3k&OintJt24CQ-D#Un-UB_R$2=_PMT3NNH$~ZX6Bnt8Tb02P zkq}}5tG8eQAAk*lPw}?zEuv=ADk#(fHoa@9S+PKzbL>h&LWCMoDj)yq+;jhTtaIys z<#Lh1_5c5V-}`iwu^;gvxl%)T_=CdOXUt@#VlzkKih`W7Q%;(viPOLto*~WvXL*)5 zYv&x5tHe1w?-Y1}Shb5ziI<4;z$1KwxBy({W#S_6C?6#**%hbCtHdL~V|z@HKvoc*VZ%tnoGCRp1-^2Jsc(7H<(ZfZMzce3d_D zHa}wgX8&6A`h8eu)>NX*yMgDzfA$sI2(|&#+v2?)5rlyG+amBiHxR&u7wjG3OUuQA z+>4&OX>9?G8=Ik!df2l9T^?7Q?|Ob;@9sz`+%WO$UC#~0KGv`Fj6gSh|CDP2T|hvH?-GM-3O9bGrp+6LkZyTY| zjWCo}XD7r#mK|eTKrfC*otMJ2r0Ak#PS7Lg^r^^D7_bUky6NrVy45-Ygt61E zi3>3t7L-hFc_(u(_PfFl2QyU}&P6ge5L-=Ve$#TTVWXAA7leiksUMSx>r5xcl4gh9 zpkSzfwoA1$Ebm1j7HN!QvJkKXp116j?GLNn|= zqaSF;Exgq%M)^HMx{ywSNZqo%4)j3YfH6lI^he__4R;G9!M6v2@+KA+Po|HSg(fN* zeLguCUX~IvcP`4fh9l%Zp$f@T<&z3Q4hl57etD(c^BmFcdUmJZ?)Kn>>9==pEoMP}f_2KPu@u994Kg!Ipdm^w;1Q{0im+ zc2_MORqA)t`nUP%IKOr;U;R({lk(k>Ti=Z=-kqL#pt3^kXO<}x9~4=&ew1IlhZUCZ zsLNF0vEvH2|ER+9QGN+3e7t{0ou>HJVf=%XQeOLM&_scn$U+k%tXliH{8uU*RhI6k zOVGrh=}-PtocVn6o8sb8X7M4+0K3&(kC?D%$dnJ&V?xP$W zweZa%G0R8kZn!TC7>u99vV>*?&EsgwXy6N($qJe(m`Lpj+ZQsBW9S-3gPBcEqM1Td zLo4HY;h_jyTV6qL0}Y&S zOg6C`w#hE^4EFVKn|#USvsn5xm|yZwJD#~?i)ZEQ@Q4{W_z28{w4x|6n~hmLX3H^K zjae&ZD>3_5%-)FEM$DdySvzKPF>BBbF>pU0vzMu~8MEs#`#0SX02Lt~?pG<`#h5)o bQedG9_@9i~YXf_4bn1iB!`zcfoqG8j+6;Yh literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/__pycache__/__main__.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..931a5931adb51dff1edeb2576d91b2c20c2cc739 GIT binary patch literal 221 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 z z*?JA(H|1dY+j0FQ{4?7m19Re(^A@2CkiJM-gmirlCf9>YaBsBu2oB-;OZca?F4>zx z7$DA?N@LFDf91^N5^R_JF(RRXmPjTxagyFfqWy8qKa};+h;5HO;YjSpQ_A%?p<9<` zz|y0{Kct*4S5$&Z>@*TY#Ro^57%QmGk9X$yFdfNEyF25PdGiwm|f#QT_mgj6!hvS=BCxJgf9M{Y$(XzA+t{gIEwh~Dz=q)8ut)PE` zU4SI*au4L|<_T~o(+HqiJ`VwUK){Sv&v{^8^Cw2nqN;UH5-#8SETjjujnxjsUtJye;^}$h_646K1ig`8csu* zj5HLf`=`P#ylqyKbzH9%9XNazuAFMx0}MD+HHJBmkPWb2lbNA7b{NMUg(RO5^(1zD z6=_RVf00ypqri-?xM%3Oc^pen6Wmozz`a7qLNN1?=zSbNqI@IAmpJx3ij6dpWbwaC z9dB4MQ1z+%jOZG|??>#=Ow*&u9b$)OCZLQ*GFk)wRmhvyFk*lDROeqxv&K7K2LGZA z=-ZY*pnQ)c@t-iuRY>Ml#qWoP8XZGvAU6IJP>z*DLqj*AT!+}9Ab`d)?Gy`QSlD)~r>pwzjck_3vos%)2pIzQ~!eN^+pQD>%Y$ zENUl#uTj22>?n@s#Kvodqc{~Zpi!LManvt}9mVO5(i2H;o8LmL3zQ>9ZhDEOGk+wY z!v(aT5%s{7S(^Zp&xoE6;5@{CM)W`~ts;@lKqURC&k8V+!FT3_~bUt;AniZ#qm5{5j#FP1Z6LT zW{Zo_k1p9oin^8j&=TXGt#~kngjLhRB2&T}S{1JPa z9-AztKZnpRF7$9h%SgIHX;fy{JAdD53*8D&p!wn;4PESH{=62XSF_IlN!$w%`=Lt@ zr0GzLLboFKeRDYa(B(tm9}q_$y7b6Y(EPd6hb~))GY{DN;NKNTwXEk91ub0eeCYCf zofuuX(FxR@aFP#Qc6Ui)FxF|IFw3LlLzitJv_fF$LzjI5!)tXubh$6+p@{v^Crh<>&w?k_zSW+u1PsFvhJS5jES$#E}F0X}NH3=Ue zjykLT!1qOL;yseD4>)$`@*6YLg5)QRm%O|x?TH9(5PuR@x2LHDqX%W);x}V97puh1TTKqi+%O9lH`bqEe zjNjkRxJkMe2eenqyVEUg@ITP{cj}Rg(cN?b*?n@m*hExHs?5?dn%17S=@)f)ONUQ6 zm|O>GzO=m#d=Fx%itab z2KVT1%VU6y^x*E*F1Z`eNuJV!Rqp(vCHn4hBy+1c8Mh{{A$py3BpfOc{N4 zIiAcRWHC8413K8AVYL!o#a%QigwTz@z}8ZZ%G+h_iZ;`#-t5Z>dG zq9=wi9(L3zv;>fBubN$J+kP3cm-GCap1#cP!itB>v1A-9-_zGOt86l^#r9%kNUo;+ zr0YJtB^KH>-|-Ws0j0F3ZSrE z+wFT;_=ATV)3(8O@&?`+_=0HWOOdJfaw?}u-zk06vjz6?=wRnDEQl^7ih57Dd6@dJ zP;<@O!l$|3qBsBT2t$y81bb5FGN-#8nkwo6^?%{EH4w`T8&u4idQ zz50aR>ci@Xuye)|wmNQ|(r1xua#qL5tigMtm{;-jCK9G$C>u6032T=rCgj|>{-y^> z$*8qNeO}@qldX@&IU`Ck*`}SCN6VwDQj=t8Ml?tnBH3r&1vrYfOKBE{1xAsbz}S2V zXOECwbuP^G`xtMlw>7b;ebu|j=GJyH*~ymC*1p!_^m9*v_FXGm!usT-&S1Uol1gg& zl@AlRDLG#ng0ogOCG$+DH&Q9R*FXz)V%3I>{NDR)Zhpv{5JpB z&DPQRARH}@Jn}S!>?*ctf4ms#j^IDpY=3UlOd!*K1EHxo*3vGJ$3{C0lcko4qw4_z zH7K43;O1x%$CTXx^UvsqXNt@V)HmZ^0l=?i@<*-LwgY#FdA7*B%iZyBq-x2vc^pj) z$H+QH(FrBLU^u!1RJ&6dzC%p?NsA+MibLkLBF{OwI+_DwB-s6gH)|tZJDbJ~q>OSO_qn7a#8MiD^0FKnQTPev2c+y)iqe~igXWEe(S^pR^HnIs_U?B_*-Z}h?vF<~7O|rXD_yc! zf9|wmmn@Ag9Hw+>bm3y~^JGAy3oB9XL6Wqr4jJiH zW|L^(BW0?nJUVuKCZzlWRyK|flV}i_^DvdL|6H41_37(twkL+KHO5+Sy zLbyVttiQ*6kKgTBxbPr52XB9UYHe+#9Z>&rhOct=q9vocV@*4ruE)~jh@Fo%cnU*3 zNTlg%kD)w@l+I1R#n0p@cgnUGerB#DFRRt~stSAd$3w#^xg zA|Q4`+Ri9lmG5yNC3>c|5811Pru`o8Q1&#pE=;srSwhRn`6m=~r1%Rom<=Zb&4lYCS17UMr|-r^$e-+C9M! zLTpugBFcCfsL;NnhlK1bSFAr5-6N7_f-MAdsYsd$wgKfeOVWNso|H1LinOIFy9+qY zpsP2Hssgby==MPwEQj`JosTjbu>-E&K#?^NX3}jxow;y`t%mkT87_yap@k?HBDP9A zI+T>xe5O?;PUr?lw{v_pi9>9a*bb$QJZ$xRa>%vULPh2WUxx$US8P>@lTl8WLut7N ziJs3s%g$!7r`T1U!(wCvFHNz#IFz>6JcBB+K6j6uV%HZ!Z;`V5`jVs# zmUNTtCaw$jwj+qPN;KJO2iiKv?c%v$xmqyxcT=PV-SkaOSJy+H?n0s^|LH+|TGo9$ zaUG3hv|iKW_;|cyID#oz5jWkWG-?g!_28EwWwY5$z$1%-7C4VZJ4FkDJPY<|gtt74 zZevDKu$|Wu4zaXqq?FwV>>C)H#VXOu)1w0ERW9?Am2t;O0_rvD4l^ldB+~2D<57-A zvXdR5)Lmth{pDCB6MDPQcoHDlq?#M?fEMwg_nD%=O}d)Fa0~seCiZ@YTs54fq*-$iNW<-Am%{QEpDc@n4k9NcX8Y z;)Nr-pJ)FMD*XrlyU)s){z!!u{55hGiGhK;$`MuCK{V3Jzq5f%Ln5sJya8o7Qmikh zdQN0HEFc^j&A@v-;m?XN7sq!f--w{khY-jP-A7|&oUAI{^H45C>=(d2D^-Ud@~{Hz-6AUwZ=(EL zWaYt=i-P7O*H>R{e*OUao5)MSw>+2bCz9>`xB1c1O&`K1<-%D=PHg3<9XBgKTFE&S zBSR2VRmkCPughuH+i@m>Kh1JJxQ@3z4w=`uz3s1Z^p2y%(bsVogI^Rl#|Dnqd>;VL zg`V>`_%)WJt8-RJk=J|?RSu7K@E7Xm;6Fv|g<5_d#fvo2h1x_{X%k(d^)*Q1jpPjn za|f5F_1S3qx#%9ycR}puq7O$o7-3NGP?wsX!`TLf$it9fNop|FgnoK0)JZl!{X3utxi*RU2ak6L{STuh{L)sLX!L7M(NLjSxpZ&`{ z5jc*W!~90X)|4aNJRx4nI?X18Ls6f2`^vs>R8wvM{{~`fN{=k6!zuAK#^UkmqPd<9~!h3`-{BNU1TKQ0dOZB&xaipn2Qq0A(x z{RK2M5&J!=1tG4kOcx6S9wP}KhS&iQk4c%lmYhg^i0c;F)quxE;4eVTtVf;E*quWKy%tA`8XK#+Y`mIeqwOC8=pktzEIZGm=ZDybqX$FY zAxT|2d_w#{K%nTAxU&F67^4%0Cl!4&Pb z{X07Es>rsT&)XctR%9N?e4bjAP?7Zl+XLZgr?r=>9gZOrr`2<@6z$&U&)q0ft>BEb zAafk7!;nn+9^SGzbSh|mI5mA}JaRC^CS9$ z&3cC&1CqD>Du=2|7xTh1B-?8~uSI*7>^zXHUJI%>W_lu~N*{TCKXAO}ujZnCb~sKP zedBo?_#?!ruZ!b*$6+zD(|jFV)IYM<$9%9CAoaEE)`Q}p1^AFob{%e{b-2%fe+sc@ zz;jY{IO!weq4_hg8N!z$=?r*MK7~FaTP1`uQ2!F%pCR@PcoHk{YGp>%L;ei(g3trW zZgDVo1~jl_e>X+FUnw#>rf8dgp7{8-RkWnRAPRC2!AD}@P^A9NoC%Ms%P4AIJ?%aa zrKxk9CbNf^MQd;`fqE6PGPT?YOBpagqV6~zK)GFx-Ej>42VDfDbUz#$P+mit9EzhvcCT?ZjalB8 zR@7c`l<_v}Uq%Td*5>gjT%KdzT zz5w$%V#Z$r>qH0i*W=^BO8-efy?^pb8!?%0AWd?xqM#MKv=yBhr+<{~-+^_9&`qTA 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

J>x$;d2o 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Μ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 DKn6qL literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/greenlet.cpp b/venv/lib/python3.12/site-packages/greenlet/greenlet.cpp new file mode 100644 index 0000000..e8d92a0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/greenlet.cpp @@ -0,0 +1,320 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/* Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ +#include +#include +#include +#include + + +#define PY_SSIZE_T_CLEAN +#include +#include "structmember.h" // PyMemberDef + +#include "greenlet_internal.hpp" +// Code after this point can assume access to things declared in stdint.h, +// including the fixed-width types. This goes for the platform-specific switch functions +// as well. +#include "greenlet_refs.hpp" +#include "greenlet_slp_switch.hpp" + +#include "greenlet_thread_support.hpp" +#include "TGreenlet.hpp" + +#include "TGreenletGlobals.cpp" + +#include "TGreenlet.cpp" +#include "TMainGreenlet.cpp" +#include "TUserGreenlet.cpp" +#include "TBrokenGreenlet.cpp" +#include "TExceptionState.cpp" +#include "TPythonState.cpp" +#include "TStackState.cpp" + +#include "TThreadState.hpp" +#include "TThreadStateCreator.hpp" +#include "TThreadStateDestroy.cpp" + +#include "PyGreenlet.cpp" +#include "PyGreenletUnswitchable.cpp" +#include "CObjects.cpp" + +using greenlet::LockGuard; +using greenlet::LockInitError; +using greenlet::PyErrOccurred; +using greenlet::Require; + +using greenlet::g_handle_exit; +using greenlet::single_result; + +using greenlet::Greenlet; +using greenlet::UserGreenlet; +using greenlet::MainGreenlet; +using greenlet::BrokenGreenlet; +using greenlet::ThreadState; +using greenlet::PythonState; + + + +// ******* Implementation of things from included files +template +greenlet::refs::_BorrowedGreenlet& greenlet::refs::_BorrowedGreenlet::operator=(const greenlet::refs::BorrowedObject& other) +{ + this->_set_raw_pointer(static_cast(other)); + return *this; +} + +template +inline greenlet::refs::_BorrowedGreenlet::operator Greenlet*() const noexcept +{ + if (!this->p) { + return nullptr; + } + return reinterpret_cast(this->p)->pimpl; +} + +template +greenlet::refs::_BorrowedGreenlet::_BorrowedGreenlet(const BorrowedObject& p) + : BorrowedReference(nullptr) +{ + + this->_set_raw_pointer(p.borrow()); +} + +template +inline greenlet::refs::_OwnedGreenlet::operator Greenlet*() const noexcept +{ + if (!this->p) { + return nullptr; + } + return reinterpret_cast(this->p)->pimpl; +} + + + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wmissing-field-initializers" +# pragma clang diagnostic ignored "-Wwritable-strings" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +// warning: ISO C++ forbids converting a string constant to ‘char*’ +// (The python APIs aren't const correct and accept writable char*) +# pragma GCC diagnostic ignored "-Wwrite-strings" +#endif + + +/*********************************************************** + +A PyGreenlet is a range of C stack addresses that must be +saved and restored in such a way that the full range of the +stack contains valid data when we switch to it. + +Stack layout for a greenlet: + + | ^^^ | + | older data | + | | + stack_stop . |_______________| + . | | + . | greenlet data | + . | in stack | + . * |_______________| . . _____________ stack_copy + stack_saved + . | | | | + . | data | |greenlet data| + . | unrelated | | saved | + . | to | | in heap | + stack_start . | this | . . |_____________| stack_copy + | greenlet | + | | + | newer data | + | vvv | + + +Note that a greenlet's stack data is typically partly at its correct +place in the stack, and partly saved away in the heap, but always in +the above configuration: two blocks, the more recent one in the heap +and the older one still in the stack (either block may be empty). + +Greenlets are chained: each points to the previous greenlet, which is +the one that owns the data currently in the C stack above my +stack_stop. The currently running greenlet is the first element of +this chain. The main (initial) greenlet is the last one. Greenlets +whose stack is entirely in the heap can be skipped from the chain. + +The chain is not related to execution order, but only to the order +in which bits of C stack happen to belong to greenlets at a particular +point in time. + +The main greenlet doesn't have a stack_stop: it is responsible for the +complete rest of the C stack, and we don't know where it begins. We +use (char*) -1, the largest possible address. + +States: + stack_stop == NULL && stack_start == NULL: did not start yet + stack_stop != NULL && stack_start == NULL: already finished + stack_stop != NULL && stack_start != NULL: active + +The running greenlet's stack_start is undefined but not NULL. + + ***********************************************************/ + + + + +/***********************************************************/ + +/* Some functions must not be inlined: + * slp_restore_state, when inlined into slp_switch might cause + it to restore stack over its own local variables + * slp_save_state, when inlined would add its own local + variables to the saved stack, wasting space + * slp_switch, cannot be inlined for obvious reasons + * g_initialstub, when inlined would receive a pointer into its + own stack frame, leading to incomplete stack save/restore + +g_initialstub is a member function and declared virtual so that the +compiler always calls it through a vtable. + +slp_save_state and slp_restore_state are also member functions. They +are called from trampoline functions that themselves are declared as +not eligible for inlining. +*/ + +extern "C" { +static int GREENLET_NOINLINE(slp_save_state_trampoline)(char* stackref) +{ + return switching_thread_state->slp_save_state(stackref); +} +static void GREENLET_NOINLINE(slp_restore_state_trampoline)() +{ + switching_thread_state->slp_restore_state(); +} +} + + +/***********************************************************/ + + +#include "PyModule.cpp" + + + +static PyObject* +greenlet_internal_mod_init() noexcept +{ + static void* _PyGreenlet_API[PyGreenlet_API_pointers]; + + try { + CreatedModule m(greenlet_module_def); + + Require(PyType_Ready(&PyGreenlet_Type)); + Require(PyType_Ready(&PyGreenletUnswitchable_Type)); + + mod_globs = new greenlet::GreenletGlobals; + ThreadState::init(); + + m.PyAddObject("greenlet", PyGreenlet_Type); + m.PyAddObject("UnswitchableGreenlet", PyGreenletUnswitchable_Type); + m.PyAddObject("error", mod_globs->PyExc_GreenletError); + m.PyAddObject("GreenletExit", mod_globs->PyExc_GreenletExit); + + m.PyAddObject("GREENLET_USE_GC", 1); + m.PyAddObject("GREENLET_USE_TRACING", 1); + m.PyAddObject("GREENLET_USE_CONTEXT_VARS", 1L); + m.PyAddObject("GREENLET_USE_STANDARD_THREADING", 1L); + + OwnedObject clocks_per_sec = OwnedObject::consuming(PyLong_FromSsize_t(CLOCKS_PER_SEC)); + m.PyAddObject("CLOCKS_PER_SEC", clocks_per_sec); + + /* also publish module-level data as attributes of the greentype. */ + // XXX: This is weird, and enables a strange pattern of + // confusing the class greenlet with the module greenlet; with + // the exception of (possibly) ``getcurrent()``, this + // shouldn't be encouraged so don't add new items here. + for (const char* const* p = copy_on_greentype; *p; p++) { + OwnedObject o = m.PyRequireAttr(*p); + PyDict_SetItemString(PyGreenlet_Type.tp_dict, *p, o.borrow()); + } + + /* + * Expose C API + */ + + /* types */ + _PyGreenlet_API[PyGreenlet_Type_NUM] = (void*)&PyGreenlet_Type; + + /* exceptions */ + _PyGreenlet_API[PyExc_GreenletError_NUM] = (void*)mod_globs->PyExc_GreenletError; + _PyGreenlet_API[PyExc_GreenletExit_NUM] = (void*)mod_globs->PyExc_GreenletExit; + + /* methods */ + _PyGreenlet_API[PyGreenlet_New_NUM] = (void*)PyGreenlet_New; + _PyGreenlet_API[PyGreenlet_GetCurrent_NUM] = (void*)PyGreenlet_GetCurrent; + _PyGreenlet_API[PyGreenlet_Throw_NUM] = (void*)PyGreenlet_Throw; + _PyGreenlet_API[PyGreenlet_Switch_NUM] = (void*)PyGreenlet_Switch; + _PyGreenlet_API[PyGreenlet_SetParent_NUM] = (void*)PyGreenlet_SetParent; + + /* Previously macros, but now need to be functions externally. */ + _PyGreenlet_API[PyGreenlet_MAIN_NUM] = (void*)Extern_PyGreenlet_MAIN; + _PyGreenlet_API[PyGreenlet_STARTED_NUM] = (void*)Extern_PyGreenlet_STARTED; + _PyGreenlet_API[PyGreenlet_ACTIVE_NUM] = (void*)Extern_PyGreenlet_ACTIVE; + _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM] = (void*)Extern_PyGreenlet_GET_PARENT; + + /* XXX: Note that our module name is ``greenlet._greenlet``, but for + backwards compatibility with existing C code, we need the _C_API to + be directly in greenlet. + */ + const NewReference c_api_object(Require( + PyCapsule_New( + (void*)_PyGreenlet_API, + "greenlet._C_API", + NULL))); + m.PyAddObject("_C_API", c_api_object); + assert(c_api_object.REFCNT() == 2); + + // cerr << "Sizes:" + // << "\n\tGreenlet : " << sizeof(Greenlet) + // << "\n\tUserGreenlet : " << sizeof(UserGreenlet) + // << "\n\tMainGreenlet : " << sizeof(MainGreenlet) + // << "\n\tExceptionState : " << sizeof(greenlet::ExceptionState) + // << "\n\tPythonState : " << sizeof(greenlet::PythonState) + // << "\n\tStackState : " << sizeof(greenlet::StackState) + // << "\n\tSwitchingArgs : " << sizeof(greenlet::SwitchingArgs) + // << "\n\tOwnedObject : " << sizeof(greenlet::refs::OwnedObject) + // << "\n\tBorrowedObject : " << sizeof(greenlet::refs::BorrowedObject) + // << "\n\tPyGreenlet : " << sizeof(PyGreenlet) + // << endl; + + return m.borrow(); // But really it's the main reference. + } + catch (const LockInitError& e) { + PyErr_SetString(PyExc_MemoryError, e.what()); + return NULL; + } + catch (const PyErrOccurred&) { + return NULL; + } + +} + +extern "C" { + +PyMODINIT_FUNC +PyInit__greenlet(void) +{ + return greenlet_internal_mod_init(); +} + +}; // extern C + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/greenlet.h b/venv/lib/python3.12/site-packages/greenlet/greenlet.h new file mode 100644 index 0000000..d02a16e --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/greenlet.h @@ -0,0 +1,164 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ + +/* Greenlet object interface */ + +#ifndef Py_GREENLETOBJECT_H +#define Py_GREENLETOBJECT_H + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is deprecated and undocumented. It does not change. */ +#define GREENLET_VERSION "1.0.0" + +#ifndef GREENLET_MODULE +#define implementation_ptr_t void* +#endif + +typedef struct _greenlet { + PyObject_HEAD + PyObject* weakreflist; + PyObject* dict; + implementation_ptr_t pimpl; +} PyGreenlet; + +#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type)) + + +/* C API functions */ + +/* Total number of symbols that are exported */ +#define PyGreenlet_API_pointers 12 + +#define PyGreenlet_Type_NUM 0 +#define PyExc_GreenletError_NUM 1 +#define PyExc_GreenletExit_NUM 2 + +#define PyGreenlet_New_NUM 3 +#define PyGreenlet_GetCurrent_NUM 4 +#define PyGreenlet_Throw_NUM 5 +#define PyGreenlet_Switch_NUM 6 +#define PyGreenlet_SetParent_NUM 7 + +#define PyGreenlet_MAIN_NUM 8 +#define PyGreenlet_STARTED_NUM 9 +#define PyGreenlet_ACTIVE_NUM 10 +#define PyGreenlet_GET_PARENT_NUM 11 + +#ifndef GREENLET_MODULE +/* This section is used by modules that uses the greenlet C API */ +static void** _PyGreenlet_API = NULL; + +# define PyGreenlet_Type \ + (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM]) + +# define PyExc_GreenletError \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM]) + +# define PyExc_GreenletExit \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM]) + +/* + * PyGreenlet_New(PyObject *args) + * + * greenlet.greenlet(run, parent=None) + */ +# define PyGreenlet_New \ + (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \ + _PyGreenlet_API[PyGreenlet_New_NUM]) + +/* + * PyGreenlet_GetCurrent(void) + * + * greenlet.getcurrent() + */ +# define PyGreenlet_GetCurrent \ + (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM]) + +/* + * PyGreenlet_Throw( + * PyGreenlet *greenlet, + * PyObject *typ, + * PyObject *val, + * PyObject *tb) + * + * g.throw(...) + */ +# define PyGreenlet_Throw \ + (*(PyObject * (*)(PyGreenlet * self, \ + PyObject * typ, \ + PyObject * val, \ + PyObject * tb)) \ + _PyGreenlet_API[PyGreenlet_Throw_NUM]) + +/* + * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args) + * + * g.switch(*args, **kwargs) + */ +# define PyGreenlet_Switch \ + (*(PyObject * \ + (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \ + _PyGreenlet_API[PyGreenlet_Switch_NUM]) + +/* + * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent) + * + * g.parent = new_parent + */ +# define PyGreenlet_SetParent \ + (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \ + _PyGreenlet_API[PyGreenlet_SetParent_NUM]) + +/* + * PyGreenlet_GetParent(PyObject* greenlet) + * + * return greenlet.parent; + * + * This could return NULL even if there is no exception active. + * If it does not return NULL, you are responsible for decrementing the + * reference count. + */ +# define PyGreenlet_GetParent \ + (*(PyGreenlet* (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM]) + +/* + * deprecated, undocumented alias. + */ +# define PyGreenlet_GET_PARENT PyGreenlet_GetParent + +# define PyGreenlet_MAIN \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_MAIN_NUM]) + +# define PyGreenlet_STARTED \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_STARTED_NUM]) + +# define PyGreenlet_ACTIVE \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_ACTIVE_NUM]) + + + + +/* Macro that imports greenlet and initializes C API */ +/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we + keep the older definition to be sure older code that might have a copy of + the header still works. */ +# define PyGreenlet_Import() \ + { \ + _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \ + } + +#endif /* GREENLET_MODULE */ + +#ifdef __cplusplus +} +#endif +#endif /* !Py_GREENLETOBJECT_H */ diff --git a/venv/lib/python3.12/site-packages/greenlet/greenlet_allocator.hpp b/venv/lib/python3.12/site-packages/greenlet/greenlet_allocator.hpp new file mode 100644 index 0000000..dc2b969 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/greenlet_allocator.hpp @@ -0,0 +1,89 @@ +#ifndef GREENLET_ALLOCATOR_HPP +#define GREENLET_ALLOCATOR_HPP + +#define PY_SSIZE_T_CLEAN +#include +#include +#include "greenlet_compiler_compat.hpp" +#include "greenlet_cpython_compat.hpp" + + +namespace greenlet +{ +#if defined(Py_GIL_DISABLED) +// Python on free threaded builds says this +// (https://docs.python.org/3/howto/free-threading-extensions.html#memory-allocation-apis): +// +// For thread-safety, the free-threaded build requires that only +// Python objects are allocated using the object domain, and that all +// Python object are allocated using that domain. +// +// This turns out to be important because the GC implementation on +// free threaded Python uses internal mimalloc APIs to find allocated +// objects. If we allocate non-PyObject objects using that API, then +// Bad Things could happen, including crashes and improper results. +// So in that case, we revert to standard C++ allocation. + + template + struct PythonAllocator : public std::allocator { + // This member is deprecated in C++17 and removed in C++20 + template< class U > + struct rebind { + typedef PythonAllocator other; + }; + }; + +#else + // This allocator is stateless; all instances are identical. + // It can *ONLY* be used when we're sure we're holding the GIL + // (Python's allocators require the GIL). + template + struct PythonAllocator : public std::allocator { + + PythonAllocator(const PythonAllocator& UNUSED(other)) + : std::allocator() + { + } + + PythonAllocator(const std::allocator other) + : std::allocator(other) + {} + + template + PythonAllocator(const std::allocator& other) + : std::allocator(other) + { + } + + PythonAllocator() : std::allocator() {} + + T* allocate(size_t number_objects, const void* UNUSED(hint)=0) + { + void* p; + if (number_objects == 1) + p = PyObject_Malloc(sizeof(T)); + else + p = PyMem_Malloc(sizeof(T) * number_objects); + return static_cast(p); + } + + void deallocate(T* t, size_t n) + { + void* p = t; + if (n == 1) { + PyObject_Free(p); + } + else + PyMem_Free(p); + } + // This member is deprecated in C++17 and removed in C++20 + template< class U > + struct rebind { + typedef PythonAllocator other; + }; + + }; +#endif // allocator type +} + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/greenlet_compiler_compat.hpp b/venv/lib/python3.12/site-packages/greenlet/greenlet_compiler_compat.hpp new file mode 100644 index 0000000..af24bd8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/greenlet_compiler_compat.hpp @@ -0,0 +1,98 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +#ifndef GREENLET_COMPILER_COMPAT_HPP +#define GREENLET_COMPILER_COMPAT_HPP + +/** + * Definitions to aid with compatibility with different compilers. + * + * .. caution:: Use extreme care with noexcept. + * Some compilers and runtimes, specifically gcc/libgcc/libstdc++ on + * Linux, implement stack unwinding by throwing an uncatchable + * exception, one that specifically does not appear to be an active + * exception to the rest of the runtime. If this happens while we're in a noexcept function, + * we have violated our dynamic exception contract, and so the runtime + * will call std::terminate(), which kills the process with the + * unhelpful message "terminate called without an active exception". + * + * This has happened in this scenario: A background thread is running + * a greenlet that has made a native call and released the GIL. + * Meanwhile, the main thread finishes and starts shutting down the + * interpreter. When the background thread is scheduled again and + * attempts to obtain the GIL, it notices that the interpreter is + * exiting and calls ``pthread_exit()``. This in turn starts to unwind + * the stack by throwing that exception. But we had the ``PyCall`` + * functions annotated as noexcept, so the runtime terminated us. + * + * #2 0x00007fab26fec2b7 in std::terminate() () from /lib/x86_64-linux-gnu/libstdc++.so.6 + * #3 0x00007fab26febb3c in __gxx_personality_v0 () from /lib/x86_64-linux-gnu/libstdc++.so.6 + * #4 0x00007fab26f34de6 in ?? () from /lib/x86_64-linux-gnu/libgcc_s.so.1 + * #6 0x00007fab276a34c6 in __GI___pthread_unwind at ./nptl/unwind.c:130 + * #7 0x00007fab2769bd3a in __do_cancel () at ../sysdeps/nptl/pthreadP.h:280 + * #8 __GI___pthread_exit (value=value@entry=0x0) at ./nptl/pthread_exit.c:36 + * #9 0x000000000052e567 in PyThread_exit_thread () at ../Python/thread_pthread.h:370 + * #10 0x00000000004d60b5 in take_gil at ../Python/ceval_gil.h:224 + * #11 0x00000000004d65f9 in PyEval_RestoreThread at ../Python/ceval.c:467 + * #12 0x000000000060cce3 in setipaddr at ../Modules/socketmodule.c:1203 + * #13 0x00000000006101cd in socket_gethostbyname + */ + +#include + +# define G_NO_COPIES_OF_CLS(Cls) private: \ + Cls(const Cls& other) = delete; \ + Cls& operator=(const Cls& other) = delete + +# define G_NO_ASSIGNMENT_OF_CLS(Cls) private: \ + Cls& operator=(const Cls& other) = delete + +# define G_NO_COPY_CONSTRUCTOR_OF_CLS(Cls) private: \ + Cls(const Cls& other) = delete; + + +// CAUTION: MSVC is stupidly picky: +// +// "The compiler ignores, without warning, any __declspec keywords +// placed after * or & and in front of the variable identifier in a +// declaration." +// (https://docs.microsoft.com/en-us/cpp/cpp/declspec?view=msvc-160) +// +// So pointer return types must be handled differently (because of the +// trailing *), or you get inscrutable compiler warnings like "error +// C2059: syntax error: ''" +// +// In C++ 11, there is a standard syntax for attributes, and +// GCC defines an attribute to use with this: [[gnu:noinline]]. +// In the future, this is expected to become standard. + +#if defined(__GNUC__) || defined(__clang__) +/* We used to check for GCC 4+ or 3.4+, but those compilers are + laughably out of date. Just assume they support it. */ +# define GREENLET_NOINLINE(name) __attribute__((noinline)) name +# define GREENLET_NOINLINE_P(rtype, name) rtype __attribute__((noinline)) name +# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) +#elif defined(_MSC_VER) +/* We used to check for && (_MSC_VER >= 1300) but that's also out of date. */ +# define GREENLET_NOINLINE(name) __declspec(noinline) name +# define GREENLET_NOINLINE_P(rtype, name) __declspec(noinline) rtype name +# define UNUSED(x) UNUSED_ ## x +#endif + +#if defined(_MSC_VER) +# define G_NOEXCEPT_WIN32 noexcept +#else +# define G_NOEXCEPT_WIN32 +#endif + +#if defined(__GNUC__) && defined(__POWERPC__) && defined(__APPLE__) +// 32-bit PPC/MacOSX. Only known to be tested on unreleased versions +// of macOS 10.6 using a macports build gcc 14. It appears that +// running C++ destructors of thread-local variables is broken. + +// See https://github.com/python-greenlet/greenlet/pull/419 +# define GREENLET_BROKEN_THREAD_LOCAL_CLEANUP_JUST_LEAK 1 +#else +# define GREENLET_BROKEN_THREAD_LOCAL_CLEANUP_JUST_LEAK 0 +#endif + + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/greenlet_cpython_compat.hpp b/venv/lib/python3.12/site-packages/greenlet/greenlet_cpython_compat.hpp new file mode 100644 index 0000000..a3b3850 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/greenlet_cpython_compat.hpp @@ -0,0 +1,150 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +#ifndef GREENLET_CPYTHON_COMPAT_H +#define GREENLET_CPYTHON_COMPAT_H + +/** + * Helpers for compatibility with multiple versions of CPython. + */ + +#define PY_SSIZE_T_CLEAN +#include "Python.h" + + +#if PY_VERSION_HEX >= 0x30A00B1 +# define GREENLET_PY310 1 +#else +# define GREENLET_PY310 0 +#endif + +/* +Python 3.10 beta 1 changed tstate->use_tracing to a nested cframe member. +See https://github.com/python/cpython/pull/25276 +We have to save and restore this as well. + +Python 3.13 removed PyThreadState.cframe (GH-108035). +*/ +#if GREENLET_PY310 && PY_VERSION_HEX < 0x30D0000 +# define GREENLET_USE_CFRAME 1 +#else +# define GREENLET_USE_CFRAME 0 +#endif + + +#if PY_VERSION_HEX >= 0x30B00A4 +/* +Greenlet won't compile on anything older than Python 3.11 alpha 4 (see +https://bugs.python.org/issue46090). Summary of breaking internal changes: +- Python 3.11 alpha 1 changed how frame objects are represented internally. + - https://github.com/python/cpython/pull/30122 +- Python 3.11 alpha 3 changed how recursion limits are stored. + - https://github.com/python/cpython/pull/29524 +- Python 3.11 alpha 4 changed how exception state is stored. It also includes a + change to help greenlet save and restore the interpreter frame "data stack". + - https://github.com/python/cpython/pull/30122 + - https://github.com/python/cpython/pull/30234 +*/ +# define GREENLET_PY311 1 +#else +# define GREENLET_PY311 0 +#endif + + +#if PY_VERSION_HEX >= 0x30C0000 +# define GREENLET_PY312 1 +#else +# define GREENLET_PY312 0 +#endif + +#if PY_VERSION_HEX >= 0x30D0000 +# define GREENLET_PY313 1 +#else +# define GREENLET_PY313 0 +#endif + +#if PY_VERSION_HEX >= 0x30E0000 +# define GREENLET_PY314 1 +#else +# define GREENLET_PY314 0 +#endif + +#ifndef Py_SET_REFCNT +/* Py_REFCNT and Py_SIZE macros are converted to functions +https://bugs.python.org/issue39573 */ +# define Py_SET_REFCNT(obj, refcnt) Py_REFCNT(obj) = (refcnt) +#endif + +#ifdef _Py_DEC_REFTOTAL +# define GREENLET_Py_DEC_REFTOTAL _Py_DEC_REFTOTAL +#else +/* _Py_DEC_REFTOTAL macro has been removed from Python 3.9 by: + https://github.com/python/cpython/commit/49932fec62c616ec88da52642339d83ae719e924 + + The symbol we use to replace it was removed by at least 3.12. +*/ +# ifdef Py_REF_DEBUG +# if GREENLET_PY312 +# define GREENLET_Py_DEC_REFTOTAL +# else +# define GREENLET_Py_DEC_REFTOTAL _Py_RefTotal-- +# endif +# else +# define GREENLET_Py_DEC_REFTOTAL +# endif +#endif +// Define these flags like Cython does if we're on an old version. +#ifndef Py_TPFLAGS_CHECKTYPES + #define Py_TPFLAGS_CHECKTYPES 0 +#endif +#ifndef Py_TPFLAGS_HAVE_INDEX + #define Py_TPFLAGS_HAVE_INDEX 0 +#endif +#ifndef Py_TPFLAGS_HAVE_NEWBUFFER + #define Py_TPFLAGS_HAVE_NEWBUFFER 0 +#endif + +#ifndef Py_TPFLAGS_HAVE_VERSION_TAG + #define Py_TPFLAGS_HAVE_VERSION_TAG 0 +#endif + +#define G_TPFLAGS_DEFAULT Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_HAVE_NEWBUFFER | Py_TPFLAGS_HAVE_GC + + +#if PY_VERSION_HEX < 0x03090000 +// The official version only became available in 3.9 +# define PyObject_GC_IsTracked(o) _PyObject_GC_IS_TRACKED(o) +#endif + + +// bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2 +#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) +static inline void PyThreadState_EnterTracing(PyThreadState *tstate) +{ + tstate->tracing++; +#if PY_VERSION_HEX >= 0x030A00A1 + tstate->cframe->use_tracing = 0; +#else + tstate->use_tracing = 0; +#endif +} +#endif + +// bpo-43760 added PyThreadState_LeaveTracing() to Python 3.11.0a2 +#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) +static inline void PyThreadState_LeaveTracing(PyThreadState *tstate) +{ + tstate->tracing--; + int use_tracing = (tstate->c_tracefunc != NULL + || tstate->c_profilefunc != NULL); +#if PY_VERSION_HEX >= 0x030A00A1 + tstate->cframe->use_tracing = use_tracing; +#else + tstate->use_tracing = use_tracing; +#endif +} +#endif + +#if !defined(Py_C_RECURSION_LIMIT) && defined(C_RECURSION_LIMIT) +# define Py_C_RECURSION_LIMIT C_RECURSION_LIMIT +#endif + +#endif /* GREENLET_CPYTHON_COMPAT_H */ diff --git a/venv/lib/python3.12/site-packages/greenlet/greenlet_exceptions.hpp b/venv/lib/python3.12/site-packages/greenlet/greenlet_exceptions.hpp new file mode 100644 index 0000000..617f07c --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/greenlet_exceptions.hpp @@ -0,0 +1,171 @@ +#ifndef GREENLET_EXCEPTIONS_HPP +#define GREENLET_EXCEPTIONS_HPP + +#define PY_SSIZE_T_CLEAN +#include +#include +#include + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-function" +#endif + +namespace greenlet { + + class PyErrOccurred : public std::runtime_error + { + public: + + // CAUTION: In debug builds, may run arbitrary Python code. + static const PyErrOccurred + from_current() + { + assert(PyErr_Occurred()); +#ifndef NDEBUG + // This is not exception safe, and + // not necessarily safe in general (what if it switches?) + // But we only do this in debug mode, where we are in + // tight control of what exceptions are getting raised and + // can prevent those issues. + + // You can't call PyObject_Str with a pending exception. + PyObject* typ; + PyObject* val; + PyObject* tb; + + PyErr_Fetch(&typ, &val, &tb); + PyObject* typs = PyObject_Str(typ); + PyObject* vals = PyObject_Str(val ? val : typ); + const char* typ_msg = PyUnicode_AsUTF8(typs); + const char* val_msg = PyUnicode_AsUTF8(vals); + PyErr_Restore(typ, val, tb); + + std::string msg(typ_msg); + msg += ": "; + msg += val_msg; + PyErrOccurred ex(msg); + Py_XDECREF(typs); + Py_XDECREF(vals); + + return ex; +#else + return PyErrOccurred(); +#endif + } + + PyErrOccurred() : std::runtime_error("") + { + assert(PyErr_Occurred()); + } + + PyErrOccurred(const std::string& msg) : std::runtime_error(msg) + { + assert(PyErr_Occurred()); + } + + PyErrOccurred(PyObject* exc_kind, const char* const msg) + : std::runtime_error(msg) + { + PyErr_SetString(exc_kind, msg); + } + + PyErrOccurred(PyObject* exc_kind, const std::string msg) + : std::runtime_error(msg) + { + // This copies the c_str, so we don't have any lifetime + // issues to worry about. + PyErr_SetString(exc_kind, msg.c_str()); + } + + PyErrOccurred(PyObject* exc_kind, + const std::string msg, //This is the format + //string; that's not + //usually safe! + + PyObject* borrowed_obj_one, PyObject* borrowed_obj_two) + : std::runtime_error(msg) + { + + //This is designed specifically for the + //``check_switch_allowed`` function. + + // PyObject_Str and PyObject_Repr are safe to call with + // NULL pointers; they return the string "" in that + // case. + // This function always returns null. + PyErr_Format(exc_kind, + msg.c_str(), + borrowed_obj_one, borrowed_obj_two); + } + }; + + class TypeError : public PyErrOccurred + { + public: + TypeError(const char* const what) + : PyErrOccurred(PyExc_TypeError, what) + { + } + TypeError(const std::string what) + : PyErrOccurred(PyExc_TypeError, what) + { + } + }; + + class ValueError : public PyErrOccurred + { + public: + ValueError(const char* const what) + : PyErrOccurred(PyExc_ValueError, what) + { + } + }; + + class AttributeError : public PyErrOccurred + { + public: + AttributeError(const char* const what) + : PyErrOccurred(PyExc_AttributeError, what) + { + } + }; + + /** + * Calls `Py_FatalError` when constructed, so you can't actually + * throw this. It just makes static analysis easier. + */ + class PyFatalError : public std::runtime_error + { + public: + PyFatalError(const char* const msg) + : std::runtime_error(msg) + { + Py_FatalError(msg); + } + }; + + static inline PyObject* + Require(PyObject* p, const std::string& msg="") + { + if (!p) { + throw PyErrOccurred(msg); + } + return p; + }; + + static inline void + Require(const int retval) + { + if (retval < 0) { + throw PyErrOccurred(); + } + }; + + +}; +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/greenlet_internal.hpp b/venv/lib/python3.12/site-packages/greenlet/greenlet_internal.hpp new file mode 100644 index 0000000..f2b15d5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/greenlet_internal.hpp @@ -0,0 +1,107 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +#ifndef GREENLET_INTERNAL_H +#define GREENLET_INTERNAL_H +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-function" +#endif + +/** + * Implementation helpers. + * + * C++ templates and inline functions should go here. + */ +#define PY_SSIZE_T_CLEAN +#include "greenlet_compiler_compat.hpp" +#include "greenlet_cpython_compat.hpp" +#include "greenlet_exceptions.hpp" +#include "TGreenlet.hpp" +#include "greenlet_allocator.hpp" + +#include +#include + +#define GREENLET_MODULE +struct _greenlet; +typedef struct _greenlet PyGreenlet; +namespace greenlet { + + class ThreadState; + // We can't use the PythonAllocator for this, because we push to it + // from the thread state destructor, which doesn't have the GIL, + // and Python's allocators can only be called with the GIL. + typedef std::vector cleanup_queue_t; + +}; + + +#define implementation_ptr_t greenlet::Greenlet* + + +#include "greenlet.h" + +void +greenlet::refs::MainGreenletExactChecker(void *p) +{ + if (!p) { + return; + } + // We control the class of the main greenlet exactly. + if (Py_TYPE(p) != &PyGreenlet_Type) { + std::string err("MainGreenlet: Expected exactly a greenlet, not a "); + err += Py_TYPE(p)->tp_name; + throw greenlet::TypeError(err); + } + + // Greenlets from dead threads no longer respond to main() with a + // true value; so in that case we need to perform an additional + // check. + Greenlet* g = static_cast(p)->pimpl; + if (g->main()) { + return; + } + if (!dynamic_cast(g)) { + std::string err("MainGreenlet: Expected exactly a main greenlet, not a "); + err += Py_TYPE(p)->tp_name; + throw greenlet::TypeError(err); + } +} + + + +template +inline greenlet::Greenlet* greenlet::refs::_OwnedGreenlet::operator->() const noexcept +{ + return reinterpret_cast(this->p)->pimpl; +} + +template +inline greenlet::Greenlet* greenlet::refs::_BorrowedGreenlet::operator->() const noexcept +{ + return reinterpret_cast(this->p)->pimpl; +} + +#include +#include + + +extern PyTypeObject PyGreenlet_Type; + + + +/** + * Forward declarations needed in multiple files. + */ +static PyObject* green_switch(PyGreenlet* self, PyObject* args, PyObject* kwargs); + + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + + +#endif + +// Local Variables: +// flycheck-clang-include-path: ("../../include" "/opt/local/Library/Frameworks/Python.framework/Versions/3.10/include/python3.10") +// End: diff --git a/venv/lib/python3.12/site-packages/greenlet/greenlet_msvc_compat.hpp b/venv/lib/python3.12/site-packages/greenlet/greenlet_msvc_compat.hpp new file mode 100644 index 0000000..c00245b --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/greenlet_msvc_compat.hpp @@ -0,0 +1,91 @@ +#ifndef GREENLET_MSVC_COMPAT_HPP +#define GREENLET_MSVC_COMPAT_HPP +/* + * Support for MSVC on Windows. + * + * Beginning with Python 3.14, some of the internal + * include files we need are not compatible with MSVC + * in C++ mode: + * + * internal\pycore_stackref.h(253): error C4576: a parenthesized type + * followed by an initializer list is a non-standard explicit type conversion syntax + * + * This file is included from ``internal/pycore_interpframe.h``, which + * we need for the ``_PyFrame_IsIncomplete`` API. + * + * Unfortunately, that API is a ``static inline`` function, as are a + * bunch of the functions it calls. The only solution seems to be to + * copy those definitions and the supporting inline functions here. + * + * Now, this makes us VERY fragile to changes in those functions. Because + * they're internal and static, the CPython devs might feel free to change + * them in even minor versions, meaning that we could runtime link and load, + * but still crash. We have that problem on all platforms though. It's just worse + * here because we have to keep copying the updated definitions. + */ +#include +#include "greenlet_cpython_compat.hpp" + +// This file is only included on 3.14+ + +extern "C" { + +// pycore_code.h ---------------- +#define _PyCode_CODE(CO) _Py_RVALUE((_Py_CODEUNIT *)(CO)->co_code_adaptive) +// End pycore_code.h ---------- + +// pycore_interpframe.h ---------- +#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG) + +#define Py_TAG_BITS 0 +#else +#define Py_TAG_BITS ((uintptr_t)1) +#define Py_TAG_DEFERRED (1) +#endif + + +static const _PyStackRef PyStackRef_NULL = { .bits = Py_TAG_DEFERRED}; +#define PyStackRef_IsNull(stackref) ((stackref).bits == PyStackRef_NULL.bits) + +static inline PyObject * +PyStackRef_AsPyObjectBorrow(_PyStackRef stackref) +{ + PyObject *cleared = ((PyObject *)((stackref).bits & (~Py_TAG_BITS))); + return cleared; +} + +static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) { + assert(!PyStackRef_IsNull(f->f_executable)); + PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable); + assert(PyCode_Check(executable)); + return (PyCodeObject *)executable; +} + + +static inline _Py_CODEUNIT * +_PyFrame_GetBytecode(_PyInterpreterFrame *f) +{ +#ifdef Py_GIL_DISABLED + PyCodeObject *co = _PyFrame_GetCode(f); + _PyCodeArray *tlbc = _PyCode_GetTLBCArray(co); + assert(f->tlbc_index >= 0 && f->tlbc_index < tlbc->size); + return (_Py_CODEUNIT *)tlbc->entries[f->tlbc_index]; +#else + return _PyCode_CODE(_PyFrame_GetCode(f)); +#endif +} + +static inline bool //_Py_NO_SANITIZE_THREAD +_PyFrame_IsIncomplete(_PyInterpreterFrame *frame) +{ + if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) { + return true; + } + return frame->owner != FRAME_OWNED_BY_GENERATOR && + frame->instr_ptr < _PyFrame_GetBytecode(frame) + + _PyFrame_GetCode(frame)->_co_firsttraceable; +} +// pycore_interpframe.h ---------- + +} +#endif // GREENLET_MSVC_COMPAT_HPP diff --git a/venv/lib/python3.12/site-packages/greenlet/greenlet_refs.hpp b/venv/lib/python3.12/site-packages/greenlet/greenlet_refs.hpp new file mode 100644 index 0000000..b7e5e3f --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/greenlet_refs.hpp @@ -0,0 +1,1118 @@ +#ifndef GREENLET_REFS_HPP +#define GREENLET_REFS_HPP + +#define PY_SSIZE_T_CLEAN +#include + +#include + +//#include "greenlet_internal.hpp" +#include "greenlet_compiler_compat.hpp" +#include "greenlet_cpython_compat.hpp" +#include "greenlet_exceptions.hpp" + +struct _greenlet; +struct _PyMainGreenlet; + +typedef struct _greenlet PyGreenlet; +extern PyTypeObject PyGreenlet_Type; + + +#ifdef GREENLET_USE_STDIO +#include +using std::cerr; +using std::endl; +#endif + +namespace greenlet +{ + class Greenlet; + + namespace refs + { + // Type checkers throw a TypeError if the argument is not + // null, and isn't of the required Python type. + // (We can't use most of the defined type checkers + // like PyList_Check, etc, directly, because they are + // implemented as macros.) + typedef void (*TypeChecker)(void*); + + void + NoOpChecker(void*) + { + return; + } + + void + GreenletChecker(void *p) + { + if (!p) { + return; + } + + PyTypeObject* typ = Py_TYPE(p); + // fast, common path. (PyObject_TypeCheck is a macro or + // static inline function, and it also does a + // direct comparison of the type pointers, but its fast + // path only handles one type) + if (typ == &PyGreenlet_Type) { + return; + } + + if (!PyObject_TypeCheck(p, &PyGreenlet_Type)) { + std::string err("GreenletChecker: Expected any type of greenlet, not "); + err += Py_TYPE(p)->tp_name; + throw TypeError(err); + } + } + + void + MainGreenletExactChecker(void *p); + + template + class PyObjectPointer; + + template + class OwnedReference; + + + template + class BorrowedReference; + + typedef BorrowedReference BorrowedObject; + typedef OwnedReference OwnedObject; + + class ImmortalObject; + class ImmortalString; + + template + class _OwnedGreenlet; + + typedef _OwnedGreenlet OwnedGreenlet; + typedef _OwnedGreenlet OwnedMainGreenlet; + + template + class _BorrowedGreenlet; + + typedef _BorrowedGreenlet BorrowedGreenlet; + + void + ContextExactChecker(void *p) + { + if (!p) { + return; + } + if (!PyContext_CheckExact(p)) { + throw TypeError( + "greenlet context must be a contextvars.Context or None" + ); + } + } + + typedef OwnedReference OwnedContext; + } +} + +namespace greenlet { + + + namespace refs { + // A set of classes to make reference counting rules in python + // code explicit. + // + // Rules of use: + // (1) Functions returning a new reference that the caller of the + // function is expected to dispose of should return a + // ``OwnedObject`` object. This object automatically releases its + // reference when it goes out of scope. It works like a ``std::shared_ptr`` + // and can be copied or used as a function parameter (but don't do + // that). Note that constructing a ``OwnedObject`` from a + // PyObject* steals the reference. + // (2) Parameters to functions should be either a + // ``OwnedObject&``, or, more generally, a ``PyObjectPointer&``. + // If the function needs to create its own new reference, it can + // do so by copying to a local ``OwnedObject``. + // (3) Functions returning an existing pointer that is NOT + // incref'd, and which the caller MUST NOT decref, + // should return a ``BorrowedObject``. + + // XXX: The following two paragraphs do not hold for all platforms. + // Notably, 32-bit PPC Linux passes structs by reference, not by + // value, so this actually doesn't work. (Although that's the only + // platform that doesn't work on.) DO NOT ATTEMPT IT. The + // unfortunate consequence of that is that the slots which we + // *know* are already type safe will wind up calling the type + // checker function (when we had the slots accepting + // BorrowedGreenlet, this was bypassed), so this slows us down. + // TODO: Optimize this again. + + // For a class with a single pointer member, whose constructor + // does nothing but copy a pointer parameter into the member, and + // which can then be converted back to the pointer type, compilers + // generate code that's the same as just passing the pointer. + // That is, func(BorrowedObject x) called like ``PyObject* p = + // ...; f(p)`` has 0 overhead. Similarly, they "unpack" to the + // pointer type with 0 overhead. + // + // If there are no virtual functions, no complex inheritance (maybe?) and + // no destructor, these can be directly used as parameters in + // Python callbacks like tp_init: the layout is the same as a + // single pointer. Only subclasses with trivial constructors that + // do nothing but set the single pointer member are safe to use + // that way. + + + // This is the base class for things that can be done with a + // PyObject pointer. It assumes nothing about memory management. + // NOTE: Nothing is virtual, so subclasses shouldn't add new + // storage fields or try to override these methods. + template + class PyObjectPointer + { + public: + typedef T PyType; + protected: + T* p; + public: + PyObjectPointer(T* it=nullptr) : p(it) + { + TC(p); + } + + // We don't allow automatic casting to PyObject* at this + // level, because then we could be passed to Py_DECREF/INCREF, + // but we want nothing to do with memory management. If you + // know better, then you can use the get() method, like on a + // std::shared_ptr. Except we name it borrow() to clarify that + // if this is a reference-tracked object, the pointer you get + // back will go away when the object does. + // TODO: This should probably not exist here, but be moved + // down to relevant sub-types. + + T* borrow() const noexcept + { + return this->p; + } + + PyObject* borrow_o() const noexcept + { + return reinterpret_cast(this->p); + } + + T* operator->() const noexcept + { + return this->p; + } + + bool is_None() const noexcept + { + return this->p == Py_None; + } + + PyObject* acquire_or_None() const noexcept + { + PyObject* result = this->p ? reinterpret_cast(this->p) : Py_None; + Py_INCREF(result); + return result; + } + + explicit operator bool() const noexcept + { + return this->p != nullptr; + } + + bool operator!() const noexcept + { + return this->p == nullptr; + } + + Py_ssize_t REFCNT() const noexcept + { + return p ? Py_REFCNT(p) : -42; + } + + PyTypeObject* TYPE() const noexcept + { + return p ? Py_TYPE(p) : nullptr; + } + + inline OwnedObject PyStr() const noexcept; + inline const std::string as_str() const noexcept; + inline OwnedObject PyGetAttr(const ImmortalObject& name) const noexcept; + inline OwnedObject PyRequireAttr(const char* const name) const; + inline OwnedObject PyRequireAttr(const ImmortalString& name) const; + inline OwnedObject PyCall(const BorrowedObject& arg) const; + inline OwnedObject PyCall(PyGreenlet* arg) const ; + inline OwnedObject PyCall(PyObject* arg) const ; + // PyObject_Call(this, args, kwargs); + inline OwnedObject PyCall(const BorrowedObject args, + const BorrowedObject kwargs) const; + inline OwnedObject PyCall(const OwnedObject& args, + const OwnedObject& kwargs) const; + + protected: + void _set_raw_pointer(void* t) + { + TC(t); + p = reinterpret_cast(t); + } + void* _get_raw_pointer() const + { + return p; + } + }; + +#ifdef GREENLET_USE_STDIO + template + std::ostream& operator<<(std::ostream& os, const PyObjectPointer& s) + { + const std::type_info& t = typeid(s); + os << t.name() + << "(addr=" << s.borrow() + << ", refcnt=" << s.REFCNT() + << ", value=" << s.as_str() + << ")"; + + return os; + } +#endif + + template + inline bool operator==(const PyObjectPointer& lhs, const PyObject* const rhs) noexcept + { + return static_cast(lhs.borrow_o()) == static_cast(rhs); + } + + template + inline bool operator==(const PyObjectPointer& lhs, const PyObjectPointer& rhs) noexcept + { + return lhs.borrow_o() == rhs.borrow_o(); + } + + template + inline bool operator!=(const PyObjectPointer& lhs, + const PyObjectPointer& rhs) noexcept + { + return lhs.borrow_o() != rhs.borrow_o(); + } + + template + class OwnedReference : public PyObjectPointer + { + private: + friend class OwnedList; + + protected: + explicit OwnedReference(T* it) : PyObjectPointer(it) + { + } + + public: + + // Constructors + + static OwnedReference consuming(PyObject* p) + { + return OwnedReference(reinterpret_cast(p)); + } + + static OwnedReference owning(T* p) + { + OwnedReference result(p); + Py_XINCREF(result.p); + return result; + } + + OwnedReference() : PyObjectPointer(nullptr) + {} + + explicit OwnedReference(const PyObjectPointer<>& other) + : PyObjectPointer(nullptr) + { + T* op = other.borrow(); + TC(op); + this->p = other.borrow(); + Py_XINCREF(this->p); + } + + // It would be good to make use of the C++11 distinction + // between move and copy operations, e.g., constructing from a + // pointer should be a move operation. + // In the common case of ``OwnedObject x = Py_SomeFunction()``, + // the call to the copy constructor will be elided completely. + OwnedReference(const OwnedReference& other) + : PyObjectPointer(other.p) + { + Py_XINCREF(this->p); + } + + static OwnedReference None() + { + Py_INCREF(Py_None); + return OwnedReference(Py_None); + } + + // We can assign from exactly our type without any extra checking + OwnedReference& operator=(const OwnedReference& other) + { + Py_XINCREF(other.p); + const T* tmp = this->p; + this->p = other.p; + Py_XDECREF(tmp); + return *this; + } + + OwnedReference& operator=(const BorrowedReference other) + { + return this->operator=(other.borrow()); + } + + OwnedReference& operator=(T* const other) + { + TC(other); + Py_XINCREF(other); + T* tmp = this->p; + this->p = other; + Py_XDECREF(tmp); + return *this; + } + + // We can assign from an arbitrary reference type + // if it passes our check. + template + OwnedReference& operator=(const OwnedReference& other) + { + X* op = other.borrow(); + TC(op); + return this->operator=(reinterpret_cast(op)); + } + + inline void steal(T* other) + { + assert(this->p == nullptr); + TC(other); + this->p = other; + } + + T* relinquish_ownership() + { + T* result = this->p; + this->p = nullptr; + return result; + } + + T* acquire() const + { + // Return a new reference. + // TODO: This may go away when we have reference objects + // throughout the code. + Py_XINCREF(this->p); + return this->p; + } + + // Nothing else declares a destructor, we're the leaf, so we + // should be able to get away without virtual. + ~OwnedReference() + { + Py_CLEAR(this->p); + } + + void CLEAR() + { + Py_CLEAR(this->p); + assert(this->p == nullptr); + } + }; + + static inline + void operator<<=(PyObject*& target, OwnedObject& o) + { + target = o.relinquish_ownership(); + } + + + class NewReference : public OwnedObject + { + private: + G_NO_COPIES_OF_CLS(NewReference); + public: + // Consumes the reference. Only use this + // for API return values. + NewReference(PyObject* it) : OwnedObject(it) + { + } + }; + + class NewDictReference : public NewReference + { + private: + G_NO_COPIES_OF_CLS(NewDictReference); + public: + NewDictReference() : NewReference(PyDict_New()) + { + if (!this->p) { + throw PyErrOccurred(); + } + } + + void SetItem(const char* const key, PyObject* value) + { + Require(PyDict_SetItemString(this->p, key, value)); + } + + void SetItem(const PyObjectPointer<>& key, PyObject* value) + { + Require(PyDict_SetItem(this->p, key.borrow_o(), value)); + } + }; + + template + class _OwnedGreenlet: public OwnedReference + { + private: + protected: + _OwnedGreenlet(T* it) : OwnedReference(it) + {} + + public: + _OwnedGreenlet() : OwnedReference() + {} + + _OwnedGreenlet(const _OwnedGreenlet& other) : OwnedReference(other) + { + } + _OwnedGreenlet(OwnedMainGreenlet& other) : + OwnedReference(reinterpret_cast(other.acquire())) + { + } + _OwnedGreenlet(const BorrowedGreenlet& other); + // Steals a reference. + static _OwnedGreenlet consuming(PyGreenlet* it) + { + return _OwnedGreenlet(reinterpret_cast(it)); + } + + inline _OwnedGreenlet& operator=(const OwnedGreenlet& other) + { + return this->operator=(other.borrow()); + } + + inline _OwnedGreenlet& operator=(const BorrowedGreenlet& other); + + _OwnedGreenlet& operator=(const OwnedMainGreenlet& other) + { + PyGreenlet* owned = other.acquire(); + Py_XDECREF(this->p); + this->p = reinterpret_cast(owned); + return *this; + } + + _OwnedGreenlet& operator=(T* const other) + { + OwnedReference::operator=(other); + return *this; + } + + T* relinquish_ownership() + { + T* result = this->p; + this->p = nullptr; + return result; + } + + PyObject* relinquish_ownership_o() + { + return reinterpret_cast(relinquish_ownership()); + } + + inline Greenlet* operator->() const noexcept; + inline operator Greenlet*() const noexcept; + }; + + template + class BorrowedReference : public PyObjectPointer + { + public: + // Allow implicit creation from PyObject* pointers as we + // transition to using these classes. Also allow automatic + // conversion to PyObject* for passing to C API calls and even + // for Py_INCREF/DECREF, because we ourselves do no memory management. + BorrowedReference(T* it) : PyObjectPointer(it) + {} + + BorrowedReference(const PyObjectPointer& ref) : PyObjectPointer(ref.borrow()) + {} + + BorrowedReference() : PyObjectPointer(nullptr) + {} + + operator T*() const + { + return this->p; + } + }; + + typedef BorrowedReference BorrowedObject; + //typedef BorrowedReference BorrowedGreenlet; + + template + class _BorrowedGreenlet : public BorrowedReference + { + public: + _BorrowedGreenlet() : + BorrowedReference(nullptr) + {} + + _BorrowedGreenlet(T* it) : + BorrowedReference(it) + {} + + _BorrowedGreenlet(const BorrowedObject& it); + + _BorrowedGreenlet(const OwnedGreenlet& it) : + BorrowedReference(it.borrow()) + {} + + _BorrowedGreenlet& operator=(const BorrowedObject& other); + + // We get one of these for PyGreenlet, but one for PyObject + // is handy as well + operator PyObject*() const + { + return reinterpret_cast(this->p); + } + Greenlet* operator->() const noexcept; + operator Greenlet*() const noexcept; + }; + + typedef _BorrowedGreenlet BorrowedGreenlet; + + template + _OwnedGreenlet::_OwnedGreenlet(const BorrowedGreenlet& other) + : OwnedReference(reinterpret_cast(other.borrow())) + { + Py_XINCREF(this->p); + } + + + class BorrowedMainGreenlet + : public _BorrowedGreenlet + { + public: + BorrowedMainGreenlet(const OwnedMainGreenlet& it) : + _BorrowedGreenlet(it.borrow()) + {} + BorrowedMainGreenlet(PyGreenlet* it=nullptr) + : _BorrowedGreenlet(it) + {} + }; + + template + _OwnedGreenlet& _OwnedGreenlet::operator=(const BorrowedGreenlet& other) + { + return this->operator=(other.borrow()); + } + + + class ImmortalObject : public PyObjectPointer<> + { + private: + G_NO_ASSIGNMENT_OF_CLS(ImmortalObject); + public: + explicit ImmortalObject(PyObject* it) : PyObjectPointer<>(it) + { + } + + ImmortalObject(const ImmortalObject& other) + : PyObjectPointer<>(other.p) + { + + } + + /** + * Become the new owner of the object. Does not change the + * reference count. + */ + ImmortalObject& operator=(PyObject* it) + { + assert(this->p == nullptr); + this->p = it; + return *this; + } + + static ImmortalObject consuming(PyObject* it) + { + return ImmortalObject(it); + } + + inline operator PyObject*() const + { + return this->p; + } + }; + + class ImmortalString : public ImmortalObject + { + private: + G_NO_COPIES_OF_CLS(ImmortalString); + const char* str; + public: + ImmortalString(const char* const str) : + ImmortalObject(str ? Require(PyUnicode_InternFromString(str)) : nullptr) + { + this->str = str; + } + + inline ImmortalString& operator=(const char* const str) + { + if (!this->p) { + this->p = Require(PyUnicode_InternFromString(str)); + this->str = str; + } + else { + assert(this->str == str); + } + return *this; + } + + inline operator std::string() const + { + return this->str; + } + + }; + + class ImmortalEventName : public ImmortalString + { + private: + G_NO_COPIES_OF_CLS(ImmortalEventName); + public: + ImmortalEventName(const char* const str) : ImmortalString(str) + {} + }; + + class ImmortalException : public ImmortalObject + { + private: + G_NO_COPIES_OF_CLS(ImmortalException); + public: + ImmortalException(const char* const name, PyObject* base=nullptr) : + ImmortalObject(name + // Python 2.7 isn't const correct + ? Require(PyErr_NewException((char*)name, base, nullptr)) + : nullptr) + {} + + inline bool PyExceptionMatches() const + { + return PyErr_ExceptionMatches(this->p) > 0; + } + + }; + + template + inline OwnedObject PyObjectPointer::PyStr() const noexcept + { + if (!this->p) { + return OwnedObject(); + } + return OwnedObject::consuming(PyObject_Str(reinterpret_cast(this->p))); + } + + template + inline const std::string PyObjectPointer::as_str() const noexcept + { + // NOTE: This is not Python exception safe. + if (this->p) { + // The Python APIs return a cached char* value that's only valid + // as long as the original object stays around, and we're + // about to (probably) toss it. Hence the copy to std::string. + OwnedObject py_str = this->PyStr(); + if (!py_str) { + return "(nil)"; + } + return PyUnicode_AsUTF8(py_str.borrow()); + } + return "(nil)"; + } + + template + inline OwnedObject PyObjectPointer::PyGetAttr(const ImmortalObject& name) const noexcept + { + assert(this->p); + return OwnedObject::consuming(PyObject_GetAttr(reinterpret_cast(this->p), name)); + } + + template + inline OwnedObject PyObjectPointer::PyRequireAttr(const char* const name) const + { + assert(this->p); + return OwnedObject::consuming(Require(PyObject_GetAttrString(this->p, name), name)); + } + + template + inline OwnedObject PyObjectPointer::PyRequireAttr(const ImmortalString& name) const + { + assert(this->p); + return OwnedObject::consuming(Require( + PyObject_GetAttr( + reinterpret_cast(this->p), + name + ), + name + )); + } + + template + inline OwnedObject PyObjectPointer::PyCall(const BorrowedObject& arg) const + { + return this->PyCall(arg.borrow()); + } + + template + inline OwnedObject PyObjectPointer::PyCall(PyGreenlet* arg) const + { + return this->PyCall(reinterpret_cast(arg)); + } + + template + inline OwnedObject PyObjectPointer::PyCall(PyObject* arg) const + { + assert(this->p); + return OwnedObject::consuming(PyObject_CallFunctionObjArgs(this->p, arg, NULL)); + } + + template + inline OwnedObject PyObjectPointer::PyCall(const BorrowedObject args, + const BorrowedObject kwargs) const + { + assert(this->p); + return OwnedObject::consuming(PyObject_Call(this->p, args, kwargs)); + } + + template + inline OwnedObject PyObjectPointer::PyCall(const OwnedObject& args, + const OwnedObject& kwargs) const + { + assert(this->p); + return OwnedObject::consuming(PyObject_Call(this->p, args.borrow(), kwargs.borrow())); + } + + inline void + ListChecker(void * p) + { + if (!p) { + return; + } + if (!PyList_Check(p)) { + throw TypeError("Expected a list"); + } + } + + class OwnedList : public OwnedReference + { + private: + G_NO_ASSIGNMENT_OF_CLS(OwnedList); + public: + // TODO: Would like to use move. + explicit OwnedList(const OwnedObject& other) + : OwnedReference(other) + { + } + + OwnedList& operator=(const OwnedObject& other) + { + if (other && PyList_Check(other.p)) { + // Valid list. Own a new reference to it, discard the + // reference to what we did own. + PyObject* new_ptr = other.p; + Py_INCREF(new_ptr); + Py_XDECREF(this->p); + this->p = new_ptr; + } + else { + // Either the other object was NULL (an error) or it + // wasn't a list. Either way, we're now invalidated. + Py_XDECREF(this->p); + this->p = nullptr; + } + return *this; + } + + inline bool empty() const + { + return PyList_GET_SIZE(p) == 0; + } + + inline Py_ssize_t size() const + { + return PyList_GET_SIZE(p); + } + + inline BorrowedObject at(const Py_ssize_t index) const + { + return PyList_GET_ITEM(p, index); + } + + inline void clear() + { + PyList_SetSlice(p, 0, PyList_GET_SIZE(p), NULL); + } + }; + + // Use this to represent the module object used at module init + // time. + // This could either be a borrowed (Py2) or new (Py3) reference; + // either way, we don't want to do any memory management + // on it here, Python itself will handle that. + // XXX: Actually, that's not quite right. On Python 3, if an + // exception occurs before we return to the interpreter, this will + // leak; but all previous versions also had that problem. + class CreatedModule : public PyObjectPointer<> + { + private: + G_NO_COPIES_OF_CLS(CreatedModule); + public: + CreatedModule(PyModuleDef& mod_def) : PyObjectPointer<>( + Require(PyModule_Create(&mod_def))) + { + } + + // PyAddObject(): Add a reference to the object to the module. + // On return, the reference count of the object is unchanged. + // + // The docs warn that PyModule_AddObject only steals the + // reference on success, so if it fails after we've incref'd + // or allocated, we're responsible for the decref. + void PyAddObject(const char* name, const long new_bool) + { + OwnedObject p = OwnedObject::consuming(Require(PyBool_FromLong(new_bool))); + this->PyAddObject(name, p); + } + + void PyAddObject(const char* name, const OwnedObject& new_object) + { + // The caller already owns a reference they will decref + // when their variable goes out of scope, we still need to + // incref/decref. + this->PyAddObject(name, new_object.borrow()); + } + + void PyAddObject(const char* name, const ImmortalObject& new_object) + { + this->PyAddObject(name, new_object.borrow()); + } + + void PyAddObject(const char* name, PyTypeObject& type) + { + this->PyAddObject(name, reinterpret_cast(&type)); + } + + void PyAddObject(const char* name, PyObject* new_object) + { + Py_INCREF(new_object); + try { + Require(PyModule_AddObject(this->p, name, new_object)); + } + catch (const PyErrOccurred&) { + Py_DECREF(p); + throw; + } + } + }; + + class PyErrFetchParam : public PyObjectPointer<> + { + // Not an owned object, because we can't be initialized with + // one, and we only sometimes acquire ownership. + private: + G_NO_COPIES_OF_CLS(PyErrFetchParam); + public: + // To allow declaring these and passing them to + // PyErr_Fetch we implement the empty constructor, + // and the address operator. + PyErrFetchParam() : PyObjectPointer<>(nullptr) + { + } + + PyObject** operator&() + { + return &this->p; + } + + // This allows us to pass one directly without the &, + // BUT it has higher precedence than the bool operator + // if it's not explicit. + operator PyObject**() + { + return &this->p; + } + + // We don't want to be able to pass these to Py_DECREF and + // such so we don't have the implicit PyObject* conversion. + + inline PyObject* relinquish_ownership() + { + PyObject* result = this->p; + this->p = nullptr; + return result; + } + + ~PyErrFetchParam() + { + Py_XDECREF(p); + } + }; + + class OwnedErrPiece : public OwnedObject + { + private: + + public: + // Unlike OwnedObject, this increments the refcount. + OwnedErrPiece(PyObject* p=nullptr) : OwnedObject(p) + { + this->acquire(); + } + + PyObject** operator&() + { + return &this->p; + } + + inline operator PyObject*() const + { + return this->p; + } + + operator PyTypeObject*() const + { + return reinterpret_cast(this->p); + } + }; + + class PyErrPieces + { + private: + OwnedErrPiece type; + OwnedErrPiece instance; + OwnedErrPiece traceback; + bool restored; + public: + // Takes new references; if we're destroyed before + // restoring the error, we drop the references. + PyErrPieces(PyObject* t, PyObject* v, PyObject* tb) : + type(t), + instance(v), + traceback(tb), + restored(0) + { + this->normalize(); + } + + PyErrPieces() : + restored(0) + { + // PyErr_Fetch transfers ownership to us, so + // we don't actually need to INCREF; but we *do* + // need to DECREF if we're not restored. + PyErrFetchParam t, v, tb; + PyErr_Fetch(&t, &v, &tb); + type.steal(t.relinquish_ownership()); + instance.steal(v.relinquish_ownership()); + traceback.steal(tb.relinquish_ownership()); + } + + void PyErrRestore() + { + // can only do this once + assert(!this->restored); + this->restored = true; + PyErr_Restore( + this->type.relinquish_ownership(), + this->instance.relinquish_ownership(), + this->traceback.relinquish_ownership()); + assert(!this->type && !this->instance && !this->traceback); + } + + private: + void normalize() + { + // First, check the traceback argument, replacing None, + // with NULL + if (traceback.is_None()) { + traceback = nullptr; + } + + if (traceback && !PyTraceBack_Check(traceback.borrow())) { + throw PyErrOccurred(PyExc_TypeError, + "throw() third argument must be a traceback object"); + } + + if (PyExceptionClass_Check(type)) { + // If we just had a type, we'll now have a type and + // instance. + // The type's refcount will have gone up by one + // because of the instance and the instance will have + // a refcount of one. Either way, we owned, and still + // do own, exactly one reference. + PyErr_NormalizeException(&type, &instance, &traceback); + + } + else if (PyExceptionInstance_Check(type)) { + /* Raising an instance --- usually that means an + object that is a subclass of BaseException, but on + Python 2, that can also mean an arbitrary old-style + object. The value should be a dummy. */ + if (instance && !instance.is_None()) { + throw PyErrOccurred( + PyExc_TypeError, + "instance exception may not have a separate value"); + } + /* Normalize to raise , */ + this->instance = this->type; + this->type = PyExceptionInstance_Class(instance.borrow()); + + /* + It would be tempting to do this: + + Py_ssize_t type_count = Py_REFCNT(Py_TYPE(instance.borrow())); + this->type = PyExceptionInstance_Class(instance.borrow()); + assert(this->type.REFCNT() == type_count + 1); + + But that doesn't work on Python 2 in the case of + old-style instances: The result of Py_TYPE is going to + be the global shared that all + old-style classes have, while the return of Instance_Class() + will be the Python-level class object. The two are unrelated. + */ + } + else { + /* Not something you can raise. throw() fails. */ + PyErr_Format(PyExc_TypeError, + "exceptions must be classes, or instances, not %s", + Py_TYPE(type.borrow())->tp_name); + throw PyErrOccurred(); + } + } + }; + + // PyArg_Parse's O argument returns a borrowed reference. + class PyArgParseParam : public BorrowedObject + { + private: + G_NO_COPIES_OF_CLS(PyArgParseParam); + public: + explicit PyArgParseParam(PyObject* p=nullptr) : BorrowedObject(p) + { + } + + inline PyObject** operator&() + { + return &this->p; + } + }; + +};}; + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/greenlet_slp_switch.hpp b/venv/lib/python3.12/site-packages/greenlet/greenlet_slp_switch.hpp new file mode 100644 index 0000000..bd4b7ae --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/greenlet_slp_switch.hpp @@ -0,0 +1,99 @@ +#ifndef GREENLET_SLP_SWITCH_HPP +#define GREENLET_SLP_SWITCH_HPP + +#include "greenlet_compiler_compat.hpp" +#include "greenlet_refs.hpp" + +/* + * the following macros are spliced into the OS/compiler + * specific code, in order to simplify maintenance. + */ +// We can save about 10% of the time it takes to switch greenlets if +// we thread the thread state through the slp_save_state() and the +// following slp_restore_state() calls from +// slp_switch()->g_switchstack() (which already needs to access it). +// +// However: +// +// that requires changing the prototypes and implementations of the +// switching functions. If we just change the prototype of +// slp_switch() to accept the argument and update the macros, without +// changing the implementation of slp_switch(), we get crashes on +// 64-bit Linux and 32-bit x86 (for reasons that aren't 100% clear); +// on the other hand, 64-bit macOS seems to be fine. Also, 64-bit +// windows is an issue because slp_switch is written fully in assembly +// and currently ignores its argument so some code would have to be +// adjusted there to pass the argument on to the +// ``slp_save_state_asm()`` function (but interestingly, because of +// the calling convention, the extra argument is just ignored and +// things function fine, albeit slower, if we just modify +// ``slp_save_state_asm`()` to fetch the pointer to pass to the +// macro.) +// +// Our compromise is to use a *glabal*, untracked, weak, pointer +// to the necessary thread state during the process of switching only. +// This is safe because we're protected by the GIL, and if we're +// running this code, the thread isn't exiting. This also nets us a +// 10-12% speed improvement. + +static greenlet::Greenlet* volatile switching_thread_state = nullptr; + + +extern "C" { +static int GREENLET_NOINLINE(slp_save_state_trampoline)(char* stackref); +static void GREENLET_NOINLINE(slp_restore_state_trampoline)(); +} + + +#define SLP_SAVE_STATE(stackref, stsizediff) \ +do { \ + assert(switching_thread_state); \ + stackref += STACK_MAGIC; \ + if (slp_save_state_trampoline((char*)stackref)) \ + return -1; \ + if (!switching_thread_state->active()) \ + return 1; \ + stsizediff = switching_thread_state->stack_start() - (char*)stackref; \ +} while (0) + +#define SLP_RESTORE_STATE() slp_restore_state_trampoline() + +#define SLP_EVAL +extern "C" { +#define slp_switch GREENLET_NOINLINE(slp_switch) +#include "slp_platformselect.h" +} +#undef slp_switch + +#ifndef STACK_MAGIC +# error \ + "greenlet needs to be ported to this platform, or taught how to detect your compiler properly." +#endif /* !STACK_MAGIC */ + + + +#ifdef EXTERNAL_ASM +/* CCP addition: Make these functions, to be called from assembler. + * The token include file for the given platform should enable the + * EXTERNAL_ASM define so that this is included. + */ +extern "C" { +intptr_t +slp_save_state_asm(intptr_t* ref) +{ + intptr_t diff; + SLP_SAVE_STATE(ref, diff); + return diff; +} + +void +slp_restore_state_asm(void) +{ + SLP_RESTORE_STATE(); +} + +extern int slp_switch(void); +}; +#endif + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/greenlet_thread_support.hpp b/venv/lib/python3.12/site-packages/greenlet/greenlet_thread_support.hpp new file mode 100644 index 0000000..3ded7d2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/greenlet_thread_support.hpp @@ -0,0 +1,31 @@ +#ifndef GREENLET_THREAD_SUPPORT_HPP +#define GREENLET_THREAD_SUPPORT_HPP + +/** + * Defines various utility functions to help greenlet integrate well + * with threads. This used to be needed when we supported Python + * 2.7 on Windows, which used a very old compiler. We wrote an + * alternative implementation using Python APIs and POSIX or Windows + * APIs, but that's no longer needed. So this file is a shadow of its + * former self --- but may be needed in the future. + */ + +#include +#include +#include + +#include "greenlet_compiler_compat.hpp" + +namespace greenlet { + typedef std::mutex Mutex; + typedef std::lock_guard LockGuard; + class LockInitError : public std::runtime_error + { + public: + LockInitError(const char* what) : std::runtime_error(what) + {}; + }; +}; + + +#endif /* GREENLET_THREAD_SUPPORT_HPP */ diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/__init__.py b/venv/lib/python3.12/site-packages/greenlet/platform/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/platform/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..060dd437c138a8281e4ee31b92918e710437f80d GIT binary patch literal 179 zcmX@j%ge<81g8$&%K*`jK?FMZ%mNgd&QQsq$>_I|p@<2{`wUX^D@Z>hKQ~oBIX@?< zQa?E(v81F@zbrMcOg|?xNxz`7BqKl1SkKT%zc{lbRkt89IXf{uwOBvBC^a=NC$&Vs zASbaTEx#yNKR!M)FS8^*Uaz3?7l%!5eoARhs$CH)& + * Add support for strange GCC caller-save decisions + * (ported from switch_aarch64_gcc.h) + * 18-Aug-11 Alexey Borzenkov + * Correctly save rbp, csr and cw + * 01-Apr-04 Hye-Shik Chang + * Ported from i386 to amd64. + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for spark + * 31-Avr-02 Armin Rigo + * Added ebx, esi and edi register-saves. + * 01-Mar-02 Samual M. Rushing + * Ported from i386. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +/* #define STACK_MAGIC 3 */ +/* the above works fine with gcc 2.96, but 2.95.3 wants this */ +#define STACK_MAGIC 0 + +#define REGS_TO_SAVE "r12", "r13", "r14", "r15" + +static int +slp_switch(void) +{ + int err; + void* rbp; + void* rbx; + unsigned int csr; + unsigned short cw; + /* This used to be declared 'register', but that does nothing in + modern compilers and is explicitly forbidden in some new + standards. */ + long *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("fstcw %0" : "=m" (cw)); + __asm__ volatile ("stmxcsr %0" : "=m" (csr)); + __asm__ volatile ("movq %%rbp, %0" : "=m" (rbp)); + __asm__ volatile ("movq %%rbx, %0" : "=m" (rbx)); + __asm__ ("movq %%rsp, %0" : "=g" (stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "addq %0, %%rsp\n" + "addq %0, %%rbp\n" + : + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + __asm__ volatile ("xorq %%rax, %%rax" : "=a" (err)); + } + __asm__ volatile ("movq %0, %%rbx" : : "m" (rbx)); + __asm__ volatile ("movq %0, %%rbp" : : "m" (rbp)); + __asm__ volatile ("ldmxcsr %0" : : "m" (csr)); + __asm__ volatile ("fldcw %0" : : "m" (cw)); + __asm__ volatile ("" : : : REGS_TO_SAVE); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm32_gcc.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm32_gcc.h new file mode 100644 index 0000000..655003a --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm32_gcc.h @@ -0,0 +1,79 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 14-Aug-06 File creation. Ported from Arm Thumb. Sylvain Baro + * 3-Sep-06 Commented out saving of r1-r3 (r4 already commented out) as I + * read that these do not need to be saved. Also added notes and + * errors related to the frame pointer. Richard Tew. + * + * NOTES + * + * It is not possible to detect if fp is used or not, so the supplied + * switch function needs to support it, so that you can remove it if + * it does not apply to you. + * + * POSSIBLE ERRORS + * + * "fp cannot be used in asm here" + * + * - Try commenting out "fp" in REGS_TO_SAVE. + * + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL +#define STACK_MAGIC 0 +#define REG_SP "sp" +#define REG_SPSP "sp,sp" +#ifdef __thumb__ +#define REG_FP "r7" +#define REG_FPFP "r7,r7" +#define REGS_TO_SAVE_GENERAL "r4", "r5", "r6", "r8", "r9", "r10", "r11", "lr" +#else +#define REG_FP "fp" +#define REG_FPFP "fp,fp" +#define REGS_TO_SAVE_GENERAL "r4", "r5", "r6", "r7", "r8", "r9", "r10", "lr" +#endif +#if defined(__SOFTFP__) +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL +#elif defined(__VFP_FP__) +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "d8", "d9", "d10", "d11", \ + "d12", "d13", "d14", "d15" +#elif defined(__MAVERICK__) +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "mvf4", "mvf5", "mvf6", "mvf7", \ + "mvf8", "mvf9", "mvf10", "mvf11", \ + "mvf12", "mvf13", "mvf14", "mvf15" +#else +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "f4", "f5", "f6", "f7" +#endif + +static int +#ifdef __GNUC__ +__attribute__((optimize("no-omit-frame-pointer"))) +#endif +slp_switch(void) +{ + void *fp; + int *stackref, stsizediff; + int result; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("mov r0," REG_FP "\n\tstr r0,%0" : "=m" (fp) : : "r0"); + __asm__ ("mov %0," REG_SP : "=r" (stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "add " REG_SPSP ",%0\n" + "add " REG_FPFP ",%0\n" + : + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("ldr r0,%1\n\tmov " REG_FP ",r0\n\tmov %0, #0" : "=r" (result) : "m" (fp) : "r0"); + __asm__ volatile ("" : : : REGS_TO_SAVE); + return result; +} + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm32_ios.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm32_ios.h new file mode 100644 index 0000000..9e640e1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm32_ios.h @@ -0,0 +1,67 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 31-May-15 iOS support. Ported from arm32. Proton + * + * NOTES + * + * It is not possible to detect if fp is used or not, so the supplied + * switch function needs to support it, so that you can remove it if + * it does not apply to you. + * + * POSSIBLE ERRORS + * + * "fp cannot be used in asm here" + * + * - Try commenting out "fp" in REGS_TO_SAVE. + * + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 0 +#define REG_SP "sp" +#define REG_SPSP "sp,sp" +#define REG_FP "r7" +#define REG_FPFP "r7,r7" +#define REGS_TO_SAVE_GENERAL "r4", "r5", "r6", "r8", "r10", "r11", "lr" +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "d8", "d9", "d10", "d11", \ + "d12", "d13", "d14", "d15" + +static int +#ifdef __GNUC__ +__attribute__((optimize("no-omit-frame-pointer"))) +#endif +slp_switch(void) +{ + void *fp; + int *stackref, stsizediff, result; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("str " REG_FP ",%0" : "=m" (fp)); + __asm__ ("mov %0," REG_SP : "=r" (stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "add " REG_SPSP ",%0\n" + "add " REG_FPFP ",%0\n" + : + : "r" (stsizediff) + : REGS_TO_SAVE /* Clobber registers, force compiler to + * recalculate address of void *fp from REG_SP or REG_FP */ + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ( + "ldr " REG_FP ", %1\n\t" + "mov %0, #0" + : "=r" (result) + : "m" (fp) + : REGS_TO_SAVE /* Force compiler to restore saved registers after this */ + ); + return result; +} + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_masm.asm b/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_masm.asm new file mode 100644 index 0000000..29f9c22 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_masm.asm @@ -0,0 +1,53 @@ + AREA switch_arm64_masm, CODE, READONLY; + GLOBAL slp_switch [FUNC] + EXTERN slp_save_state_asm + EXTERN slp_restore_state_asm + +slp_switch + ; push callee saved registers to stack + stp x19, x20, [sp, #-16]! + stp x21, x22, [sp, #-16]! + stp x23, x24, [sp, #-16]! + stp x25, x26, [sp, #-16]! + stp x27, x28, [sp, #-16]! + stp x29, x30, [sp, #-16]! + stp d8, d9, [sp, #-16]! + stp d10, d11, [sp, #-16]! + stp d12, d13, [sp, #-16]! + stp d14, d15, [sp, #-16]! + + ; call slp_save_state_asm with stack pointer + mov x0, sp + bl slp_save_state_asm + + ; early return for return value of 1 and -1 + cmp x0, #-1 + b.eq RETURN + cmp x0, #1 + b.eq RETURN + + ; increment stack and frame pointer + add sp, sp, x0 + add x29, x29, x0 + + bl slp_restore_state_asm + + ; store return value for successful completion of routine + mov x0, #0 + +RETURN + ; pop registers from stack + ldp d14, d15, [sp], #16 + ldp d12, d13, [sp], #16 + ldp d10, d11, [sp], #16 + ldp d8, d9, [sp], #16 + ldp x29, x30, [sp], #16 + ldp x27, x28, [sp], #16 + ldp x25, x26, [sp], #16 + ldp x23, x24, [sp], #16 + ldp x21, x22, [sp], #16 + ldp x19, x20, [sp], #16 + + ret + + END diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_masm.obj b/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_masm.obj new file mode 100644 index 0000000000000000000000000000000000000000..f6f220e4310baaa9756110685ce7d6a2bdf90c37 GIT binary patch literal 746 zcma)4PiqrF6n~qoo~*PNZ{i+=wji4b#Xu2~wiJpGk)*AMF07NyB(9n1#+i+!)I;v| zBKQG3?t1eB$T(lYgGb4+lu{_QmQrebldQB_4?cMF-uu0IZ{DA2e8@rZ+e^~30488W z`B{KPh%yV{HEIpyeum^wI#7P*HfX)ux?9U&c!SFK-$o|OFtKn{Q|a-#N>2inp0-tb zCRKXAtg2SolaoLv$Ll&ds_Epj?SH+8K{t?XSjKaFsEy%yh}=V70BaHj zEY5kWk_zcJo{$SSUL~=K(zW|tnhm$rA z<%dZ$q?>RX*18r{!azhaYR1lVb;g;mR-6h!#F>|p@;aje%0a|CZrE7s+SXuT>MS=Y ziQPiMY-5DD&5+S7^H03fy7qt7ir}L34kK|h68s-MU>{lXOqlr?!Y=`~WwviNenFS_ zZalVSHh-0FU4lj#X8u5`ODn6@#{f?dHE&%XdP~_I3;$RS9-(z*>>ydkm*f@oWlUn~ Qn+^;lsEi}=H#!Q3U&UU-WdHyG literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_msvc.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_msvc.h new file mode 100644 index 0000000..7ab7f45 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_msvc.h @@ -0,0 +1,17 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 21-Oct-21 Niyas Sait + * First version to enable win/arm64 support. + */ + +#define STACK_REFPLUS 1 +#define STACK_MAGIC 0 + +/* Use the generic support for an external assembly language slp_switch function. */ +#define EXTERNAL_ASM + +#ifdef SLP_EVAL +/* This always uses the external masm assembly file. */ +#endif \ No newline at end of file diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_csky_gcc.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_csky_gcc.h new file mode 100644 index 0000000..ac469d3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_csky_gcc.h @@ -0,0 +1,48 @@ +#ifdef SLP_EVAL +#define STACK_MAGIC 0 +#define REG_FP "r8" +#ifdef __CSKYABIV2__ +#define REGS_TO_SAVE_GENERAL "r4", "r5", "r6", "r7", "r9", "r10", "r11", "r15",\ + "r16", "r17", "r18", "r19", "r20", "r21", "r22",\ + "r23", "r24", "r25" + +#if defined (__CSKY_HARD_FLOAT__) || (__CSKY_VDSP__) +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL, "vr8", "vr9", "vr10", "vr11", "vr12",\ + "vr13", "vr14", "vr15" +#else +#define REGS_TO_SAVE REGS_TO_SAVE_GENERAL +#endif +#else +#define REGS_TO_SAVE "r9", "r10", "r11", "r12", "r13", "r15" +#endif + + +static int +#ifdef __GNUC__ +__attribute__((optimize("no-omit-frame-pointer"))) +#endif +slp_switch(void) +{ + int *stackref, stsizediff; + int result; + + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("mov %0, sp" : "=r" (stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "addu sp,%0\n" + "addu "REG_FP",%0\n" + : + : "r" (stsizediff) + ); + + SLP_RESTORE_STATE(); + } + __asm__ volatile ("movi %0, 0" : "=r" (result)); + __asm__ volatile ("" : : : REGS_TO_SAVE); + + return result; +} + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_loongarch64_linux.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_loongarch64_linux.h new file mode 100644 index 0000000..9eaf34e --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_loongarch64_linux.h @@ -0,0 +1,31 @@ +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL +#define STACK_MAGIC 0 + +#define REGS_TO_SAVE "s0", "s1", "s2", "s3", "s4", "s5", \ + "s6", "s7", "s8", "fp", \ + "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31" + +static int +slp_switch(void) +{ + int ret; + long *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("move %0, $sp" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "add.d $sp, $sp, %0\n\t" + : /* no outputs */ + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("move %0, $zero" : "=r" (ret) : ); + return ret; +} + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_m68k_gcc.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_m68k_gcc.h new file mode 100644 index 0000000..da761c2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_m68k_gcc.h @@ -0,0 +1,38 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 2014-01-06 Andreas Schwab + * File created. + */ + +#ifdef SLP_EVAL + +#define STACK_MAGIC 0 + +#define REGS_TO_SAVE "%d2", "%d3", "%d4", "%d5", "%d6", "%d7", \ + "%a2", "%a3", "%a4" + +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; + void *fp, *a5; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("move.l %%fp, %0" : "=m"(fp)); + __asm__ volatile ("move.l %%a5, %0" : "=m"(a5)); + __asm__ ("move.l %%sp, %0" : "=r"(stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ("add.l %0, %%sp; add.l %0, %%fp" : : "r"(stsizediff)); + SLP_RESTORE_STATE(); + __asm__ volatile ("clr.l %0" : "=g" (err)); + } + __asm__ volatile ("move.l %0, %%a5" : : "m"(a5)); + __asm__ volatile ("move.l %0, %%fp" : : "m"(fp)); + __asm__ volatile ("" : : : REGS_TO_SAVE); + return err; +} + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_mips_unix.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_mips_unix.h new file mode 100644 index 0000000..b9003e9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_mips_unix.h @@ -0,0 +1,64 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 20-Sep-14 Matt Madison + * Re-code the saving of the gp register for MIPS64. + * 05-Jan-08 Thiemo Seufer + * Ported from ppc. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 0 + +#define REGS_TO_SAVE "$16", "$17", "$18", "$19", "$20", "$21", "$22", \ + "$23", "$30" +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; +#ifdef __mips64 + uint64_t gpsave; +#endif + __asm__ __volatile__ ("" : : : REGS_TO_SAVE); +#ifdef __mips64 + __asm__ __volatile__ ("sd $28,%0" : "=m" (gpsave) : : ); +#endif + __asm__ ("move %0, $29" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ __volatile__ ( +#ifdef __mips64 + "daddu $29, %0\n" +#else + "addu $29, %0\n" +#endif + : /* no outputs */ + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + } +#ifdef __mips64 + __asm__ __volatile__ ("ld $28,%0" : : "m" (gpsave) : ); +#endif + __asm__ __volatile__ ("" : : : REGS_TO_SAVE); + __asm__ __volatile__ ("move %0, $0" : "=r" (err)); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc64_aix.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc64_aix.h new file mode 100644 index 0000000..e7e0b87 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc64_aix.h @@ -0,0 +1,103 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 16-Oct-20 Jesse Gorzinski + * Copied from Linux PPC64 implementation + * 04-Sep-18 Alexey Borzenkov + * Workaround a gcc bug using manual save/restore of r30 + * 21-Mar-18 Tulio Magno Quites Machado Filho + * Added r30 to the list of saved registers in order to fully comply with + * both ppc64 ELFv1 ABI and the ppc64le ELFv2 ABI, that classify this + * register as a nonvolatile register used for local variables. + * 21-Mar-18 Laszlo Boszormenyi + * Save r2 (TOC pointer) manually. + * 10-Dec-13 Ulrich Weigand + * Support ELFv2 ABI. Save float/vector registers. + * 09-Mar-12 Michael Ellerman + * 64-bit implementation, copied from 32-bit. + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'r31' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 14-Jan-04 Bob Ippolito + * added cr2-cr4 to the registers to be saved. + * Open questions: Should we save FP registers? + * What about vector registers? + * Differences between darwin and unix? + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 04-Oct-02 Gustavo Niemeyer + * Ported from MacOS version. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 29-Jun-02 Christian Tismer + * Added register 13-29, 31 saves. The same way as + * Armin Rigo did for the x86_unix version. + * This seems to be now fully functional! + * 04-Mar-02 Hye-Shik Chang + * Ported from i386. + * 31-Jul-12 Trevor Bowen + * Changed memory constraints to register only. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 6 + +#if defined(__ALTIVEC__) +#define ALTIVEC_REGS \ + "v20", "v21", "v22", "v23", "v24", "v25", "v26", "v27", \ + "v28", "v29", "v30", "v31", +#else +#define ALTIVEC_REGS +#endif + +#define REGS_TO_SAVE "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ + "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ + "r31", \ + "fr14", "fr15", "fr16", "fr17", "fr18", "fr19", "fr20", "fr21", \ + "fr22", "fr23", "fr24", "fr25", "fr26", "fr27", "fr28", "fr29", \ + "fr30", "fr31", \ + ALTIVEC_REGS \ + "cr2", "cr3", "cr4" + +static int +slp_switch(void) +{ + int err; + long *stackref, stsizediff; + void * toc; + void * r30; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("std 2, %0" : "=m" (toc)); + __asm__ volatile ("std 30, %0" : "=m" (r30)); + __asm__ ("mr %0, 1" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "mr 11, %0\n" + "add 1, 1, 11\n" + : /* no outputs */ + : "r" (stsizediff) + : "11" + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("ld 30, %0" : : "m" (r30)); + __asm__ volatile ("ld 2, %0" : : "m" (toc)); + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("li %0, 0" : "=r" (err)); + return err; +} + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc64_linux.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc64_linux.h new file mode 100644 index 0000000..3c324d0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc64_linux.h @@ -0,0 +1,105 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 04-Sep-18 Alexey Borzenkov + * Workaround a gcc bug using manual save/restore of r30 + * 21-Mar-18 Tulio Magno Quites Machado Filho + * Added r30 to the list of saved registers in order to fully comply with + * both ppc64 ELFv1 ABI and the ppc64le ELFv2 ABI, that classify this + * register as a nonvolatile register used for local variables. + * 21-Mar-18 Laszlo Boszormenyi + * Save r2 (TOC pointer) manually. + * 10-Dec-13 Ulrich Weigand + * Support ELFv2 ABI. Save float/vector registers. + * 09-Mar-12 Michael Ellerman + * 64-bit implementation, copied from 32-bit. + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'r31' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 14-Jan-04 Bob Ippolito + * added cr2-cr4 to the registers to be saved. + * Open questions: Should we save FP registers? + * What about vector registers? + * Differences between darwin and unix? + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 04-Oct-02 Gustavo Niemeyer + * Ported from MacOS version. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 29-Jun-02 Christian Tismer + * Added register 13-29, 31 saves. The same way as + * Armin Rigo did for the x86_unix version. + * This seems to be now fully functional! + * 04-Mar-02 Hye-Shik Chang + * Ported from i386. + * 31-Jul-12 Trevor Bowen + * Changed memory constraints to register only. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#if _CALL_ELF == 2 +#define STACK_MAGIC 4 +#else +#define STACK_MAGIC 6 +#endif + +#if defined(__ALTIVEC__) +#define ALTIVEC_REGS \ + "v20", "v21", "v22", "v23", "v24", "v25", "v26", "v27", \ + "v28", "v29", "v30", "v31", +#else +#define ALTIVEC_REGS +#endif + +#define REGS_TO_SAVE "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ + "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ + "r31", \ + "fr14", "fr15", "fr16", "fr17", "fr18", "fr19", "fr20", "fr21", \ + "fr22", "fr23", "fr24", "fr25", "fr26", "fr27", "fr28", "fr29", \ + "fr30", "fr31", \ + ALTIVEC_REGS \ + "cr2", "cr3", "cr4" + +static int +slp_switch(void) +{ + int err; + long *stackref, stsizediff; + void * toc; + void * r30; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("std 2, %0" : "=m" (toc)); + __asm__ volatile ("std 30, %0" : "=m" (r30)); + __asm__ ("mr %0, 1" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "mr 11, %0\n" + "add 1, 1, 11\n" + : /* no outputs */ + : "r" (stsizediff) + : "11" + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("ld 30, %0" : : "m" (r30)); + __asm__ volatile ("ld 2, %0" : : "m" (toc)); + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("li %0, 0" : "=r" (err)); + return err; +} + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_aix.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_aix.h new file mode 100644 index 0000000..6d93c13 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_aix.h @@ -0,0 +1,87 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 07-Mar-11 Floris Bruynooghe + * Do not add stsizediff to general purpose + * register (GPR) 30 as this is a non-volatile and + * unused by the PowerOpen Environment, therefore + * this was modifying a user register instead of the + * frame pointer (which does not seem to exist). + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'r31' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 14-Jan-04 Bob Ippolito + * added cr2-cr4 to the registers to be saved. + * Open questions: Should we save FP registers? + * What about vector registers? + * Differences between darwin and unix? + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 04-Oct-02 Gustavo Niemeyer + * Ported from MacOS version. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 29-Jun-02 Christian Tismer + * Added register 13-29, 31 saves. The same way as + * Armin Rigo did for the x86_unix version. + * This seems to be now fully functional! + * 04-Mar-02 Hye-Shik Chang + * Ported from i386. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 3 + +/* !!!!WARNING!!!! need to add "r31" in the next line if this header file + * is meant to be compiled non-dynamically! + */ +#define REGS_TO_SAVE "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ + "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ + "cr2", "cr3", "cr4" +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("mr %0, 1" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "mr 11, %0\n" + "add 1, 1, 11\n" + : /* no outputs */ + : "r" (stsizediff) + : "11" + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("li %0, 0" : "=r" (err)); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_linux.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_linux.h new file mode 100644 index 0000000..e83ad70 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_linux.h @@ -0,0 +1,84 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'r31' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 14-Jan-04 Bob Ippolito + * added cr2-cr4 to the registers to be saved. + * Open questions: Should we save FP registers? + * What about vector registers? + * Differences between darwin and unix? + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 04-Oct-02 Gustavo Niemeyer + * Ported from MacOS version. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 29-Jun-02 Christian Tismer + * Added register 13-29, 31 saves. The same way as + * Armin Rigo did for the x86_unix version. + * This seems to be now fully functional! + * 04-Mar-02 Hye-Shik Chang + * Ported from i386. + * 31-Jul-12 Trevor Bowen + * Changed memory constraints to register only. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 3 + +/* !!!!WARNING!!!! need to add "r31" in the next line if this header file + * is meant to be compiled non-dynamically! + */ +#define REGS_TO_SAVE "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ + "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ + "cr2", "cr3", "cr4" +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("mr %0, 1" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "mr 11, %0\n" + "add 1, 1, 11\n" + "add 30, 30, 11\n" + : /* no outputs */ + : "r" (stsizediff) + : "11" + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("li %0, 0" : "=r" (err)); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_macosx.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_macosx.h new file mode 100644 index 0000000..bd414c6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_macosx.h @@ -0,0 +1,82 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'r31' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 14-Jan-04 Bob Ippolito + * added cr2-cr4 to the registers to be saved. + * Open questions: Should we save FP registers? + * What about vector registers? + * Differences between darwin and unix? + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 29-Jun-02 Christian Tismer + * Added register 13-29, 31 saves. The same way as + * Armin Rigo did for the x86_unix version. + * This seems to be now fully functional! + * 04-Mar-02 Hye-Shik Chang + * Ported from i386. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 3 + +/* !!!!WARNING!!!! need to add "r31" in the next line if this header file + * is meant to be compiled non-dynamically! + */ +#define REGS_TO_SAVE "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ + "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ + "cr2", "cr3", "cr4" + +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("; asm block 2\n\tmr %0, r1" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "; asm block 3\n" + "\tmr r11, %0\n" + "\tadd r1, r1, r11\n" + "\tadd r30, r30, r11\n" + : /* no outputs */ + : "r" (stsizediff) + : "r11" + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("li %0, 0" : "=r" (err)); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_unix.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_unix.h new file mode 100644 index 0000000..bb18808 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_unix.h @@ -0,0 +1,82 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'r31' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 14-Jan-04 Bob Ippolito + * added cr2-cr4 to the registers to be saved. + * Open questions: Should we save FP registers? + * What about vector registers? + * Differences between darwin and unix? + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 04-Oct-02 Gustavo Niemeyer + * Ported from MacOS version. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 29-Jun-02 Christian Tismer + * Added register 13-29, 31 saves. The same way as + * Armin Rigo did for the x86_unix version. + * This seems to be now fully functional! + * 04-Mar-02 Hye-Shik Chang + * Ported from i386. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 3 + +/* !!!!WARNING!!!! need to add "r31" in the next line if this header file + * is meant to be compiled non-dynamically! + */ +#define REGS_TO_SAVE "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", \ + "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", \ + "cr2", "cr3", "cr4" +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ ("mr %0, 1" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "mr 11, %0\n" + "add 1, 1, 11\n" + "add 30, 30, 11\n" + : /* no outputs */ + : "r" (stsizediff) + : "11" + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("li %0, 0" : "=r" (err)); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_riscv_unix.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_riscv_unix.h new file mode 100644 index 0000000..8761122 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_riscv_unix.h @@ -0,0 +1,41 @@ +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL +#define STACK_MAGIC 0 + +#define REGS_TO_SAVE "s1", "s2", "s3", "s4", "s5", \ + "s6", "s7", "s8", "s9", "s10", "s11", "fs0", "fs1", \ + "fs2", "fs3", "fs4", "fs5", "fs6", "fs7", "fs8", "fs9", \ + "fs10", "fs11" + +static int +slp_switch(void) +{ + int ret; + long fp; + long *stackref, stsizediff; + + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("mv %0, fp" : "=r" (fp) : ); + __asm__ volatile ("mv %0, sp" : "=r" (stackref) : ); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "add sp, sp, %0\n\t" + "add fp, fp, %0\n\t" + : /* no outputs */ + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); +#if __riscv_xlen == 32 + __asm__ volatile ("lw fp, %0" : : "m" (fp)); +#else + __asm__ volatile ("ld fp, %0" : : "m" (fp)); +#endif + __asm__ volatile ("mv %0, zero" : "=r" (ret) : ); + return ret; +} + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_s390_unix.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_s390_unix.h new file mode 100644 index 0000000..9199367 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_s390_unix.h @@ -0,0 +1,87 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 25-Jan-12 Alexey Borzenkov + * Fixed Linux/S390 port to work correctly with + * different optimization options both on 31-bit + * and 64-bit. Thanks to Stefan Raabe for lots + * of testing. + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 06-Oct-02 Gustavo Niemeyer + * Ported to Linux/S390. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#ifdef __s390x__ +#define STACK_MAGIC 20 /* 20 * 8 = 160 bytes of function call area */ +#else +#define STACK_MAGIC 24 /* 24 * 4 = 96 bytes of function call area */ +#endif + +/* Technically, r11-r13 also need saving, but function prolog starts + with stm(g) and since there are so many saved registers already + it won't be optimized, resulting in all r6-r15 being saved */ +#define REGS_TO_SAVE "r6", "r7", "r8", "r9", "r10", "r14", \ + "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", \ + "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15" + +static int +slp_switch(void) +{ + int ret; + long *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); +#ifdef __s390x__ + __asm__ volatile ("lgr %0, 15" : "=r" (stackref) : ); +#else + __asm__ volatile ("lr %0, 15" : "=r" (stackref) : ); +#endif + { + SLP_SAVE_STATE(stackref, stsizediff); +/* N.B. + r11 may be used as the frame pointer, and in that case it cannot be + clobbered and needs offsetting just like the stack pointer (but in cases + where frame pointer isn't used we might clobber it accidentally). What's + scary is that r11 is 2nd (and even 1st when GOT is used) callee saved + register that gcc would chose for surviving function calls. However, + since r6-r10 are clobbered above, their cost for reuse is reduced, so + gcc IRA will chose them over r11 (not seeing r11 is implicitly saved), + making it relatively safe to offset in all cases. :) */ + __asm__ volatile ( +#ifdef __s390x__ + "agr 15, %0\n\t" + "agr 11, %0" +#else + "ar 15, %0\n\t" + "ar 11, %0" +#endif + : /* no outputs */ + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("lhi %0, 0" : "=r" (ret) : ); + return ret; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_sh_gcc.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_sh_gcc.h new file mode 100644 index 0000000..5ecc3b3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_sh_gcc.h @@ -0,0 +1,36 @@ +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL +#define STACK_MAGIC 0 +#define REGS_TO_SAVE "r8", "r9", "r10", "r11", "r13", \ + "fr12", "fr13", "fr14", "fr15" + +// r12 Global context pointer, GP +// r14 Frame pointer, FP +// r15 Stack pointer, SP + +static int +slp_switch(void) +{ + int err; + void* fp; + int *stackref, stsizediff; + __asm__ volatile("" : : : REGS_TO_SAVE); + __asm__ volatile("mov.l r14, %0" : "=m"(fp) : :); + __asm__("mov r15, %0" : "=r"(stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile( + "add %0, r15\n" + "add %0, r14\n" + : /* no outputs */ + : "r"(stsizediff)); + SLP_RESTORE_STATE(); + __asm__ volatile("mov r0, %0" : "=r"(err) : :); + } + __asm__ volatile("mov.l %0, r14" : : "m"(fp) :); + __asm__ volatile("" : : : REGS_TO_SAVE); + return err; +} + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_sparc_sun_gcc.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_sparc_sun_gcc.h new file mode 100644 index 0000000..96990c3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_sparc_sun_gcc.h @@ -0,0 +1,92 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 16-May-15 Alexey Borzenkov + * Move stack spilling code inside save/restore functions + * 30-Aug-13 Floris Bruynooghe + Clean the register windows again before returning. + This does not clobber the PIC register as it leaves + the current window intact and is required for multi- + threaded code to work correctly. + * 08-Mar-11 Floris Bruynooghe + * No need to set return value register explicitly + * before the stack and framepointer are adjusted + * as none of the other registers are influenced by + * this. Also don't needlessly clean the windows + * ('ta %0" :: "i" (ST_CLEAN_WINDOWS)') as that + * clobbers the gcc PIC register (%l7). + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * added support for SunOS sparc with gcc + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + + +#define STACK_MAGIC 0 + + +#if defined(__sparcv9) +#define SLP_FLUSHW __asm__ volatile ("flushw") +#else +#define SLP_FLUSHW __asm__ volatile ("ta 3") /* ST_FLUSH_WINDOWS */ +#endif + +/* On sparc we need to spill register windows inside save/restore functions */ +#define SLP_BEFORE_SAVE_STATE() SLP_FLUSHW +#define SLP_BEFORE_RESTORE_STATE() SLP_FLUSHW + + +static int +slp_switch(void) +{ + int err; + int *stackref, stsizediff; + + /* Put current stack pointer into stackref. + * Register spilling is done in save/restore. + */ + __asm__ volatile ("mov %%sp, %0" : "=r" (stackref)); + + { + /* Thou shalt put SLP_SAVE_STATE into a local block */ + /* Copy the current stack onto the heap */ + SLP_SAVE_STATE(stackref, stsizediff); + + /* Increment stack and frame pointer by stsizediff */ + __asm__ volatile ( + "add %0, %%sp, %%sp\n\t" + "add %0, %%fp, %%fp" + : : "r" (stsizediff)); + + /* Copy new stack from it's save store on the heap */ + SLP_RESTORE_STATE(); + + __asm__ volatile ("mov %1, %0" : "=r" (err) : "i" (0)); + return err; + } +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_x32_unix.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_x32_unix.h new file mode 100644 index 0000000..893369c --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_x32_unix.h @@ -0,0 +1,63 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 17-Aug-12 Fantix King + * Ported from amd64. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 0 + +#define REGS_TO_SAVE "r12", "r13", "r14", "r15" + + +static int +slp_switch(void) +{ + void* ebp; + void* ebx; + unsigned int csr; + unsigned short cw; + int err; + int *stackref, stsizediff; + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("fstcw %0" : "=m" (cw)); + __asm__ volatile ("stmxcsr %0" : "=m" (csr)); + __asm__ volatile ("movl %%ebp, %0" : "=m" (ebp)); + __asm__ volatile ("movl %%ebx, %0" : "=m" (ebx)); + __asm__ ("movl %%esp, %0" : "=g" (stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "addl %0, %%esp\n" + "addl %0, %%ebp\n" + : + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + } + __asm__ volatile ("movl %0, %%ebx" : : "m" (ebx)); + __asm__ volatile ("movl %0, %%ebp" : : "m" (ebp)); + __asm__ volatile ("ldmxcsr %0" : : "m" (csr)); + __asm__ volatile ("fldcw %0" : : "m" (cw)); + __asm__ volatile ("" : : : REGS_TO_SAVE); + __asm__ volatile ("xorl %%eax, %%eax" : "=a" (err)); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_masm.asm b/venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_masm.asm new file mode 100644 index 0000000..f5c72a2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_masm.asm @@ -0,0 +1,111 @@ +; +; stack switching code for MASM on x641 +; Kristjan Valur Jonsson, sept 2005 +; + + +;prototypes for our calls +slp_save_state_asm PROTO +slp_restore_state_asm PROTO + + +pushxmm MACRO reg + sub rsp, 16 + .allocstack 16 + movaps [rsp], reg ; faster than movups, but we must be aligned + ; .savexmm128 reg, offset (don't know what offset is, no documentation) +ENDM +popxmm MACRO reg + movaps reg, [rsp] ; faster than movups, but we must be aligned + add rsp, 16 +ENDM + +pushreg MACRO reg + push reg + .pushreg reg +ENDM +popreg MACRO reg + pop reg +ENDM + + +.code +slp_switch PROC FRAME + ;realign stack to 16 bytes after return address push, makes the following faster + sub rsp,8 + .allocstack 8 + + pushxmm xmm15 + pushxmm xmm14 + pushxmm xmm13 + pushxmm xmm12 + pushxmm xmm11 + pushxmm xmm10 + pushxmm xmm9 + pushxmm xmm8 + pushxmm xmm7 + pushxmm xmm6 + + pushreg r15 + pushreg r14 + pushreg r13 + pushreg r12 + + pushreg rbp + pushreg rbx + pushreg rdi + pushreg rsi + + sub rsp, 10h ;allocate the singlefunction argument (must be multiple of 16) + .allocstack 10h +.endprolog + + lea rcx, [rsp+10h] ;load stack base that we are saving + call slp_save_state_asm ;pass stackpointer, return offset in eax + cmp rax, 1 + je EXIT1 + cmp rax, -1 + je EXIT2 + ;actual stack switch: + add rsp, rax + call slp_restore_state_asm + xor rax, rax ;return 0 + +EXIT: + + add rsp, 10h + popreg rsi + popreg rdi + popreg rbx + popreg rbp + + popreg r12 + popreg r13 + popreg r14 + popreg r15 + + popxmm xmm6 + popxmm xmm7 + popxmm xmm8 + popxmm xmm9 + popxmm xmm10 + popxmm xmm11 + popxmm xmm12 + popxmm xmm13 + popxmm xmm14 + popxmm xmm15 + + add rsp, 8 + ret + +EXIT1: + mov rax, 1 + jmp EXIT + +EXIT2: + sar rax, 1 + jmp EXIT + +slp_switch ENDP + +END \ No newline at end of file diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_masm.obj b/venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_masm.obj new file mode 100644 index 0000000000000000000000000000000000000000..64e3e6b898ec765d4e37075f7b1635ad24c9efa2 GIT binary patch literal 1078 zcmZ{j&ubG=5XWb`DJB@*%~BA=L%=;Gk}d_~52VO$4J4q2U~MY6&1RFl{E&?scGnt@ zn(9GNy!ihFEO@PV4?T&H9`x2*oO!!jlNJZwd!P4xlX&_;U$Bg3z>p zje>}2kp+MsxE|w5hOUr>YC~(=fz6fwPdZd5+Hlb^P3{;ZO@Yuv96GG&+Gx?QfclNd zhy2KN&~>fNnlHQRR;U1cMyQ?hlQ$~k<0KBbB<0uD2#PTjVo+na7Q;#m=@=3m;xJOa zs2V#)&Db`cY;WzTF9)11;SjkVQWE!?bPTC%x3h3^F2;aBns5!i%m4&-*h69;~AUpZR%rDpm!zuXY+kc zFCz-n*^4&c)5~}y3e?r-Evy*n*(lp9r%ti58Y#l5&)rDjx5EbRd}nC+_8znRzz&#& XZ_Fi+`GM=5Rl{n4%KxAK>jC@)Nz=zi literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_msvc.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_msvc.h new file mode 100644 index 0000000..601ea56 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_msvc.h @@ -0,0 +1,60 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 26-Sep-02 Christian Tismer + * again as a result of virtualized stack access, + * the compiler used less registers. Needed to + * explicit mention registers in order to get them saved. + * Thanks to Jeff Senn for pointing this out and help. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 01-Mar-02 Christian Tismer + * Initial final version after lots of iterations for i386. + */ + +/* Avoid alloca redefined warning on mingw64 */ +#ifndef alloca +#define alloca _alloca +#endif + +#define STACK_REFPLUS 1 +#define STACK_MAGIC 0 + +/* Use the generic support for an external assembly language slp_switch function. */ +#define EXTERNAL_ASM + +#ifdef SLP_EVAL +/* This always uses the external masm assembly file. */ +#endif + +/* + * further self-processing support + */ + +/* we have IsBadReadPtr available, so we can peek at objects */ +/* +#define STACKLESS_SPY + +#ifdef IMPLEMENT_STACKLESSMODULE +#include "Windows.h" +#define CANNOT_READ_MEM(p, bytes) IsBadReadPtr(p, bytes) + +static int IS_ON_STACK(void*p) +{ + int stackref; + intptr_t stackbase = ((intptr_t)&stackref) & 0xfffff000; + return (intptr_t)p >= stackbase && (intptr_t)p < stackbase + 0x00100000; +} + +#endif +*/ \ No newline at end of file diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_x86_msvc.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_x86_msvc.h new file mode 100644 index 0000000..0f3a59f --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_x86_msvc.h @@ -0,0 +1,326 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 26-Sep-02 Christian Tismer + * again as a result of virtualized stack access, + * the compiler used less registers. Needed to + * explicit mention registers in order to get them saved. + * Thanks to Jeff Senn for pointing this out and help. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for sparc + * 01-Mar-02 Christian Tismer + * Initial final version after lots of iterations for i386. + */ + +#define alloca _alloca + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +#define STACK_MAGIC 0 + +/* Some magic to quell warnings and keep slp_switch() from crashing when built + with VC90. Disable global optimizations, and the warning: frame pointer + register 'ebp' modified by inline assembly code. + + We used to just disable global optimizations ("g") but upstream stackless + Python, as well as stackman, turn off all optimizations. + +References: +https://github.com/stackless-dev/stackman/blob/dbc72fe5207a2055e658c819fdeab9731dee78b9/stackman/platforms/switch_x86_msvc.h +https://github.com/stackless-dev/stackless/blob/main-slp/Stackless/platf/switch_x86_msvc.h +*/ +#define WIN32_LEAN_AND_MEAN +#include + +#pragma optimize("", off) /* so that autos are stored on the stack */ +#pragma warning(disable:4731) +#pragma warning(disable:4733) /* disable warning about modifying FS[0] */ + +/** + * Most modern compilers and environments handle C++ exceptions without any + * special help from us. MSVC on 32-bit windows is an exception. There, C++ + * exceptions are dealt with using Windows' Structured Exception Handling + * (SEH). + * + * SEH is implemented as a singly linked list of nodes. The + * head of this list is stored in the Thread Information Block, which itself + * is pointed to from the FS register. It's the first field in the structure, + * or offset 0, so we can access it using assembly FS:[0], or the compiler + * intrinsics and field offset information from the headers (as we do below). + * Somewhat unusually, the tail of the list doesn't have prev == NULL, it has + * prev == 0xFFFFFFFF. + * + * SEH was designed for C, and traditionally uses the MSVC compiler + * intrinsincs __try{}/__except{}. It is also utilized for C++ exceptions by + * MSVC; there, every throw of a C++ exception raises a SEH error with the + * ExceptionCode 0xE06D7363; the SEH handler list is then traversed to + * deal with the exception. + * + * If the SEH list is corrupt, then when a C++ exception is thrown the program + * will abruptly exit with exit code 1. This does not use std::terminate(), so + * std::set_terminate() is useless to debug this. + * + * The SEH list is closely tied to the call stack; entering a function that + * uses __try{} or most C++ functions will push a new handler onto the front + * of the list. Returning from the function will remove the handler. Saving + * and restoring the head node of the SEH list (FS:[0]) per-greenlet is NOT + * ENOUGH to make SEH or exceptions work. + * + * Stack switching breaks SEH because the call stack no longer necessarily + * matches the SEH list. For example, given greenlet A that switches to + * greenlet B, at the moment of entering greenlet B, we will have any SEH + * handlers from greenlet A on the SEH list; greenlet B can then add its own + * handlers to the SEH list. When greenlet B switches back to greenlet A, + * greenlet B's handlers would still be on the SEH stack, but when switch() + * returns control to greenlet A, we have replaced the contents of the stack + * in memory, so all the address that greenlet B added to the SEH list are now + * invalid: part of the call stack has been unwound, but the SEH list was out + * of sync with the call stack. The net effect is that exception handling + * stops working. + * + * Thus, when switching greenlets, we need to be sure that the SEH list + * matches the effective call stack, "cutting out" any handlers that were + * pushed by the greenlet that switched out and which are no longer valid. + * + * The easiest way to do this is to capture the SEH list at the time the main + * greenlet for a thread is created, and, when initially starting a greenlet, + * start a new SEH list for it, which contains nothing but the handler + * established for the new greenlet itself, with the tail being the handlers + * for the main greenlet. If we then save and restore the SEH per-greenlet, + * they won't interfere with each others SEH lists. (No greenlet can unwind + * the call stack past the handlers established by the main greenlet). + * + * By observation, a new thread starts with three SEH handlers on the list. By + * the time we get around to creating the main greenlet, though, there can be + * many more, established by transient calls that lead to the creation of the + * main greenlet. Therefore, 3 is a magic constant telling us when to perform + * the initial slice. + * + * All of this can be debugged using a vectored exception handler, which + * operates independently of the SEH handler list, and is called first. + * Walking the SEH list at key points can also be helpful. + * + * References: + * https://en.wikipedia.org/wiki/Win32_Thread_Information_Block + * https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273 + * https://docs.microsoft.com/en-us/cpp/cpp/try-except-statement?view=msvc-160 + * https://docs.microsoft.com/en-us/cpp/cpp/structured-exception-handling-c-cpp?view=msvc-160 + * https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling + * https://docs.microsoft.com/en-us/windows/win32/debug/using-a-vectored-exception-handler + * https://bytepointer.com/resources/pietrek_crash_course_depths_of_win32_seh.htm + */ +#define GREENLET_NEEDS_EXCEPTION_STATE_SAVED + + +typedef struct _GExceptionRegistration { + struct _GExceptionRegistration* prev; + void* handler_f; +} GExceptionRegistration; + +static void +slp_set_exception_state(const void *const seh_state) +{ + // Because the stack from from which we do this is ALSO a handler, and + // that one we want to keep, we need to relink the current SEH handler + // frame to point to this one, cutting out the middle men, as it were. + // + // Entering a try block doesn't change the SEH frame, but entering a + // function containing a try block does. + GExceptionRegistration* current_seh_state = (GExceptionRegistration*)__readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); + current_seh_state->prev = (GExceptionRegistration*)seh_state; +} + + +static GExceptionRegistration* +x86_slp_get_third_oldest_handler() +{ + GExceptionRegistration* a = NULL; /* Closest to the top */ + GExceptionRegistration* b = NULL; /* second */ + GExceptionRegistration* c = NULL; + GExceptionRegistration* seh_state = (GExceptionRegistration*)__readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); + a = b = c = seh_state; + + while (seh_state && seh_state != (GExceptionRegistration*)0xFFFFFFFF) { + if ((void*)seh_state->prev < (void*)100) { + fprintf(stderr, "\tERROR: Broken SEH chain.\n"); + return NULL; + } + a = b; + b = c; + c = seh_state; + + seh_state = seh_state->prev; + } + return a ? a : (b ? b : c); +} + + +static void* +slp_get_exception_state() +{ + // XXX: There appear to be three SEH handlers on the stack already at the + // start of the thread. Is that a guarantee? Almost certainly not. Yet in + // all observed cases it has been three. This is consistent with + // faulthandler off or on, and optimizations off or on. It may not be + // consistent with other operating system versions, though: we only have + // CI on one or two versions (don't ask what there are). + // In theory we could capture the number of handlers on the chain when + // PyInit__greenlet is called: there are probably only the default + // handlers at that point (unless we're embedded and people have used + // __try/__except or a C++ handler)? + return x86_slp_get_third_oldest_handler(); +} + +static int +slp_switch(void) +{ + /* MASM syntax is typically reversed from other assemblers. + It is usually + */ + int *stackref, stsizediff; + /* store the structured exception state for this stack */ + DWORD seh_state = __readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); + __asm mov stackref, esp; + /* modify EBX, ESI and EDI in order to get them preserved */ + __asm mov ebx, ebx; + __asm xchg esi, edi; + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm { + mov eax, stsizediff + add esp, eax + add ebp, eax + } + SLP_RESTORE_STATE(); + } + __writefsdword(FIELD_OFFSET(NT_TIB, ExceptionList), seh_state); + return 0; +} + +/* re-enable ebp warning and global optimizations. */ +#pragma optimize("", on) +#pragma warning(default:4731) +#pragma warning(default:4733) /* disable warning about modifying FS[0] */ + + +#endif + +/* + * further self-processing support + */ + +/* we have IsBadReadPtr available, so we can peek at objects */ +#define STACKLESS_SPY + +#ifdef GREENLET_DEBUG + +#define CANNOT_READ_MEM(p, bytes) IsBadReadPtr(p, bytes) + +static int IS_ON_STACK(void*p) +{ + int stackref; + int stackbase = ((int)&stackref) & 0xfffff000; + return (int)p >= stackbase && (int)p < stackbase + 0x00100000; +} + +static void +x86_slp_show_seh_chain() +{ + GExceptionRegistration* seh_state = (GExceptionRegistration*)__readfsdword(FIELD_OFFSET(NT_TIB, ExceptionList)); + fprintf(stderr, "====== SEH Chain ======\n"); + while (seh_state && seh_state != (GExceptionRegistration*)0xFFFFFFFF) { + fprintf(stderr, "\tSEH_chain addr: %p handler: %p prev: %p\n", + seh_state, + seh_state->handler_f, seh_state->prev); + if ((void*)seh_state->prev < (void*)100) { + fprintf(stderr, "\tERROR: Broken chain.\n"); + break; + } + seh_state = seh_state->prev; + } + fprintf(stderr, "====== End SEH Chain ======\n"); + fflush(NULL); + return; +} + +//addVectoredExceptionHandler constants: +//CALL_FIRST means call this exception handler first; +//CALL_LAST means call this exception handler last +#define CALL_FIRST 1 +#define CALL_LAST 0 + +LONG WINAPI +GreenletVectorHandler(PEXCEPTION_POINTERS ExceptionInfo) +{ + // We get one of these for every C++ exception, with code + // E06D7363 + // This is a special value that means "C++ exception from MSVC" + // https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273 + // + // Install in the module init function with: + // AddVectoredExceptionHandler(CALL_FIRST, GreenletVectorHandler); + PEXCEPTION_RECORD ExceptionRecord = ExceptionInfo->ExceptionRecord; + + fprintf(stderr, + "GOT VECTORED EXCEPTION:\n" + "\tExceptionCode : %p\n" + "\tExceptionFlags : %p\n" + "\tExceptionAddr : %p\n" + "\tNumberparams : %ld\n", + ExceptionRecord->ExceptionCode, + ExceptionRecord->ExceptionFlags, + ExceptionRecord->ExceptionAddress, + ExceptionRecord->NumberParameters + ); + if (ExceptionRecord->ExceptionFlags & 1) { + fprintf(stderr, "\t\tEH_NONCONTINUABLE\n" ); + } + if (ExceptionRecord->ExceptionFlags & 2) { + fprintf(stderr, "\t\tEH_UNWINDING\n" ); + } + if (ExceptionRecord->ExceptionFlags & 4) { + fprintf(stderr, "\t\tEH_EXIT_UNWIND\n" ); + } + if (ExceptionRecord->ExceptionFlags & 8) { + fprintf(stderr, "\t\tEH_STACK_INVALID\n" ); + } + if (ExceptionRecord->ExceptionFlags & 0x10) { + fprintf(stderr, "\t\tEH_NESTED_CALL\n" ); + } + if (ExceptionRecord->ExceptionFlags & 0x20) { + fprintf(stderr, "\t\tEH_TARGET_UNWIND\n" ); + } + if (ExceptionRecord->ExceptionFlags & 0x40) { + fprintf(stderr, "\t\tEH_COLLIDED_UNWIND\n" ); + } + fprintf(stderr, "\n"); + fflush(NULL); + for(DWORD i = 0; i < ExceptionRecord->NumberParameters; i++) { + fprintf(stderr, "\t\t\tParam %ld: %lX\n", i, ExceptionRecord->ExceptionInformation[i]); + } + + if (ExceptionRecord->NumberParameters == 3) { + fprintf(stderr, "\tAbout to traverse SEH chain\n"); + // C++ Exception records have 3 params. + x86_slp_show_seh_chain(); + } + + return EXCEPTION_CONTINUE_SEARCH; +} + + + + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/platform/switch_x86_unix.h b/venv/lib/python3.12/site-packages/greenlet/platform/switch_x86_unix.h new file mode 100644 index 0000000..493fa6b --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/platform/switch_x86_unix.h @@ -0,0 +1,105 @@ +/* + * this is the internal transfer function. + * + * HISTORY + * 3-May-13 Ralf Schmitt + * Add support for strange GCC caller-save decisions + * (ported from switch_aarch64_gcc.h) + * 19-Aug-11 Alexey Borzenkov + * Correctly save ebp, ebx and cw + * 07-Sep-05 (py-dev mailing list discussion) + * removed 'ebx' from the register-saved. !!!! WARNING !!!! + * It means that this file can no longer be compiled statically! + * It is now only suitable as part of a dynamic library! + * 24-Nov-02 Christian Tismer + * needed to add another magic constant to insure + * that f in slp_eval_frame(PyFrameObject *f) + * STACK_REFPLUS will probably be 1 in most cases. + * gets included into the saved stack area. + * 17-Sep-02 Christian Tismer + * after virtualizing stack save/restore, the + * stack size shrunk a bit. Needed to introduce + * an adjustment STACK_MAGIC per platform. + * 15-Sep-02 Gerd Woetzel + * slightly changed framework for spark + * 31-Avr-02 Armin Rigo + * Added ebx, esi and edi register-saves. + * 01-Mar-02 Samual M. Rushing + * Ported from i386. + */ + +#define STACK_REFPLUS 1 + +#ifdef SLP_EVAL + +/* #define STACK_MAGIC 3 */ +/* the above works fine with gcc 2.96, but 2.95.3 wants this */ +#define STACK_MAGIC 0 + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) +# define ATTR_NOCLONE __attribute__((noclone)) +#else +# define ATTR_NOCLONE +#endif + +static int +slp_switch(void) +{ + int err; +#ifdef _WIN32 + void *seh; +#endif + void *ebp, *ebx; + unsigned short cw; + int *stackref, stsizediff; + __asm__ volatile ("" : : : "esi", "edi"); + __asm__ volatile ("fstcw %0" : "=m" (cw)); + __asm__ volatile ("movl %%ebp, %0" : "=m" (ebp)); + __asm__ volatile ("movl %%ebx, %0" : "=m" (ebx)); +#ifdef _WIN32 + __asm__ volatile ( + "movl %%fs:0x0, %%eax\n" + "movl %%eax, %0\n" + : "=m" (seh) + : + : "eax"); +#endif + __asm__ ("movl %%esp, %0" : "=g" (stackref)); + { + SLP_SAVE_STATE(stackref, stsizediff); + __asm__ volatile ( + "addl %0, %%esp\n" + "addl %0, %%ebp\n" + : + : "r" (stsizediff) + ); + SLP_RESTORE_STATE(); + __asm__ volatile ("xorl %%eax, %%eax" : "=a" (err)); + } +#ifdef _WIN32 + __asm__ volatile ( + "movl %0, %%eax\n" + "movl %%eax, %%fs:0x0\n" + : + : "m" (seh) + : "eax"); +#endif + __asm__ volatile ("movl %0, %%ebx" : : "m" (ebx)); + __asm__ volatile ("movl %0, %%ebp" : : "m" (ebp)); + __asm__ volatile ("fldcw %0" : : "m" (cw)); + __asm__ volatile ("" : : : "esi", "edi"); + return err; +} + +#endif + +/* + * further self-processing support + */ + +/* + * if you want to add self-inspection tools, place them + * here. See the x86_msvc for the necessary defines. + * These features are highly experimental und not + * essential yet. + */ diff --git a/venv/lib/python3.12/site-packages/greenlet/slp_platformselect.h b/venv/lib/python3.12/site-packages/greenlet/slp_platformselect.h new file mode 100644 index 0000000..d9b7d0a --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/slp_platformselect.h @@ -0,0 +1,77 @@ +/* + * Platform Selection for Stackless Python + */ +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(MS_WIN32) && !defined(MS_WIN64) && defined(_M_IX86) && defined(_MSC_VER) +# include "platform/switch_x86_msvc.h" /* MS Visual Studio on X86 */ +#elif defined(MS_WIN64) && defined(_M_X64) && defined(_MSC_VER) || defined(__MINGW64__) +# include "platform/switch_x64_msvc.h" /* MS Visual Studio on X64 */ +#elif defined(MS_WIN64) && defined(_M_ARM64) +# include "platform/switch_arm64_msvc.h" /* MS Visual Studio on ARM64 */ +#elif defined(__GNUC__) && defined(__amd64__) && defined(__ILP32__) +# include "platform/switch_x32_unix.h" /* gcc on amd64 with x32 ABI */ +#elif defined(__GNUC__) && defined(__amd64__) +# include "platform/switch_amd64_unix.h" /* gcc on amd64 */ +#elif defined(__GNUC__) && defined(__i386__) +# include "platform/switch_x86_unix.h" /* gcc on X86 */ +#elif defined(__GNUC__) && defined(__powerpc64__) && (defined(__linux__) || defined(__FreeBSD__)) +# include "platform/switch_ppc64_linux.h" /* gcc on PowerPC 64-bit */ +#elif defined(__GNUC__) && defined(__PPC__) && (defined(__linux__) || defined(__FreeBSD__)) +# include "platform/switch_ppc_linux.h" /* gcc on PowerPC */ +#elif defined(__GNUC__) && defined(__POWERPC__) && defined(__APPLE__) +# include "platform/switch_ppc_macosx.h" /* Apple MacOS X on 32-bit PowerPC */ +#elif defined(__GNUC__) && defined(__powerpc64__) && defined(_AIX) +# include "platform/switch_ppc64_aix.h" /* gcc on AIX/PowerPC 64-bit */ +#elif defined(__GNUC__) && defined(_ARCH_PPC) && defined(_AIX) +# include "platform/switch_ppc_aix.h" /* gcc on AIX/PowerPC */ +#elif defined(__GNUC__) && defined(__powerpc__) && defined(__NetBSD__) +#include "platform/switch_ppc_unix.h" /* gcc on NetBSD/powerpc */ +#elif defined(__GNUC__) && defined(sparc) +# include "platform/switch_sparc_sun_gcc.h" /* SunOS sparc with gcc */ +#elif defined(__GNUC__) && defined(__sparc__) +# include "platform/switch_sparc_sun_gcc.h" /* NetBSD sparc with gcc */ +#elif defined(__SUNPRO_C) && defined(sparc) && defined(sun) +# include "platform/switch_sparc_sun_gcc.h" /* SunStudio on amd64 */ +#elif defined(__SUNPRO_C) && defined(__amd64__) && defined(sun) +# include "platform/switch_amd64_unix.h" /* SunStudio on amd64 */ +#elif defined(__SUNPRO_C) && defined(__i386__) && defined(sun) +# include "platform/switch_x86_unix.h" /* SunStudio on x86 */ +#elif defined(__GNUC__) && defined(__s390__) && defined(__linux__) +# include "platform/switch_s390_unix.h" /* Linux/S390 */ +#elif defined(__GNUC__) && defined(__s390x__) && defined(__linux__) +# include "platform/switch_s390_unix.h" /* Linux/S390 zSeries (64-bit) */ +#elif defined(__GNUC__) && defined(__arm__) +# ifdef __APPLE__ +# include +# endif +# if TARGET_OS_IPHONE +# include "platform/switch_arm32_ios.h" /* iPhone OS on arm32 */ +# else +# include "platform/switch_arm32_gcc.h" /* gcc using arm32 */ +# endif +#elif defined(__GNUC__) && defined(__mips__) && defined(__linux__) +# include "platform/switch_mips_unix.h" /* Linux/MIPS */ +#elif defined(__GNUC__) && defined(__aarch64__) +# include "platform/switch_aarch64_gcc.h" /* Aarch64 ABI */ +#elif defined(__GNUC__) && defined(__mc68000__) +# include "platform/switch_m68k_gcc.h" /* gcc on m68k */ +#elif defined(__GNUC__) && defined(__csky__) +#include "platform/switch_csky_gcc.h" /* gcc on csky */ +# elif defined(__GNUC__) && defined(__riscv) +# include "platform/switch_riscv_unix.h" /* gcc on RISC-V */ +#elif defined(__GNUC__) && defined(__alpha__) +# include "platform/switch_alpha_unix.h" /* gcc on DEC Alpha */ +#elif defined(MS_WIN32) && defined(__llvm__) && defined(__aarch64__) +# include "platform/switch_aarch64_gcc.h" /* LLVM Aarch64 ABI for Windows */ +#elif defined(__GNUC__) && defined(__loongarch64) && defined(__linux__) +# include "platform/switch_loongarch64_linux.h" /* LoongArch64 */ +#elif defined(__GNUC__) && defined(__sh__) +# include "platform/switch_sh_gcc.h" /* SuperH */ +#endif + +#ifdef __cplusplus +}; +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__init__.py b/venv/lib/python3.12/site-packages/greenlet/tests/__init__.py new file mode 100644 index 0000000..1861360 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/__init__.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +""" +Tests for greenlet. + +""" +import os +import sys +import sysconfig +import unittest + +from gc import collect +from gc import get_objects +from threading import active_count as active_thread_count +from time import sleep +from time import time + +import psutil + +from greenlet import greenlet as RawGreenlet +from greenlet import getcurrent + +from greenlet._greenlet import get_pending_cleanup_count +from greenlet._greenlet import get_total_main_greenlets + +from . import leakcheck + +PY312 = sys.version_info[:2] >= (3, 12) +PY313 = sys.version_info[:2] >= (3, 13) +# XXX: First tested on 3.14a7. Revisit all uses of this on later versions to ensure they +# are still valid. +PY314 = sys.version_info[:2] >= (3, 14) + +WIN = sys.platform.startswith("win") +RUNNING_ON_GITHUB_ACTIONS = os.environ.get('GITHUB_ACTIONS') +RUNNING_ON_TRAVIS = os.environ.get('TRAVIS') or RUNNING_ON_GITHUB_ACTIONS +RUNNING_ON_APPVEYOR = os.environ.get('APPVEYOR') +RUNNING_ON_CI = RUNNING_ON_TRAVIS or RUNNING_ON_APPVEYOR +RUNNING_ON_MANYLINUX = os.environ.get('GREENLET_MANYLINUX') + +# Is the current interpreter free-threaded?) Note that this +# isn't the same as whether the GIL is enabled, this is the build-time +# value. Certain CPython details, like the garbage collector, +# work very differently on potentially-free-threaded builds than +# standard builds. +RUNNING_ON_FREETHREAD_BUILD = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) + +class TestCaseMetaClass(type): + # wrap each test method with + # a) leak checks + def __new__(cls, classname, bases, classDict): + # pylint and pep8 fight over what this should be called (mcs or cls). + # pylint gets it right, but we can't scope disable pep8, so we go with + # its convention. + # pylint: disable=bad-mcs-classmethod-argument + check_totalrefcount = True + + # Python 3: must copy, we mutate the classDict. Interestingly enough, + # it doesn't actually error out, but under 3.6 we wind up wrapping + # and re-wrapping the same items over and over and over. + for key, value in list(classDict.items()): + if key.startswith('test') and callable(value): + classDict.pop(key) + if check_totalrefcount: + value = leakcheck.wrap_refcount(value) + classDict[key] = value + return type.__new__(cls, classname, bases, classDict) + + +class TestCase(unittest.TestCase, metaclass=TestCaseMetaClass): + + cleanup_attempt_sleep_duration = 0.001 + cleanup_max_sleep_seconds = 1 + + def wait_for_pending_cleanups(self, + initial_active_threads=None, + initial_main_greenlets=None): + initial_active_threads = initial_active_threads or self.threads_before_test + initial_main_greenlets = initial_main_greenlets or self.main_greenlets_before_test + sleep_time = self.cleanup_attempt_sleep_duration + # NOTE: This is racy! A Python-level thread object may be dead + # and gone, but the C thread may not yet have fired its + # destructors and added to the queue. There's no particular + # way to know that's about to happen. We try to watch the + # Python threads to make sure they, at least, have gone away. + # Counting the main greenlets, which we can easily do deterministically, + # also helps. + + # Always sleep at least once to let other threads run + sleep(sleep_time) + quit_after = time() + self.cleanup_max_sleep_seconds + # TODO: We could add an API that calls us back when a particular main greenlet is deleted? + # It would have to drop the GIL + while ( + get_pending_cleanup_count() + or active_thread_count() > initial_active_threads + or (not self.expect_greenlet_leak + and get_total_main_greenlets() > initial_main_greenlets)): + sleep(sleep_time) + if time() > quit_after: + print("Time limit exceeded.") + print("Threads: Waiting for only", initial_active_threads, + "-->", active_thread_count()) + print("MGlets : Waiting for only", initial_main_greenlets, + "-->", get_total_main_greenlets()) + break + collect() + + def count_objects(self, kind=list, exact_kind=True): + # pylint:disable=unidiomatic-typecheck + # Collect the garbage. + for _ in range(3): + collect() + if exact_kind: + return sum( + 1 + for x in get_objects() + if type(x) is kind + ) + # instances + return sum( + 1 + for x in get_objects() + if isinstance(x, kind) + ) + + greenlets_before_test = 0 + threads_before_test = 0 + main_greenlets_before_test = 0 + expect_greenlet_leak = False + + def count_greenlets(self): + """ + Find all the greenlets and subclasses tracked by the GC. + """ + return self.count_objects(RawGreenlet, False) + + def setUp(self): + # Ensure the main greenlet exists, otherwise the first test + # gets a false positive leak + super().setUp() + getcurrent() + self.threads_before_test = active_thread_count() + self.main_greenlets_before_test = get_total_main_greenlets() + self.wait_for_pending_cleanups(self.threads_before_test, self.main_greenlets_before_test) + self.greenlets_before_test = self.count_greenlets() + + def tearDown(self): + if getattr(self, 'skipTearDown', False): + return + + self.wait_for_pending_cleanups(self.threads_before_test, self.main_greenlets_before_test) + super().tearDown() + + def get_expected_returncodes_for_aborted_process(self): + import signal + # The child should be aborted in an unusual way. On POSIX + # platforms, this is done with abort() and signal.SIGABRT, + # which is reflected in a negative return value; however, on + # Windows, even though we observe the child print "Fatal + # Python error: Aborted" and in older versions of the C + # runtime "This application has requested the Runtime to + # terminate it in an unusual way," it always has an exit code + # of 3. This is interesting because 3 is the error code for + # ERROR_PATH_NOT_FOUND; BUT: the C runtime abort() function + # also uses this code. + # + # If we link to the static C library on Windows, the error + # code changes to '0xc0000409' (hex(3221226505)), which + # apparently is STATUS_STACK_BUFFER_OVERRUN; but "What this + # means is that nowadays when you get a + # STATUS_STACK_BUFFER_OVERRUN, it doesn’t actually mean that + # there is a stack buffer overrun. It just means that the + # application decided to terminate itself with great haste." + # + # + # On windows, we've also seen '0xc0000005' (hex(3221225477)). + # That's "Access Violation" + # + # See + # https://devblogs.microsoft.com/oldnewthing/20110519-00/?p=10623 + # and + # https://docs.microsoft.com/en-us/previous-versions/k089yyh0(v=vs.140)?redirectedfrom=MSDN + # and + # https://devblogs.microsoft.com/oldnewthing/20190108-00/?p=100655 + expected_exit = ( + -signal.SIGABRT, + # But beginning on Python 3.11, the faulthandler + # that prints the C backtraces sometimes segfaults after + # reporting the exception but before printing the stack. + # This has only been seen on linux/gcc. + -signal.SIGSEGV, + ) if not WIN else ( + 3, + 0xc0000409, + 0xc0000005, + ) + return expected_exit + + def get_process_uss(self): + """ + Return the current process's USS in bytes. + + uss is available on Linux, macOS, Windows. Also known as + "Unique Set Size", this is the memory which is unique to a + process and which would be freed if the process was terminated + right now. + + If this is not supported by ``psutil``, this raises the + :exc:`unittest.SkipTest` exception. + """ + try: + return psutil.Process().memory_full_info().uss + except AttributeError as e: + raise unittest.SkipTest("uss not supported") from e + + def run_script(self, script_name, show_output=True): + import subprocess + script = os.path.join( + os.path.dirname(__file__), + script_name, + ) + + try: + return subprocess.check_output([sys.executable, script], + encoding='utf-8', + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as ex: + if show_output: + print('-----') + print('Failed to run script', script) + print('~~~~~') + print(ex.output) + print('------') + raise + + + def assertScriptRaises(self, script_name, exitcodes=None): + import subprocess + with self.assertRaises(subprocess.CalledProcessError) as exc: + output = self.run_script(script_name, show_output=False) + __traceback_info__ = output + # We're going to fail the assertion if we get here, at least + # preserve the output in the traceback. + + if exitcodes is None: + exitcodes = self.get_expected_returncodes_for_aborted_process() + self.assertIn(exc.exception.returncode, exitcodes) + return exc.exception diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c976cb66b583d5b3d8e094c9200c2fb383fa579b GIT binary patch literal 9232 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7a6da0c5513c60bc54ed23058d63a1ee5f0a6ab GIT binary patch literal 2064 zcmb7F&u<$=6rNe{dbexG4oOQ$(!&TF?~Z?Ey=!L2 zNgegzsFBieNp9(}M^N}HIKstI5~Zx7suCwoG^7$zPrTW+V>d#ej_l_*Gw;3m=6&yt ze;pao5sY7cep>xgM(B4j=?|r+oX=o{_K=N=$i}wh;3CMPBq56H@xJlkcfY3=)qPsvPt!xW@>5e9DRG6VugA&RMP0;`Al~ zDT3LaI}5uOZ$YRwY55c)n4Dxv)H20vdD$>3lwWun#`3ZbLB%;vZH1DW%PA{UM*Zwf zvFehwPyh^vnrwivM9Utdq(mz<*R8n~9niuhLMKLN1wi68n;zpWx5nE9tjX7HUiILf zo(;G{^PH~#A@W_Yh8gHlht9`fbq`Sl#RNp!7>JHR0%JkqZp;0wmoBSsE<%W^5a07} zLF^$8G8%QUdTH2rz%Pf&jz(SWYlB~Vs1n*J8!KcbCL99QFl_1=#`sqVzBBW>U?4tr)OEV1DY*51p zbi-(PcFPfZ(lEY*vWs@Yqkcqe1YB5B2$8K&jDq5e6mOz)SyD2Qjo5z88o&$I@Vg)) zgv`63?4kSE#w!^(bNYX3V2Ig44J;yl@oLqtK)_4mKE&_$^rEPTAO^|KO6y|_kkeN? zj9mlgtir^dnFb=z1Tp27YXxzyRHqgXMlU--eIwrgLx=%~;u&Q^i1+e>Ls^-RY{bqh911!@N(8LeX+~%01tRCM2mq>_tzB9mQD-f@XoB+!u6C1^;c z?6mx9px*s_@tZG~7PBK^gL@X#x7Kg909OJXD7IRR0W>DcBf=(1Fw9Q~hKYa80N;x^ zSDFmo)=S7=Y9B@Y^9j2F#^NFHAAxut!x*2*aV(#w5FULdYaRSVo$A~>QKt^nsYCU8 zM}DR!JL=!Ej5B8l#EVoM&p*^ow8;Z)@&$s)Ut<`*bEY9BzOnM4aVTHy;O9Ebwr+3S ze((hZMCw**Bh?*!`|+pU^vn^OJ&Pk8@3_0>BYuo-JVAI%+t4=k-;mO&?M@t{$uN)q E05NjFKmY&$ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_cpp_exception.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_cpp_exception.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f52bd440b6bd52831f8be2678135dae1344bdc1 GIT binary patch literal 1599 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 a^Dn~w_Ei+x+S~F7*$c_}wl&mbq{Kf}1b-s{ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_slp_switch.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_slp_switch.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43d6f45415c9d619619ace883e9383c4f756c407 GIT binary patch literal 1292 zcmc&z&r2IY6rR~kb~mfB_LtiB(4`b~k!I0eOKG7{sE|`zXfDgTJK0TKH_OaMiGoB? z^ytlh!D9c49@~pSOJtE!=qWdCdh*nn#Bp0Gv_18~%=_NFd2iml`Q~FjuK^|>-hcK7 zB>=v1LkKzt-R}(S02h>i3tcIIB`5(bxfvI|%kHr#E)aO2lyT*CP(m11ds+J)F<0{f zr5d9>*tbdwR;pK1k3f6p00NwG6%Wo$06?V|U=9AwepmIRG{Qw#v2r5wpW+*mkz0DA zJ=J4OW8KF=go$3R5uIXO2|az$x7o5CML49)z@biq$fApt*zqR}t!!5UTF^TDxZ=`n zZd&OEy+Uu!>QHb{Os_S8FGSH%V{F_-pox@k6;`I zI8HPV$4;FP9L9+p*?rxA$+w`(?|t^#^lS6jbZWtD!*tlw#|?7$#GVcCbI~~H5&o-_5%w<}{1d+|Oh}Coey9Ga!wj9NkLGJ`J;0C2 z7&H0v&{Z~_WFUm6C<9S<5WwM689;eozPEe-6EMFhxhDE1W#RRcA&||t(MSu8w9#Y> zP3}HBKvPY41mLE+s;+BC%D^_k6a&+fz6CQ%G6LyTm KBR8~Oi9Z0rD+6T! literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ce5edfa282f0d1dc6af756cf447749ff6fe8c61 GIT binary patch literal 1708 zcmaJ>&2Jk;6rWkouI;r;-A(JLG_5w3LS!IbC8*+pK!qj}qROEv2w6zhW_RKb?6oyJ zPO()aBZ>$Tg5268LR?V9f53?&Tw=wQ(^Z5bK@Yj05Z`*@&8*jPQk5soo1K~WHNW4R z{o~v@9fAD*+n?oMV+j2v7LmYO%J3hU9wHYNkc(ZVh6|tz3XGT=vx!R{pn~eEpfy(m z9aj*=)y&9yVxsCc4wo278)f2f^hveIrl7pgCO)=_QKdTdd=z(VAv5W2BJ#lK1s=&0 zi?))}J3r_j`qVdVlQY|)=6cI>c%|W)ykYwHD%>fXB}S=Nqa2L5+4AUTP3my!n!Hk{ zeqPt#2jen5WVXY zdjg9%jA0GbL&QgLBOtDX@1q~_FWPqqBR&S6WnIO_T=+?;3hy(rJz9g`Nxax(yf(^_ z)j+*_=jMCME1h)m_VUe-mQA_Lkw9&-ipPU=Sb32*ilwDMfuV&vIUEq1m3&`BH=nIp z-P$FWpT?*5re`+O`w9K~ z#6=|? zpTNf?9o2FF!@)=*5Ub<25S;)?|NANcM!cFMOmq=DFO>NANeF&Mva0CPO9Ewf2~MxU z@UMdEqQ6OcK&E?S8X9gu%pNfZWWGn{ACsJPJ`~TQlYSRE-K=NVkfcIQY(IQDh&i{$3)gY*-mcD+8jw1<>%>#MXZ@;~qLU=RQR literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd0ffdb49588656a0e6697d38ad2800c09bfed30 GIT binary patch literal 2562 zcmbVN&2Jk;6rWx1hriZo*7+=LvMGefg4&I%UJ#-xB!MVVq_!y_U#!jU#2<0i*6cdP zs}?x|Dd7UXHQcJ+P=wUKfCERk*s_tsDngNf1GkdgdV!hs?%F91sXEfUotgLMH#6_| z-rJuAAqa5$>Brx)EDONzWa2i)8Jrwb06YRJNCA~n=>nC)GDYL=Q++a%0V!5xz62>w zWwGQ{4og1`Ae9$gsO}eX`xuQh3fMMuXVxnr@7kp3Gj05yjStfK;okk7vD!rmK{h|$ z$HEUo>$Hnw-$zgbFtqR7i2`RJ2i-lXvyel5ki##5ES!rRIgJc`{ocOsJoe1{4V*^7 zUKZ2~knO&_LdOQpd%@-029(z2Tp_LB&lyTKZDbLI=?oTH0UG+-(puS&j4YHCxloXD zno*Q8vuRY;r2EBkL6v01C}T}UT7$tYxsX%i!N17mR#uf>LU8N>9`}p}p)G1jBBsFM}xZJip3g8Rv;p=VzH}tZhbJnU=C)I(mE<`O15sjtSM$Vusnb3<9W%ph;bH=pk6K* zy2Z(*64X?SEg|eSE9^SBn9d|E8h5U<5~D0eMj2fvc_Ten64~M=Oen>|S|y=mWy7c> zwxG6^DCE`>rHYX)YS-hl$%LLW;MJ0>Y{(g?CtSi32Gk8b@jr|v<+#<&%e%gjocL^H6cIr(9R>_F)xiy6UsdtQ`K!BJvnS-eLJ&Dy?7!8cG=vdyDJrLnNYOwB^HRS@r?xjzTA4DoKbu z&BB)<8Hz^mWE6LO1&c1VAMGi%n=R&b!s=TsX5>)3*cPW7;&fA-X^ZiO7;lR=8sd$o zn@w@9y5vMeedvwH8}(?i#aurK4WR)H*`MMcu;$e%&U@L6VVvfX*@x)9IqrqYLkj=F zxK|@IX%p(Om?REez%)Af@6nk2FO7rv+KBubQIrTm846$#qtTGN`T}1zJQ~B=3EcH1 zEUMrSClx)ugyFUzH3X?CTxkojh7fBD*BZjLra>9kr@(rH9oglpg|BI#*6r;`Sx5ZPgC`@-_;(CAcQ;zW!3teYp_ z0XqckfPe0PceALL3-CQe+(!zJ{tp(tNV%6UUgAFiPi?zU|BX*?vX`3Bsup_mtCObh8UR*h1xShrA z#a*q*T&_|FfZvI1M{0q++1<}uVB%N6R<)Yk0%Hf~+10t%_HMVpq+?i*Of;FvE@Ei! ac70&71tdrR#Zn&~IWAwD`2}3G9rQ14B?i3! literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d0173f720cb98f10c517ec97c10b58ed47a4c06 GIT binary patch literal 1679 zcmaJ>&u<$=6rNenuI;r8PSe^%3T0CSkp;~v;aVXTnn)2<4pl+O7qi_R+iUDyYj&Jq z3n5nuYLO7+R*tAQRPj%6giEZta#%$u66yg?CBF5)YU5 zqjT}^8hbQOP+@*ky4Y>TsK+N6MG?#MA)4uN6$q@kP&Fz}1N|@2-_Djj+c|+t64s~z z>LKD|s1cA>#b2UF_-E}qgb|;B&hn08VJ_mN)J621Oo5!qmzd4hK}E6?s&{VR_;7Qp zn@!)^yz$AVA+JResx9VvJj_NfFLS?K-3S#}T4X8WkXWo51md;Ei_grO-=wDPH|};! zyJm6TF?XrAYc|}wW~;+%zW3hR`i2>}oL*~L_UBfW24)PC$!Wj?vtqf8a$Il^D#l!` zwK`#1QYsi_87TP*tl%1`9y%uJ2djr<>EPxQvh*yucvM(^kUdW6->1Jxf143s%|o*C z!_5I%89@L#1HSmJh&6fy>MZ#2tYReLR>46^U5iiCJ%+oUn%d%1UA%WrVt=4@N_S#f zamGrfR-jDX>84>utLtUf_U?3Zw<&KkIpC40yR>CB8m?D0{7S{3o&)>XyQneI0~5~= zQ!%)qUZs3I%c0h?#7r~M#ZQQ3g}eBl3AHFYy9k#&tl&DR2>N#hB!9A4=-oOd*&!+P zNdagW5~ELyAzAH{)d49235WXffV?gbZ#^Y%%iQNP@d2a;CO}707yQi041N<{9DD{T zWv6@jJS;Yf2^i#c*<%FA0wh1v>xKZAT?X?NSivPwqF3$y_Py=>k7N%kPsvpo(=D7+ zq-nWcF%u@sWyiP6WhSPTi8M_7R3!s)iY1=1P?!oRmm~hthrLK$Z~9KVLEmSuK^8GL z!B?Q3CosnUC@LnSEW)`HHQiH=ggSZkQZIW-G<@|mr{UGpoPytco{$TfsdPM}!TTYO2KYcH~pZOiBhuRwhWE@47uQL*ae*s+xUtItI literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/leakcheck.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/leakcheck.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dad6b96fd0afaa3d9c079b39bf26633bcb268708 GIT binary patch literal 11686 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_contextvars.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_contextvars.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a065f0469d4619af5c5d106e272d7ba91d1ed8e4 GIT binary patch literal 15463 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_cpp.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_cpp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e986ceb63a7e36c3042adefad674370629444ff4 GIT binary patch literal 4093 zcmcInUu+Y}8K3=Qdu`T99P-D|01hb}<{&9hBzl70rEmvn?g9rK1Q)d0>`vk}_O6*- zAF*YtIf?2V2`QjH%O#vn;;G;4dc6S* zy_b%qnQv#l`Mz&{^ZgnBBN~k$Xn+6uU+J%adQ3mO7HAUdcYs(#DpHv&$}uU1p)?CL zn_?ZB%W^3W=C~}M;+-*&3aGpq$OdzvR0w!@H3+nj5*#`NbT}1e5Q|PCRk(rFFc$7{ zuD(NS%mpa+%w1@?=?vV6}cjp-1LNgCmFjVPc+?<_3cEv(WP@2R)n}`XE>z%5-fM;LJ-w zeh)RUqj@$4-F$;bMU-SGp3tKu1v@}weFodY48d5>VvEp&+P%%IS8$%XiDX{aEG2CR zy;08Rv98)-tQT^a$QHIY$)<^k_3pKToK5mJZ{qB<9rmP}Hlx|hj7gOypglW1k~VU9 zL@}~c#StYfTUK%8W2}EXlGUb0@i4{tLY<@x zN!qiXLB9-TwtIa3SP{VHLI2yMywIwIVSUJ(^~bW1`Jw;iQRT3&ul+tX06TYk{QkIM zU)%L_Xb2rZCUYIi%_YRZ@1{1(T|(EHWN?B|cPsTy%UYI33uN?WSVP>D0tIV&|B)nX z3s_eSRnuo6$}JTWQrhIY=pz zG_7T^B-s%>iUPi3(k(M(M@Em>DE?6nAGqwK&Yx+&zWteM!fBW7VLPYeZ{rrmv9 z&StTC#{GPk5QEsFlE%ta$tYO)f@KHKoj-B3LyfMU#+Jmx{=s_xo?8E&Z$o^v<2w#`Ry$Bf zY~jlMl`7ka57gshwfNX_e7qWM2;xF?K6-0vF?T!n#g!G|P;+Lf@9U7=Blqd-Z-(1`|GyAiQS>+UIR3-I?45CKrA8%y4G6&CYsG^ zrQ>#~)=QD_l0tWxi6lpkz&^K3{@{f&W2K34UD6G3aP=4&vCfw`Y?Xde->oWQ` z>^h}c8fjhh_uH@^RG2h3&Y^z`VqHkqgyhQJWA(k4YI`r;7gA&kto=W6A#Z_#=a7lF zMFwCYN2#$JKpA@BjKK3UBKuy2NIUKWR6{#BF4Xp3xG!9E;3y4EPENj+IG=`$rW)8x z=!TV0h-{`4hMu^n>8f%4`IjXv!;UZg9Eex>lK;Z2FyL*J|AnTq@V0Au9QCOrH(~cd zPL*U+^4>R+n`cT}&Qpid6Vt@VC1gF(dW#PyS=SARK|0VhJCPqjXHvX6i zUP>$C|3=(Flbx{lQZ)T2YgGiu$UeeO{x_413*(DU`Q{7Ay-r5y$FBnGb-RvJd}0nh zt(aZ%-E$%-5_)PoB1t(zEjV>llqCARcFU#n;&rV*28`q#$}JRh&7!tLVY;g2$EJxu z*Qhe`9&~q+gR+y32(3U)M^mUd3f&E~#)bm>*1CHFTh@lzz%Q6JfelPDaD&0#xyetZ zPo!JtmbSjV9C+s&J`#wm2T*X7S#`)bWpt%e!L+XDWI$X>M(H7@3s8Td$`cwAgvJtq zKd^|5(XInJ+s&zjzHD8q9s3**cq)mE(-oS^9P^(EM_pikO3uI>%_t^4;wr~5%pg1=@Ugj5pMLPk2fsSK K${~KMqvyZ)gvh-B literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_extension_interface.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_extension_interface.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4eb19edc03552f63690a938342a9ccfaf291c7c6 GIT binary patch literal 7451 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 BtnL5+ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_generator.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_generator.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16468574d8327408d99f0e0f441e942e00e9783c GIT binary patch literal 3067 zcma)8TW=dh6rNe{I_vn7OKlT3O=C4&x-G=L2xz5J2)(5eq*7bdU;$?9-8fF2bvnCl zapXv86)83pnyRf5BqSunOO+QM_ys(|OF}4hRuMviKIAR61FHJOIlEpbPAlL@duDd# z%$zyjoXh;=_cs%uAAk65xXcss2mUxD)H>Dk(3vF)QD}yYk~pD6;S^Vfj&gC15{{fA z3O_{@K_&ApdrjP>xTi>*SDJtemV^ir9+j(0xbw}DcNN7YYKkeQs#-=hR%vbEys8_= zWL-_#8`>Nvk*Y5Pnr?#CAaBLSqcV{&L+4K z2VV<()hMu8QlJHrfSyeS4*u%Wd2){qNSw*bETxQZ zpS%|v&W@_FWHxg#A4?9)hLMk5QMD_vO!{JMEN={FwS&F;55)Aep}sOECojnAQe|HG%e&Tap zC9#L${WaK!S7zDgh7Q_y&OXgO@H9)mjuJJ^h^V2mMntUv1XFJU24`s5R`fot2raM_ z4MS&^7S? zxeWGwiNxicoUvzE2W-s($l8&GknI3w@~EE$@gMKk@)CwvSqv}Ma*rue_P0%*tZFL>>^y&P9I_y~Hgb#=c&=l@>a1T~zT2ytQHDJ+_=U8ugN zKqtKhdcH*pov*pXby}b!R;G34?Dju*q&HyCXRRE;xYQMa6YYOlxQ|* z=xIfb%27MSq$bOK*mJOf*__tXnr_HiQnkP}eP?r;kseh~FqUQZ6-7W0a-VQT&g4{+ zrW)KwcpsIp7eQtxeEL~n;4fn91F>^a>|7GNCi_+b9dm78o%|)xQ}+6bhgaHnE_A=X z)P88ecc|?1-|R2-7wLKvX^EiJ{Xox}NCfwE|F!<(i?d2A0ToQ7M( zI#VP=RH0L%!Wl4XlT&jbpdgqe*Fe&l!Pbe|Y8Za*(v7lmfqVy>#a&Q7xFZtP55%w4Ze&!xRUFjUUQsNh{w>+L_K%U&JI3*$tnLj`4VkECTSD}ug<&NM$uQsh&<$lK_HUpu=)q&Qv) zqqxE%g>y!alMka%YdEYmifhv-B%cXai65!QTx}^dg}NLiH$ptQv5UugglKJ{60dM6 zc%OVj$6+5QQmyz98s7q#5-{IYKLUn+u*rmaV8LPGjwfh|8>ySJp>e}hN;ano$0#^# zl;2bw1tx1L)pW~aW2$CH5TH&qlcw=yp=!1FgzY=*1<*9UyYWtX@u{%skx+Ne)IpHe z(Z9eQRzln7_TL_?5Y8W&q2<=#toN38uJ2A@r8P7o-xVJTFkyMfDw?&5kW0-B^P0^N z9ENPw8asr)uQ_bhnmr7^5C`aqY(KICz)Y{K>nbx&VA7WayZoaaCQX=hguMz~9ks!u zDf*TjB7sJe#=HL2;(fS-3=GUHx3m@cb>8KPtc%1So{>L$r$|@Y!ZVj|cYy7l?qc6^ z2>;Gk2=#YwLgH@VkqbuFTp+aOCLaF-v13u}m=)klxfnZdsZw$>5;0lbS;-2eap literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_generator_nested.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_generator_nested.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b694386c41c4c32040963cf7f5736518535f9ac GIT binary patch literal 7805 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_greenlet.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_greenlet.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4ff86385432b7e5868aacd57de41f4d353473c9 GIT binary patch literal 75519 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_greenlet_trash.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_greenlet_trash.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6cb1f9694f494ddd7f7b561d23a661966a432b2e GIT binary patch literal 6764 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_leaks.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_leaks.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba165233acf02da158e4bbb43ce85be5fbc39e5b GIT binary patch literal 19958 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 Vt3PM0m#y{l)*x8qt$woo{~rR8*r5Ob literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_stack_saved.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_stack_saved.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b011a10bd44f83a5837affb259ff67f6d4e1d7f6 GIT binary patch literal 1331 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 xfyZhq$Ca;p(mdy%71s$qSCRs8OH-L&yBe0 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_version.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_version.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e8211c709b4d49729de8eb1f23bb96e16dcc08e GIT binary patch literal 2550 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 +#include + +struct exception_t { + int depth; + exception_t(int depth) : depth(depth) {} +}; + +/* Functions are called via pointers to prevent inlining */ +static void (*p_test_exception_throw_nonstd)(int depth); +static void (*p_test_exception_throw_std)(); +static PyObject* (*p_test_exception_switch_recurse)(int depth, int left); + +static void +test_exception_throw_nonstd(int depth) +{ + throw exception_t(depth); +} + +static void +test_exception_throw_std() +{ + throw std::runtime_error("Thrown from an extension."); +} + +static PyObject* +test_exception_switch_recurse(int depth, int left) +{ + if (left > 0) { + return p_test_exception_switch_recurse(depth, left - 1); + } + + PyObject* result = NULL; + PyGreenlet* self = PyGreenlet_GetCurrent(); + if (self == NULL) + return NULL; + + try { + if (PyGreenlet_Switch(PyGreenlet_GET_PARENT(self), NULL, NULL) == NULL) { + Py_DECREF(self); + return NULL; + } + p_test_exception_throw_nonstd(depth); + PyErr_SetString(PyExc_RuntimeError, + "throwing C++ exception didn't work"); + } + catch (const exception_t& e) { + if (e.depth != depth) + PyErr_SetString(PyExc_AssertionError, "depth mismatch"); + else + result = PyLong_FromLong(depth); + } + catch (...) { + PyErr_SetString(PyExc_RuntimeError, "unexpected C++ exception"); + } + + Py_DECREF(self); + return result; +} + +/* test_exception_switch(int depth) + * - recurses depth times + * - switches to parent inside try/catch block + * - throws an exception that (expected to be caught in the same function) + * - verifies depth matches (exceptions shouldn't be caught in other greenlets) + */ +static PyObject* +test_exception_switch(PyObject* UNUSED(self), PyObject* args) +{ + int depth; + if (!PyArg_ParseTuple(args, "i", &depth)) + return NULL; + return p_test_exception_switch_recurse(depth, depth); +} + + +static PyObject* +py_test_exception_throw_nonstd(PyObject* self, PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) + return NULL; + p_test_exception_throw_nonstd(0); + PyErr_SetString(PyExc_AssertionError, "unreachable code running after throw"); + return NULL; +} + +static PyObject* +py_test_exception_throw_std(PyObject* self, PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) + return NULL; + p_test_exception_throw_std(); + PyErr_SetString(PyExc_AssertionError, "unreachable code running after throw"); + return NULL; +} + +static PyObject* +py_test_call(PyObject* self, PyObject* arg) +{ + PyObject* noargs = PyTuple_New(0); + PyObject* ret = PyObject_Call(arg, noargs, nullptr); + Py_DECREF(noargs); + return ret; +} + + + +/* test_exception_switch_and_do_in_g2(g2func) + * - creates new greenlet g2 to run g2func + * - switches to g2 inside try/catch block + * - verifies that no exception has been caught + * + * it is used together with test_exception_throw to verify that unhandled + * exceptions thrown in one greenlet do not propagate to other greenlet nor + * segfault the process. + */ +static PyObject* +test_exception_switch_and_do_in_g2(PyObject* self, PyObject* args) +{ + PyObject* g2func = NULL; + PyObject* result = NULL; + + if (!PyArg_ParseTuple(args, "O", &g2func)) + return NULL; + PyGreenlet* g2 = PyGreenlet_New(g2func, NULL); + if (!g2) { + return NULL; + } + + try { + result = PyGreenlet_Switch(g2, NULL, NULL); + if (!result) { + return NULL; + } + } + catch (const exception_t& e) { + /* if we are here the memory can be already corrupted and the program + * might crash before below py-level exception might become printed. + * -> print something to stderr to make it clear that we had entered + * this catch block. + * See comments in inner_bootstrap() + */ +#if defined(WIN32) || defined(_WIN32) + fprintf(stderr, "C++ exception unexpectedly caught in g1\n"); + PyErr_SetString(PyExc_AssertionError, "C++ exception unexpectedly caught in g1"); + Py_XDECREF(result); + return NULL; +#else + throw; +#endif + } + + Py_XDECREF(result); + Py_RETURN_NONE; +} + +static PyMethodDef test_methods[] = { + {"test_exception_switch", + (PyCFunction)&test_exception_switch, + METH_VARARGS, + "Switches to parent twice, to test exception handling and greenlet " + "switching."}, + {"test_exception_switch_and_do_in_g2", + (PyCFunction)&test_exception_switch_and_do_in_g2, + METH_VARARGS, + "Creates new greenlet g2 to run g2func and switches to it inside try/catch " + "block. Used together with test_exception_throw to verify that unhandled " + "C++ exceptions thrown in a greenlet doe not corrupt memory."}, + {"test_exception_throw_nonstd", + (PyCFunction)&py_test_exception_throw_nonstd, + METH_VARARGS, + "Throws non-standard C++ exception. Calling this function directly should abort the process." + }, + {"test_exception_throw_std", + (PyCFunction)&py_test_exception_throw_std, + METH_VARARGS, + "Throws standard C++ exception. Calling this function directly should abort the process." + }, + {"test_call", + (PyCFunction)&py_test_call, + METH_O, + "Call the given callable. Unlike calling it directly, this creates a " + "new C-level stack frame, which may be helpful in testing." + }, + {NULL, NULL, 0, NULL} +}; + + +static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT, + "greenlet.tests._test_extension_cpp", + NULL, + 0, + test_methods, + NULL, + NULL, + NULL, + NULL}; + +PyMODINIT_FUNC +PyInit__test_extension_cpp(void) +{ + PyObject* module = NULL; + + module = PyModule_Create(&moduledef); + + if (module == NULL) { + return NULL; + } + + PyGreenlet_Import(); + if (_PyGreenlet_API == NULL) { + return NULL; + } + + p_test_exception_throw_nonstd = test_exception_throw_nonstd; + p_test_exception_throw_std = test_exception_throw_std; + p_test_exception_switch_recurse = test_exception_switch_recurse; + + return module; +} diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/_test_extension_cpp.cpython-312-x86_64-linux-gnu.so b/venv/lib/python3.12/site-packages/greenlet/tests/_test_extension_cpp.cpython-312-x86_64-linux-gnu.so new file mode 100755 index 0000000000000000000000000000000000000000..d5513ccb51d7498e95d01be1528e40e1ffa1d242 GIT binary patch literal 58384 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 appearing to return, even though +# it has yet to run. +print('In main with', x, flush=True) +g.switch() +print('RESULTS', results) diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/fail_cpp_exception.py b/venv/lib/python3.12/site-packages/greenlet/tests/fail_cpp_exception.py new file mode 100644 index 0000000..fa4dc2e --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/fail_cpp_exception.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +""" +Helper for testing a C++ exception throw aborts the process. + +Takes one argument, the name of the function in :mod:`_test_extension_cpp` to call. +""" +import sys +import greenlet +from greenlet.tests import _test_extension_cpp +print('fail_cpp_exception is running') + +def run_unhandled_exception_in_greenlet_aborts(): + def _(): + _test_extension_cpp.test_exception_switch_and_do_in_g2( + _test_extension_cpp.test_exception_throw_nonstd + ) + g1 = greenlet.greenlet(_) + g1.switch() + + +func_name = sys.argv[1] +try: + func = getattr(_test_extension_cpp, func_name) +except AttributeError: + if func_name == run_unhandled_exception_in_greenlet_aborts.__name__: + func = run_unhandled_exception_in_greenlet_aborts + elif func_name == 'run_as_greenlet_target': + g = greenlet.greenlet(_test_extension_cpp.test_exception_throw_std) + func = g.switch + else: + raise +print('raising', func, flush=True) +func() diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/fail_initialstub_already_started.py b/venv/lib/python3.12/site-packages/greenlet/tests/fail_initialstub_already_started.py new file mode 100644 index 0000000..c1a44ef --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/fail_initialstub_already_started.py @@ -0,0 +1,78 @@ +""" +Testing initialstub throwing an already started exception. +""" + +import greenlet + +a = None +b = None +c = None +main = greenlet.getcurrent() + +# If we switch into a dead greenlet, +# we go looking for its parents. +# if a parent is not yet started, we start it. + +results = [] + +def a_run(*args): + #results.append('A') + results.append(('Begin A', args)) + + +def c_run(): + results.append('Begin C') + b.switch('From C') + results.append('C done') + +class A(greenlet.greenlet): pass + +class B(greenlet.greenlet): + doing_it = False + def __getattribute__(self, name): + if name == 'run' and not self.doing_it: + assert greenlet.getcurrent() is c + self.doing_it = True + results.append('Switch to b from B.__getattribute__ in ' + + type(greenlet.getcurrent()).__name__) + b.switch() + results.append('B.__getattribute__ back from main in ' + + type(greenlet.getcurrent()).__name__) + if name == 'run': + name = '_B_run' + return object.__getattribute__(self, name) + + def _B_run(self, *arg): + results.append(('Begin B', arg)) + results.append('_B_run switching to main') + main.switch('From B') + +class C(greenlet.greenlet): + pass +a = A(a_run) +b = B(parent=a) +c = C(c_run, b) + +# Start a child; while running, it will start B, +# but starting B will ALSO start B. +result = c.switch() +results.append(('main from c', result)) + +# Switch back to C, which was in the middle of switching +# already. This will throw the ``GreenletStartedWhileInPython`` +# exception, which results in parent A getting started (B is finished) +c.switch() + +results.append(('A dead?', a.dead, 'B dead?', b.dead, 'C dead?', c.dead)) + +# A and B should both be dead now. +assert a.dead +assert b.dead +assert not c.dead + +result = c.switch() +results.append(('main from c.2', result)) +# Now C is dead +assert c.dead + +print("RESULTS:", results) diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/fail_slp_switch.py b/venv/lib/python3.12/site-packages/greenlet/tests/fail_slp_switch.py new file mode 100644 index 0000000..0990526 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/fail_slp_switch.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +""" +A test helper for seeing what happens when slp_switch() +fails. +""" +# pragma: no cover + +import greenlet + + +print('fail_slp_switch is running', flush=True) + +runs = [] +def func(): + runs.append(1) + greenlet.getcurrent().parent.switch() + runs.append(2) + greenlet.getcurrent().parent.switch() + runs.append(3) + +g = greenlet._greenlet.UnswitchableGreenlet(func) +g.switch() +assert runs == [1] +g.switch() +assert runs == [1, 2] +g.force_slp_switch_error = True + +# This should crash. +g.switch() diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_three_greenlets.py b/venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_three_greenlets.py new file mode 100644 index 0000000..e151b19 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_three_greenlets.py @@ -0,0 +1,44 @@ +""" +Uses a trace function to switch greenlets at unexpected times. + +In the trace function, we switch from the current greenlet to another +greenlet, which switches +""" +import greenlet + +g1 = None +g2 = None + +switch_to_g2 = False + +def tracefunc(*args): + print('TRACE', *args) + global switch_to_g2 + if switch_to_g2: + switch_to_g2 = False + g2.switch() + print('\tLEAVE TRACE', *args) + +def g1_run(): + print('In g1_run') + global switch_to_g2 + switch_to_g2 = True + from_parent = greenlet.getcurrent().parent.switch() + print('Return to g1_run') + print('From parent', from_parent) + +def g2_run(): + #g1.switch() + greenlet.getcurrent().parent.switch() + +greenlet.settrace(tracefunc) + +g1 = greenlet.greenlet(g1_run) +g2 = greenlet.greenlet(g2_run) + +# This switch didn't actually finish! +# And if it did, it would raise TypeError +# because g1_run() doesn't take any arguments. +g1.switch(1) +print('Back in main') +g1.switch(2) diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_three_greenlets2.py b/venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_three_greenlets2.py new file mode 100644 index 0000000..1f6b66b --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_three_greenlets2.py @@ -0,0 +1,55 @@ +""" +Like fail_switch_three_greenlets, but the call into g1_run would actually be +valid. +""" +import greenlet + +g1 = None +g2 = None + +switch_to_g2 = True + +results = [] + +def tracefunc(*args): + results.append(('trace', args[0])) + print('TRACE', *args) + global switch_to_g2 + if switch_to_g2: + switch_to_g2 = False + g2.switch('g2 from tracefunc') + print('\tLEAVE TRACE', *args) + +def g1_run(arg): + results.append(('g1 arg', arg)) + print('In g1_run') + from_parent = greenlet.getcurrent().parent.switch('from g1_run') + results.append(('g1 from parent', from_parent)) + return 'g1 done' + +def g2_run(arg): + #g1.switch() + results.append(('g2 arg', arg)) + parent = greenlet.getcurrent().parent.switch('from g2_run') + global switch_to_g2 + switch_to_g2 = False + results.append(('g2 from parent', parent)) + return 'g2 done' + + +greenlet.settrace(tracefunc) + +g1 = greenlet.greenlet(g1_run) +g2 = greenlet.greenlet(g2_run) + +x = g1.switch('g1 from main') +results.append(('main g1', x)) +print('Back in main', x) +x = g1.switch('g2 from main') +results.append(('main g2', x)) +print('back in amain again', x) +x = g1.switch('g1 from main 2') +results.append(('main g1.2', x)) +x = g2.switch() +results.append(('main g2.2', x)) +print("RESULTS:", results) diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_two_greenlets.py b/venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_two_greenlets.py new file mode 100644 index 0000000..3e52345 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_two_greenlets.py @@ -0,0 +1,41 @@ +""" +Uses a trace function to switch greenlets at unexpected times. + +In the trace function, we switch from the current greenlet to another +greenlet, which switches +""" +import greenlet + +g1 = None +g2 = None + +switch_to_g2 = False + +def tracefunc(*args): + print('TRACE', *args) + global switch_to_g2 + if switch_to_g2: + switch_to_g2 = False + g2.switch() + print('\tLEAVE TRACE', *args) + +def g1_run(): + print('In g1_run') + global switch_to_g2 + switch_to_g2 = True + greenlet.getcurrent().parent.switch() + print('Return to g1_run') + print('Falling off end of g1_run') + +def g2_run(): + g1.switch() + print('Falling off end of g2') + +greenlet.settrace(tracefunc) + +g1 = greenlet.greenlet(g1_run) +g2 = greenlet.greenlet(g2_run) + +g1.switch() +print('Falling off end of main') +g2.switch() diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/leakcheck.py b/venv/lib/python3.12/site-packages/greenlet/tests/leakcheck.py new file mode 100644 index 0000000..7046e41 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/leakcheck.py @@ -0,0 +1,336 @@ +# Copyright (c) 2018 gevent community +# Copyright (c) 2021 greenlet community +# +# This was originally part of gevent's test suite. The main author +# (Jason Madden) vendored a copy of it into greenlet. +# +# 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 print_function + +import os +import sys +import gc + +from functools import wraps +import unittest + + +import objgraph + +# graphviz 0.18 (Nov 7 2021), available only on Python 3.6 and newer, +# has added type hints (sigh). It wants to use ``typing.Literal`` for +# some stuff, but that's only available on Python 3.9+. If that's not +# found, it creates a ``unittest.mock.MagicMock`` object and annotates +# with that. These are GC'able objects, and doing almost *anything* +# with them results in an explosion of objects. For example, trying to +# compare them for equality creates new objects. This causes our +# leakchecks to fail, with reports like: +# +# greenlet.tests.leakcheck.LeakCheckError: refcount increased by [337, 1333, 343, 430, 530, 643, 769] +# _Call 1820 +546 +# dict 4094 +76 +# MagicProxy 585 +73 +# tuple 2693 +66 +# _CallList 24 +3 +# weakref 1441 +1 +# function 5996 +1 +# type 736 +1 +# cell 592 +1 +# MagicMock 8 +1 +# +# To avoid this, we *could* filter this type of object out early. In +# principle it could leak, but we don't use mocks in greenlet, so it +# doesn't leak from us. However, a further issue is that ``MagicMock`` +# objects have subobjects that are also GC'able, like ``_Call``, and +# those create new mocks of their own too. So we'd have to filter them +# as well, and they're not public. That's OK, we can workaround the +# problem by being very careful to never compare by equality or other +# user-defined operators, only using object identity or other builtin +# functions. + +RUNNING_ON_GITHUB_ACTIONS = os.environ.get('GITHUB_ACTIONS') +RUNNING_ON_TRAVIS = os.environ.get('TRAVIS') or RUNNING_ON_GITHUB_ACTIONS +RUNNING_ON_APPVEYOR = os.environ.get('APPVEYOR') +RUNNING_ON_CI = RUNNING_ON_TRAVIS or RUNNING_ON_APPVEYOR +RUNNING_ON_MANYLINUX = os.environ.get('GREENLET_MANYLINUX') +SKIP_LEAKCHECKS = RUNNING_ON_MANYLINUX or os.environ.get('GREENLET_SKIP_LEAKCHECKS') +SKIP_FAILING_LEAKCHECKS = os.environ.get('GREENLET_SKIP_FAILING_LEAKCHECKS') +ONLY_FAILING_LEAKCHECKS = os.environ.get('GREENLET_ONLY_FAILING_LEAKCHECKS') + +def ignores_leakcheck(func): + """ + Ignore the given object during leakchecks. + + Can be applied to a method, in which case the method will run, but + will not be subject to leak checks. + + If applied to a class, the entire class will be skipped during leakchecks. This + is intended to be used for classes that are very slow and cause problems such as + test timeouts; typically it will be used for classes that are subclasses of a base + class and specify variants of behaviour (such as pool sizes). + """ + func.ignore_leakcheck = True + return func + +def fails_leakcheck(func): + """ + Mark that the function is known to leak. + """ + func.fails_leakcheck = True + if SKIP_FAILING_LEAKCHECKS: + func = unittest.skip("Skipping known failures")(func) + return func + +class LeakCheckError(AssertionError): + pass + +if hasattr(sys, 'getobjects'): + # In a Python build with ``--with-trace-refs``, make objgraph + # trace *all* the objects, not just those that are tracked by the + # GC + class _MockGC(object): + def get_objects(self): + return sys.getobjects(0) # pylint:disable=no-member + def __getattr__(self, name): + return getattr(gc, name) + objgraph.gc = _MockGC() + fails_strict_leakcheck = fails_leakcheck +else: + def fails_strict_leakcheck(func): + """ + Decorator for a function that is known to fail when running + strict (``sys.getobjects()``) leakchecks. + + This type of leakcheck finds all objects, even those, such as + strings, which are not tracked by the garbage collector. + """ + return func + +class ignores_types_in_strict_leakcheck(object): + def __init__(self, types): + self.types = types + def __call__(self, func): + func.leakcheck_ignore_types = self.types + return func + +class _RefCountChecker(object): + + # Some builtin things that we ignore + # XXX: Those things were ignored by gevent, but they're important here, + # presumably. + IGNORED_TYPES = () #(tuple, dict, types.FrameType, types.TracebackType) + + # Names of types that should be ignored. Use this when we cannot + # or don't want to import the class directly. + IGNORED_TYPE_NAMES = ( + # This appears in Python3.14 with the JIT enabled. It + # doesn't seem to be directly exposed to Python; the only way to get + # one is to cause code to get jitted and then look for all objects + # and find one with this name. But they multiply as code + # executes and gets jitted, in ways we don't want to rely on. + # So just ignore it. + 'uop_executor', + ) + + def __init__(self, testcase, function): + self.testcase = testcase + self.function = function + self.deltas = [] + self.peak_stats = {} + self.ignored_types = () + + # The very first time we are called, we have already been + # self.setUp() by the test runner, so we don't need to do it again. + self.needs_setUp = False + + def _include_object_p(self, obj): + # pylint:disable=too-many-return-statements + # + # See the comment block at the top. We must be careful to + # avoid invoking user-defined operations. + if obj is self: + return False + kind = type(obj) + # ``self._include_object_p == obj`` returns NotImplemented + # for non-function objects, which causes the interpreter + # to try to reverse the order of arguments...which leads + # to the explosion of mock objects. We don't want that, so we implement + # the check manually. + if kind == type(self._include_object_p): + try: + # pylint:disable=not-callable + exact_method_equals = self._include_object_p.__eq__(obj) + except AttributeError: + # Python 2.7 methods may only have __cmp__, and that raises a + # TypeError for non-method arguments + # pylint:disable=no-member + exact_method_equals = self._include_object_p.__cmp__(obj) == 0 + + if exact_method_equals is not NotImplemented and exact_method_equals: + return False + + # Similarly, we need to check identity in our __dict__ to avoid mock explosions. + for x in self.__dict__.values(): + if obj is x: + return False + + + if ( + kind in self.ignored_types + or kind in self.IGNORED_TYPES + or kind.__name__ in self.IGNORED_TYPE_NAMES + ): + return False + + + return True + + def _growth(self): + return objgraph.growth(limit=None, peak_stats=self.peak_stats, + filter=self._include_object_p) + + def _report_diff(self, growth): + if not growth: + return "" + + lines = [] + width = max(len(name) for name, _, _ in growth) + for name, count, delta in growth: + lines.append('%-*s%9d %+9d' % (width, name, count, delta)) + + diff = '\n'.join(lines) + return diff + + + def _run_test(self, args, kwargs): + gc_enabled = gc.isenabled() + gc.disable() + + if self.needs_setUp: + self.testcase.setUp() + self.testcase.skipTearDown = False + try: + self.function(self.testcase, *args, **kwargs) + finally: + self.testcase.tearDown() + self.testcase.doCleanups() + self.testcase.skipTearDown = True + self.needs_setUp = True + if gc_enabled: + gc.enable() + + def _growth_after(self): + # Grab post snapshot + # pylint:disable=no-member + if 'urlparse' in sys.modules: + sys.modules['urlparse'].clear_cache() + if 'urllib.parse' in sys.modules: + sys.modules['urllib.parse'].clear_cache() + + return self._growth() + + def _check_deltas(self, growth): + # Return false when we have decided there is no leak, + # true if we should keep looping, raises an assertion + # if we have decided there is a leak. + + deltas = self.deltas + if not deltas: + # We haven't run yet, no data, keep looping + return True + + if gc.garbage: + raise LeakCheckError("Generated uncollectable garbage %r" % (gc.garbage,)) + + + # the following configurations are classified as "no leak" + # [0, 0] + # [x, 0, 0] + # [... a, b, c, d] where a+b+c+d = 0 + # + # the following configurations are classified as "leak" + # [... z, z, z] where z > 0 + + if deltas[-2:] == [0, 0] and len(deltas) in (2, 3): + return False + + if deltas[-3:] == [0, 0, 0]: + return False + + if len(deltas) >= 4 and sum(deltas[-4:]) == 0: + return False + + if len(deltas) >= 3 and deltas[-1] > 0 and deltas[-1] == deltas[-2] and deltas[-2] == deltas[-3]: + diff = self._report_diff(growth) + raise LeakCheckError('refcount increased by %r\n%s' % (deltas, diff)) + + # OK, we don't know for sure yet. Let's search for more + if sum(deltas[-3:]) <= 0 or sum(deltas[-4:]) <= 0 or deltas[-4:].count(0) >= 2: + # this is suspicious, so give a few more runs + limit = 11 + else: + limit = 7 + if len(deltas) >= limit: + raise LeakCheckError('refcount increased by %r\n%s' + % (deltas, + self._report_diff(growth))) + + # We couldn't decide yet, keep going + return True + + def __call__(self, args, kwargs): + for _ in range(3): + gc.collect() + + expect_failure = getattr(self.function, 'fails_leakcheck', False) + if expect_failure: + self.testcase.expect_greenlet_leak = True + self.ignored_types = getattr(self.function, "leakcheck_ignore_types", ()) + + # Capture state before; the incremental will be + # updated by each call to _growth_after + growth = self._growth() + + try: + while self._check_deltas(growth): + self._run_test(args, kwargs) + + growth = self._growth_after() + + self.deltas.append(sum((stat[2] for stat in growth))) + except LeakCheckError: + if not expect_failure: + raise + else: + if expect_failure: + raise LeakCheckError("Expected %s to leak but it did not." % (self.function,)) + +def wrap_refcount(method): + if getattr(method, 'ignore_leakcheck', False) or SKIP_LEAKCHECKS: + return method + + @wraps(method) + def wrapper(self, *args, **kwargs): # pylint:disable=too-many-branches + if getattr(self, 'ignore_leakcheck', False): + raise unittest.SkipTest("This class ignored during leakchecks") + if ONLY_FAILING_LEAKCHECKS and not getattr(method, 'fails_leakcheck', False): + raise unittest.SkipTest("Only running tests that fail leakchecks.") + return _RefCountChecker(self, method)(args, kwargs) + + return wrapper diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_contextvars.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_contextvars.py new file mode 100644 index 0000000..b0d1ccf --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_contextvars.py @@ -0,0 +1,312 @@ +from __future__ import print_function + +import gc +import sys +import unittest + +from functools import partial +from unittest import skipUnless +from unittest import skipIf + +from greenlet import greenlet +from greenlet import getcurrent +from . import TestCase +from . import PY314 + +try: + from contextvars import Context + from contextvars import ContextVar + from contextvars import copy_context + # From the documentation: + # + # Important: Context Variables should be created at the top module + # level and never in closures. Context objects hold strong + # references to context variables which prevents context variables + # from being properly garbage collected. + ID_VAR = ContextVar("id", default=None) + VAR_VAR = ContextVar("var", default=None) + ContextVar = None +except ImportError: + Context = ContextVar = copy_context = None + +# We don't support testing if greenlet's built-in context var support is disabled. +@skipUnless(Context is not None, "ContextVar not supported") +class ContextVarsTests(TestCase): + def _new_ctx_run(self, *args, **kwargs): + return copy_context().run(*args, **kwargs) + + def _increment(self, greenlet_id, callback, counts, expect): + ctx_var = ID_VAR + if expect is None: + self.assertIsNone(ctx_var.get()) + else: + self.assertEqual(ctx_var.get(), expect) + ctx_var.set(greenlet_id) + for _ in range(2): + counts[ctx_var.get()] += 1 + callback() + + def _test_context(self, propagate_by): + # pylint:disable=too-many-branches + ID_VAR.set(0) + + callback = getcurrent().switch + counts = dict((i, 0) for i in range(5)) + + lets = [ + greenlet(partial( + partial( + copy_context().run, + self._increment + ) if propagate_by == "run" else self._increment, + greenlet_id=i, + callback=callback, + counts=counts, + expect=( + i - 1 if propagate_by == "share" else + 0 if propagate_by in ("set", "run") else None + ) + )) + for i in range(1, 5) + ] + + for let in lets: + if propagate_by == "set": + let.gr_context = copy_context() + elif propagate_by == "share": + let.gr_context = getcurrent().gr_context + + for i in range(2): + counts[ID_VAR.get()] += 1 + for let in lets: + let.switch() + + if propagate_by == "run": + # Must leave each context.run() in reverse order of entry + for let in reversed(lets): + let.switch() + else: + # No context.run(), so fine to exit in any order. + for let in lets: + let.switch() + + for let in lets: + self.assertTrue(let.dead) + # When using run(), we leave the run() as the greenlet dies, + # and there's no context "underneath". When not using run(), + # gr_context still reflects the context the greenlet was + # running in. + if propagate_by == 'run': + self.assertIsNone(let.gr_context) + else: + self.assertIsNotNone(let.gr_context) + + + if propagate_by == "share": + self.assertEqual(counts, {0: 1, 1: 1, 2: 1, 3: 1, 4: 6}) + else: + self.assertEqual(set(counts.values()), set([2])) + + def test_context_propagated_by_context_run(self): + self._new_ctx_run(self._test_context, "run") + + def test_context_propagated_by_setting_attribute(self): + self._new_ctx_run(self._test_context, "set") + + def test_context_not_propagated(self): + self._new_ctx_run(self._test_context, None) + + def test_context_shared(self): + self._new_ctx_run(self._test_context, "share") + + def test_break_ctxvars(self): + let1 = greenlet(copy_context().run) + let2 = greenlet(copy_context().run) + let1.switch(getcurrent().switch) + let2.switch(getcurrent().switch) + # Since let2 entered the current context and let1 exits its own, the + # interpreter emits: + # RuntimeError: cannot exit context: thread state references a different context object + let1.switch() + + def test_not_broken_if_using_attribute_instead_of_context_run(self): + let1 = greenlet(getcurrent().switch) + let2 = greenlet(getcurrent().switch) + let1.gr_context = copy_context() + let2.gr_context = copy_context() + let1.switch() + let2.switch() + let1.switch() + let2.switch() + + def test_context_assignment_while_running(self): + # pylint:disable=too-many-statements + ID_VAR.set(None) + + def target(): + self.assertIsNone(ID_VAR.get()) + self.assertIsNone(gr.gr_context) + + # Context is created on first use + ID_VAR.set(1) + self.assertIsInstance(gr.gr_context, Context) + self.assertEqual(ID_VAR.get(), 1) + self.assertEqual(gr.gr_context[ID_VAR], 1) + + # Clearing the context makes it get re-created as another + # empty context when next used + old_context = gr.gr_context + gr.gr_context = None # assign None while running + self.assertIsNone(ID_VAR.get()) + self.assertIsNone(gr.gr_context) + ID_VAR.set(2) + self.assertIsInstance(gr.gr_context, Context) + self.assertEqual(ID_VAR.get(), 2) + self.assertEqual(gr.gr_context[ID_VAR], 2) + + new_context = gr.gr_context + getcurrent().parent.switch((old_context, new_context)) + # parent switches us back to old_context + + self.assertEqual(ID_VAR.get(), 1) + gr.gr_context = new_context # assign non-None while running + self.assertEqual(ID_VAR.get(), 2) + + getcurrent().parent.switch() + # parent switches us back to no context + self.assertIsNone(ID_VAR.get()) + self.assertIsNone(gr.gr_context) + gr.gr_context = old_context + self.assertEqual(ID_VAR.get(), 1) + + getcurrent().parent.switch() + # parent switches us back to no context + self.assertIsNone(ID_VAR.get()) + self.assertIsNone(gr.gr_context) + + gr = greenlet(target) + + with self.assertRaisesRegex(AttributeError, "can't delete context attribute"): + del gr.gr_context + + self.assertIsNone(gr.gr_context) + old_context, new_context = gr.switch() + self.assertIs(new_context, gr.gr_context) + self.assertEqual(old_context[ID_VAR], 1) + self.assertEqual(new_context[ID_VAR], 2) + self.assertEqual(new_context.run(ID_VAR.get), 2) + gr.gr_context = old_context # assign non-None while suspended + gr.switch() + self.assertIs(gr.gr_context, new_context) + gr.gr_context = None # assign None while suspended + gr.switch() + self.assertIs(gr.gr_context, old_context) + gr.gr_context = None + gr.switch() + self.assertIsNone(gr.gr_context) + + # Make sure there are no reference leaks + gr = None + gc.collect() + # Python 3.14 elides reference counting operations + # in some cases. See https://github.com/python/cpython/pull/130708 + self.assertEqual(sys.getrefcount(old_context), 2 if not PY314 else 1) + self.assertEqual(sys.getrefcount(new_context), 2 if not PY314 else 1) + + def test_context_assignment_different_thread(self): + import threading + VAR_VAR.set(None) + ctx = Context() + + is_running = threading.Event() + should_suspend = threading.Event() + did_suspend = threading.Event() + should_exit = threading.Event() + holder = [] + + def greenlet_in_thread_fn(): + VAR_VAR.set(1) + is_running.set() + should_suspend.wait(10) + VAR_VAR.set(2) + getcurrent().parent.switch() + holder.append(VAR_VAR.get()) + + def thread_fn(): + gr = greenlet(greenlet_in_thread_fn) + gr.gr_context = ctx + holder.append(gr) + gr.switch() + did_suspend.set() + should_exit.wait(10) + gr.switch() + del gr + greenlet() # trigger cleanup + + thread = threading.Thread(target=thread_fn, daemon=True) + thread.start() + is_running.wait(10) + gr = holder[0] + + # Can't access or modify context if the greenlet is running + # in a different thread + with self.assertRaisesRegex(ValueError, "running in a different"): + getattr(gr, 'gr_context') + with self.assertRaisesRegex(ValueError, "running in a different"): + gr.gr_context = None + + should_suspend.set() + did_suspend.wait(10) + + # OK to access and modify context if greenlet is suspended + self.assertIs(gr.gr_context, ctx) + self.assertEqual(gr.gr_context[VAR_VAR], 2) + gr.gr_context = None + + should_exit.set() + thread.join(10) + + self.assertEqual(holder, [gr, None]) + + # Context can still be accessed/modified when greenlet is dead: + self.assertIsNone(gr.gr_context) + gr.gr_context = ctx + self.assertIs(gr.gr_context, ctx) + + # Otherwise we leak greenlets on some platforms. + # XXX: Should be able to do this automatically + del holder[:] + gr = None + thread = None + + def test_context_assignment_wrong_type(self): + g = greenlet() + with self.assertRaisesRegex(TypeError, + "greenlet context must be a contextvars.Context or None"): + g.gr_context = self + + +@skipIf(Context is not None, "ContextVar supported") +class NoContextVarsTests(TestCase): + def test_contextvars_errors(self): + let1 = greenlet(getcurrent().switch) + self.assertFalse(hasattr(let1, 'gr_context')) + with self.assertRaises(AttributeError): + getattr(let1, 'gr_context') + + with self.assertRaises(AttributeError): + let1.gr_context = None + + let1.switch() + + with self.assertRaises(AttributeError): + getattr(let1, 'gr_context') + + with self.assertRaises(AttributeError): + let1.gr_context = None + + del let1 + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_cpp.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_cpp.py new file mode 100644 index 0000000..2d0cc9c --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_cpp.py @@ -0,0 +1,73 @@ +from __future__ import print_function +from __future__ import absolute_import + +import subprocess +import unittest + +import greenlet +from . import _test_extension_cpp +from . import TestCase +from . import WIN + +class CPPTests(TestCase): + def test_exception_switch(self): + greenlets = [] + for i in range(4): + g = greenlet.greenlet(_test_extension_cpp.test_exception_switch) + g.switch(i) + greenlets.append(g) + for i, g in enumerate(greenlets): + self.assertEqual(g.switch(), i) + + def _do_test_unhandled_exception(self, target): + import os + import sys + script = os.path.join( + os.path.dirname(__file__), + 'fail_cpp_exception.py', + ) + args = [sys.executable, script, target.__name__ if not isinstance(target, str) else target] + __traceback_info__ = args + with self.assertRaises(subprocess.CalledProcessError) as exc: + subprocess.check_output( + args, + encoding='utf-8', + stderr=subprocess.STDOUT + ) + + ex = exc.exception + expected_exit = self.get_expected_returncodes_for_aborted_process() + self.assertIn(ex.returncode, expected_exit) + self.assertIn('fail_cpp_exception is running', ex.output) + return ex.output + + + def test_unhandled_nonstd_exception_aborts(self): + # verify that plain unhandled throw aborts + self._do_test_unhandled_exception(_test_extension_cpp.test_exception_throw_nonstd) + + def test_unhandled_std_exception_aborts(self): + # verify that plain unhandled throw aborts + self._do_test_unhandled_exception(_test_extension_cpp.test_exception_throw_std) + + @unittest.skipIf(WIN, "XXX: This does not crash on Windows") + # Meaning the exception is getting lost somewhere... + def test_unhandled_std_exception_as_greenlet_function_aborts(self): + # verify that plain unhandled throw aborts + output = self._do_test_unhandled_exception('run_as_greenlet_target') + self.assertIn( + # We really expect this to be prefixed with "greenlet: Unhandled C++ exception:" + # as added by our handler for std::exception (see TUserGreenlet.cpp), but + # that's not correct everywhere --- our handler never runs before std::terminate + # gets called (for example, on arm32). + 'Thrown from an extension.', + output + ) + + def test_unhandled_exception_in_greenlet_aborts(self): + # verify that unhandled throw called in greenlet aborts too + self._do_test_unhandled_exception('run_unhandled_exception_in_greenlet_aborts') + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_extension_interface.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_extension_interface.py new file mode 100644 index 0000000..34b6656 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_extension_interface.py @@ -0,0 +1,115 @@ +from __future__ import print_function +from __future__ import absolute_import + +import sys + +import greenlet +from . import _test_extension +from . import TestCase + +# pylint:disable=c-extension-no-member + +class CAPITests(TestCase): + def test_switch(self): + self.assertEqual( + 50, _test_extension.test_switch(greenlet.greenlet(lambda: 50))) + + def test_switch_kwargs(self): + def adder(x, y): + return x * y + g = greenlet.greenlet(adder) + self.assertEqual(6, _test_extension.test_switch_kwargs(g, x=3, y=2)) + + def test_setparent(self): + # pylint:disable=disallowed-name + def foo(): + def bar(): + greenlet.getcurrent().parent.switch() + + # This final switch should go back to the main greenlet, since + # the test_setparent() function in the C extension should have + # reparented this greenlet. + greenlet.getcurrent().parent.switch() + raise AssertionError("Should never have reached this code") + child = greenlet.greenlet(bar) + child.switch() + greenlet.getcurrent().parent.switch(child) + greenlet.getcurrent().parent.throw( + AssertionError("Should never reach this code")) + foo_child = greenlet.greenlet(foo).switch() + self.assertEqual(None, _test_extension.test_setparent(foo_child)) + + def test_getcurrent(self): + _test_extension.test_getcurrent() + + def test_new_greenlet(self): + self.assertEqual(-15, _test_extension.test_new_greenlet(lambda: -15)) + + def test_raise_greenlet_dead(self): + self.assertRaises( + greenlet.GreenletExit, _test_extension.test_raise_dead_greenlet) + + def test_raise_greenlet_error(self): + self.assertRaises( + greenlet.error, _test_extension.test_raise_greenlet_error) + + def test_throw(self): + seen = [] + + def foo(): # pylint:disable=disallowed-name + try: + greenlet.getcurrent().parent.switch() + except ValueError: + seen.append(sys.exc_info()[1]) + except greenlet.GreenletExit: + raise AssertionError + g = greenlet.greenlet(foo) + g.switch() + _test_extension.test_throw(g) + self.assertEqual(len(seen), 1) + self.assertTrue( + isinstance(seen[0], ValueError), + "ValueError was not raised in foo()") + self.assertEqual( + str(seen[0]), + 'take that sucka!', + "message doesn't match") + + def test_non_traceback_param(self): + with self.assertRaises(TypeError) as exc: + _test_extension.test_throw_exact( + greenlet.getcurrent(), + Exception, + Exception(), + self + ) + self.assertEqual(str(exc.exception), + "throw() third argument must be a traceback object") + + def test_instance_of_wrong_type(self): + with self.assertRaises(TypeError) as exc: + _test_extension.test_throw_exact( + greenlet.getcurrent(), + Exception(), + BaseException(), + None, + ) + + self.assertEqual(str(exc.exception), + "instance exception may not have a separate value") + + def test_not_throwable(self): + with self.assertRaises(TypeError) as exc: + _test_extension.test_throw_exact( + greenlet.getcurrent(), + "abc", + None, + None, + ) + self.assertEqual(str(exc.exception), + "exceptions must be classes, or instances, not str") + + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_gc.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_gc.py new file mode 100644 index 0000000..994addb --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_gc.py @@ -0,0 +1,86 @@ +import gc + +import weakref + +import greenlet + + +from . import TestCase +from .leakcheck import fails_leakcheck +# These only work with greenlet gc support +# which is no longer optional. +assert greenlet.GREENLET_USE_GC + +class GCTests(TestCase): + def test_dead_circular_ref(self): + o = weakref.ref(greenlet.greenlet(greenlet.getcurrent).switch()) + gc.collect() + if o() is not None: + import sys + print("O IS NOT NONE.", sys.getrefcount(o())) + self.assertIsNone(o()) + self.assertFalse(gc.garbage, gc.garbage) + + def test_circular_greenlet(self): + class circular_greenlet(greenlet.greenlet): + self = None + o = circular_greenlet() + o.self = o + o = weakref.ref(o) + gc.collect() + self.assertIsNone(o()) + self.assertFalse(gc.garbage, gc.garbage) + + def test_inactive_ref(self): + class inactive_greenlet(greenlet.greenlet): + def __init__(self): + greenlet.greenlet.__init__(self, run=self.run) + + def run(self): + pass + o = inactive_greenlet() + o = weakref.ref(o) + gc.collect() + self.assertIsNone(o()) + self.assertFalse(gc.garbage, gc.garbage) + + @fails_leakcheck + def test_finalizer_crash(self): + # This test is designed to crash when active greenlets + # are made garbage collectable, until the underlying + # problem is resolved. How does it work: + # - order of object creation is important + # - array is created first, so it is moved to unreachable first + # - we create a cycle between a greenlet and this array + # - we create an object that participates in gc, is only + # referenced by a greenlet, and would corrupt gc lists + # on destruction, the easiest is to use an object with + # a finalizer + # - because array is the first object in unreachable it is + # cleared first, which causes all references to greenlet + # to disappear and causes greenlet to be destroyed, but since + # it is still live it causes a switch during gc, which causes + # an object with finalizer to be destroyed, which causes stack + # corruption and then a crash + + class object_with_finalizer(object): + def __del__(self): + pass + array = [] + parent = greenlet.getcurrent() + def greenlet_body(): + greenlet.getcurrent().object = object_with_finalizer() + try: + parent.switch() + except greenlet.GreenletExit: + print("Got greenlet exit!") + finally: + del greenlet.getcurrent().object + g = greenlet.greenlet(greenlet_body) + g.array = array + array.append(g) + g.switch() + del array + del g + greenlet.getcurrent() + gc.collect() diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_generator.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_generator.py new file mode 100644 index 0000000..ca4a644 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_generator.py @@ -0,0 +1,59 @@ + +from greenlet import greenlet + +from . import TestCase + +class genlet(greenlet): + parent = None + def __init__(self, *args, **kwds): + self.args = args + self.kwds = kwds + + def run(self): + fn, = self.fn + fn(*self.args, **self.kwds) + + def __iter__(self): + return self + + def __next__(self): + self.parent = greenlet.getcurrent() + result = self.switch() + if self: + return result + + raise StopIteration + + next = __next__ + + +def Yield(value): + g = greenlet.getcurrent() + while not isinstance(g, genlet): + if g is None: + raise RuntimeError('yield outside a genlet') + g = g.parent + g.parent.switch(value) + + +def generator(func): + class Generator(genlet): + fn = (func,) + return Generator + +# ____________________________________________________________ + + +class GeneratorTests(TestCase): + def test_generator(self): + seen = [] + + def g(n): + for i in range(n): + seen.append(i) + Yield(i) + g = generator(g) + for _ in range(3): + for j in g(5): + seen.append(j) + self.assertEqual(seen, 3 * [0, 0, 1, 1, 2, 2, 3, 3, 4, 4]) diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_generator_nested.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_generator_nested.py new file mode 100644 index 0000000..8d752a6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_generator_nested.py @@ -0,0 +1,168 @@ + +from greenlet import greenlet +from . import TestCase +from .leakcheck import fails_leakcheck + +class genlet(greenlet): + parent = None + def __init__(self, *args, **kwds): + self.args = args + self.kwds = kwds + self.child = None + + def run(self): + # Note the function is packed in a tuple + # to avoid creating a bound method for it. + fn, = self.fn + fn(*self.args, **self.kwds) + + def __iter__(self): + return self + + def set_child(self, child): + self.child = child + + def __next__(self): + if self.child: + child = self.child + while child.child: + tmp = child + child = child.child + tmp.child = None + + result = child.switch() + else: + self.parent = greenlet.getcurrent() + result = self.switch() + + if self: + return result + + raise StopIteration + + next = __next__ + +def Yield(value, level=1): + g = greenlet.getcurrent() + + while level != 0: + if not isinstance(g, genlet): + raise RuntimeError('yield outside a genlet') + if level > 1: + g.parent.set_child(g) + g = g.parent + level -= 1 + + g.switch(value) + + +def Genlet(func): + class TheGenlet(genlet): + fn = (func,) + return TheGenlet + +# ____________________________________________________________ + + +def g1(n, seen): + for i in range(n): + seen.append(i + 1) + yield i + + +def g2(n, seen): + for i in range(n): + seen.append(i + 1) + Yield(i) + +g2 = Genlet(g2) + + +def nested(i): + Yield(i) + + +def g3(n, seen): + for i in range(n): + seen.append(i + 1) + nested(i) +g3 = Genlet(g3) + + +def a(n): + if n == 0: + return + for ii in ax(n - 1): + Yield(ii) + Yield(n) +ax = Genlet(a) + + +def perms(l): + if len(l) > 1: + for e in l: + # No syntactical sugar for generator expressions + x = [Yield([e] + p) for p in perms([x for x in l if x != e])] + assert x + else: + Yield(l) +perms = Genlet(perms) + + +def gr1(n): + for ii in range(1, n): + Yield(ii) + Yield(ii * ii, 2) + +gr1 = Genlet(gr1) + + +def gr2(n, seen): + for ii in gr1(n): + seen.append(ii) + +gr2 = Genlet(gr2) + + +class NestedGeneratorTests(TestCase): + def test_layered_genlets(self): + seen = [] + for ii in gr2(5, seen): + seen.append(ii) + self.assertEqual(seen, [1, 1, 2, 4, 3, 9, 4, 16]) + + @fails_leakcheck + def test_permutations(self): + gen_perms = perms(list(range(4))) + permutations = list(gen_perms) + self.assertEqual(len(permutations), 4 * 3 * 2 * 1) + self.assertIn([0, 1, 2, 3], permutations) + self.assertIn([3, 2, 1, 0], permutations) + res = [] + for ii in zip(perms(list(range(4))), perms(list(range(3)))): + res.append(ii) + self.assertEqual( + res, + [([0, 1, 2, 3], [0, 1, 2]), ([0, 1, 3, 2], [0, 2, 1]), + ([0, 2, 1, 3], [1, 0, 2]), ([0, 2, 3, 1], [1, 2, 0]), + ([0, 3, 1, 2], [2, 0, 1]), ([0, 3, 2, 1], [2, 1, 0])]) + # XXX Test to make sure we are working as a generator expression + + def test_genlet_simple(self): + for g in g1, g2, g3: + seen = [] + for _ in range(3): + for j in g(5, seen): + seen.append(j) + self.assertEqual(seen, 3 * [1, 0, 2, 1, 3, 2, 4, 3, 5, 4]) + + def test_genlet_bad(self): + try: + Yield(10) + except RuntimeError: + pass + + def test_nested_genlets(self): + seen = [] + for ii in ax(5): + seen.append(ii) diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_greenlet.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_greenlet.py new file mode 100644 index 0000000..1fa4bd1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_greenlet.py @@ -0,0 +1,1353 @@ +import gc +import sys +import time +import threading +import unittest + +from abc import ABCMeta +from abc import abstractmethod + +import greenlet +from greenlet import greenlet as RawGreenlet +from . import TestCase +from . import RUNNING_ON_MANYLINUX +from . import PY313 +from . import PY314 +from . import RUNNING_ON_FREETHREAD_BUILD +from .leakcheck import fails_leakcheck + + +# We manually manage locks in many tests +# pylint:disable=consider-using-with +# pylint:disable=too-many-public-methods +# This module is quite large. +# TODO: Refactor into separate test files. For example, +# put all the regression tests that used to produce +# crashes in test_greenlet_no_crash; put tests that DO deliberately crash +# the interpreter into test_greenlet_crash. +# pylint:disable=too-many-lines + +class SomeError(Exception): + pass + + +def fmain(seen): + try: + greenlet.getcurrent().parent.switch() + except: + seen.append(sys.exc_info()[0]) + raise + raise SomeError + + +def send_exception(g, exc): + # note: send_exception(g, exc) can be now done with g.throw(exc). + # the purpose of this test is to explicitly check the propagation rules. + def crasher(exc): + raise exc + g1 = RawGreenlet(crasher, parent=g) + g1.switch(exc) + + +class TestGreenlet(TestCase): + + def _do_simple_test(self): + lst = [] + + def f(): + lst.append(1) + greenlet.getcurrent().parent.switch() + lst.append(3) + g = RawGreenlet(f) + lst.append(0) + g.switch() + lst.append(2) + g.switch() + lst.append(4) + self.assertEqual(lst, list(range(5))) + + def test_simple(self): + self._do_simple_test() + + def test_switch_no_run_raises_AttributeError(self): + g = RawGreenlet() + with self.assertRaises(AttributeError) as exc: + g.switch() + + self.assertIn("run", str(exc.exception)) + + def test_throw_no_run_raises_AttributeError(self): + g = RawGreenlet() + with self.assertRaises(AttributeError) as exc: + g.throw(SomeError) + + self.assertIn("run", str(exc.exception)) + + def test_parent_equals_None(self): + g = RawGreenlet(parent=None) + self.assertIsNotNone(g) + self.assertIs(g.parent, greenlet.getcurrent()) + + def test_run_equals_None(self): + g = RawGreenlet(run=None) + self.assertIsNotNone(g) + self.assertIsNone(g.run) + + def test_two_children(self): + lst = [] + + def f(): + lst.append(1) + greenlet.getcurrent().parent.switch() + lst.extend([1, 1]) + g = RawGreenlet(f) + h = RawGreenlet(f) + g.switch() + self.assertEqual(len(lst), 1) + h.switch() + self.assertEqual(len(lst), 2) + h.switch() + self.assertEqual(len(lst), 4) + self.assertEqual(h.dead, True) + g.switch() + self.assertEqual(len(lst), 6) + self.assertEqual(g.dead, True) + + def test_two_recursive_children(self): + lst = [] + + def f(): + lst.append('b') + greenlet.getcurrent().parent.switch() + + def g(): + lst.append('a') + g = RawGreenlet(f) + g.switch() + lst.append('c') + self.assertEqual(sys.getrefcount(g), 2 if not PY314 else 1) + g = RawGreenlet(g) + # Python 3.14 elides reference counting operations + # in some cases. See https://github.com/python/cpython/pull/130708 + self.assertEqual(sys.getrefcount(g), 2 if not PY314 else 1) + g.switch() + self.assertEqual(lst, ['a', 'b', 'c']) + # Just the one in this frame, plus the one on the stack we pass to the function + self.assertEqual(sys.getrefcount(g), 2 if not PY314 else 1) + + def test_threads(self): + success = [] + + def f(): + self._do_simple_test() + success.append(True) + ths = [threading.Thread(target=f) for i in range(10)] + for th in ths: + th.start() + for th in ths: + th.join(10) + self.assertEqual(len(success), len(ths)) + + def test_exception(self): + seen = [] + g1 = RawGreenlet(fmain) + g2 = RawGreenlet(fmain) + g1.switch(seen) + g2.switch(seen) + g2.parent = g1 + + self.assertEqual(seen, []) + #with self.assertRaises(SomeError): + # p("***Switching back") + # g2.switch() + # Creating this as a bound method can reveal bugs that + # are hidden on newer versions of Python that avoid creating + # bound methods for direct expressions; IOW, don't use the `with` + # form! + self.assertRaises(SomeError, g2.switch) + self.assertEqual(seen, [SomeError]) + + value = g2.switch() + self.assertEqual(value, ()) + self.assertEqual(seen, [SomeError]) + + value = g2.switch(25) + self.assertEqual(value, 25) + self.assertEqual(seen, [SomeError]) + + + def test_send_exception(self): + seen = [] + g1 = RawGreenlet(fmain) + g1.switch(seen) + self.assertRaises(KeyError, send_exception, g1, KeyError) + self.assertEqual(seen, [KeyError]) + + def test_dealloc(self): + seen = [] + g1 = RawGreenlet(fmain) + g2 = RawGreenlet(fmain) + g1.switch(seen) + g2.switch(seen) + self.assertEqual(seen, []) + del g1 + gc.collect() + self.assertEqual(seen, [greenlet.GreenletExit]) + del g2 + gc.collect() + self.assertEqual(seen, [greenlet.GreenletExit, greenlet.GreenletExit]) + + def test_dealloc_catches_GreenletExit_throws_other(self): + def run(): + try: + greenlet.getcurrent().parent.switch() + except greenlet.GreenletExit: + raise SomeError from None + + g = RawGreenlet(run) + g.switch() + # Destroying the only reference to the greenlet causes it + # to get GreenletExit; when it in turn raises, even though we're the parent + # we don't get the exception, it just gets printed. + # When we run on 3.8 only, we can use sys.unraisablehook + oldstderr = sys.stderr + from io import StringIO + stderr = sys.stderr = StringIO() + try: + del g + finally: + sys.stderr = oldstderr + + v = stderr.getvalue() + self.assertIn("Exception", v) + self.assertIn('ignored', v) + self.assertIn("SomeError", v) + + + @unittest.skipIf( + PY313 and RUNNING_ON_MANYLINUX, + "Sometimes flaky (getting one GreenletExit in the second list)" + # Probably due to funky timing interactions? + # TODO: FIXME Make that work. + ) + + def test_dealloc_other_thread(self): + seen = [] + someref = [] + + bg_glet_created_running_and_no_longer_ref_in_bg = threading.Event() + fg_ref_released = threading.Event() + bg_should_be_clear = threading.Event() + ok_to_exit_bg_thread = threading.Event() + + def f(): + g1 = RawGreenlet(fmain) + g1.switch(seen) + someref.append(g1) + del g1 + gc.collect() + bg_glet_created_running_and_no_longer_ref_in_bg.set() + fg_ref_released.wait(3) + + RawGreenlet() # trigger release + bg_should_be_clear.set() + ok_to_exit_bg_thread.wait(3) + RawGreenlet() # One more time + + t = threading.Thread(target=f) + t.start() + bg_glet_created_running_and_no_longer_ref_in_bg.wait(10) + + self.assertEqual(seen, []) + self.assertEqual(len(someref), 1) + del someref[:] + if not RUNNING_ON_FREETHREAD_BUILD: + # The free-threaded GC is very different. In 3.14rc1, + # the free-threaded GC traverses ``g1``, realizes it is + # not referenced from anywhere else IT cares about, + # calls ``tp_clear`` and then ``green_dealloc``. This causes + # the greenlet to lose its reference to the main greenlet and thread + # in which it was running, which means we can no longer throw an + # exception into it, preventing the rest of this test from working. + # Standard 3.14 traverses the object but doesn't ``tp_clear`` or + # ``green_dealloc`` it. + gc.collect() + # g1 is not released immediately because it's from another thread; + # switching back to that thread will allocate a greenlet and thus + # trigger deletion actions. + self.assertEqual(seen, []) + fg_ref_released.set() + bg_should_be_clear.wait(3) + try: + self.assertEqual(seen, [greenlet.GreenletExit]) + finally: + ok_to_exit_bg_thread.set() + t.join(10) + del seen[:] + del someref[:] + + def test_frame(self): + def f1(): + f = sys._getframe(0) # pylint:disable=protected-access + self.assertEqual(f.f_back, None) + greenlet.getcurrent().parent.switch(f) + return "meaning of life" + g = RawGreenlet(f1) + frame = g.switch() + self.assertTrue(frame is g.gr_frame) + self.assertTrue(g) + + from_g = g.switch() + self.assertFalse(g) + self.assertEqual(from_g, 'meaning of life') + self.assertEqual(g.gr_frame, None) + + def test_thread_bug(self): + def runner(x): + g = RawGreenlet(lambda: time.sleep(x)) + g.switch() + t1 = threading.Thread(target=runner, args=(0.2,)) + t2 = threading.Thread(target=runner, args=(0.3,)) + t1.start() + t2.start() + t1.join(10) + t2.join(10) + + def test_switch_kwargs(self): + def run(a, b): + self.assertEqual(a, 4) + self.assertEqual(b, 2) + return 42 + x = RawGreenlet(run).switch(a=4, b=2) + self.assertEqual(x, 42) + + def test_switch_kwargs_to_parent(self): + def run(x): + greenlet.getcurrent().parent.switch(x=x) + greenlet.getcurrent().parent.switch(2, x=3) + return x, x ** 2 + g = RawGreenlet(run) + self.assertEqual({'x': 3}, g.switch(3)) + self.assertEqual(((2,), {'x': 3}), g.switch()) + self.assertEqual((3, 9), g.switch()) + + def test_switch_to_another_thread(self): + data = {} + created_event = threading.Event() + done_event = threading.Event() + + def run(): + data['g'] = RawGreenlet(lambda: None) + created_event.set() + done_event.wait(10) + thread = threading.Thread(target=run) + thread.start() + created_event.wait(10) + with self.assertRaises(greenlet.error): + data['g'].switch() + done_event.set() + thread.join(10) + # XXX: Should handle this automatically + data.clear() + + def test_exc_state(self): + def f(): + try: + raise ValueError('fun') + except: # pylint:disable=bare-except + exc_info = sys.exc_info() + RawGreenlet(h).switch() + self.assertEqual(exc_info, sys.exc_info()) + + def h(): + self.assertEqual(sys.exc_info(), (None, None, None)) + + RawGreenlet(f).switch() + + def test_instance_dict(self): + def f(): + greenlet.getcurrent().test = 42 + def deldict(g): + del g.__dict__ + def setdict(g, value): + g.__dict__ = value + g = RawGreenlet(f) + self.assertEqual(g.__dict__, {}) + g.switch() + self.assertEqual(g.test, 42) + self.assertEqual(g.__dict__, {'test': 42}) + g.__dict__ = g.__dict__ + self.assertEqual(g.__dict__, {'test': 42}) + self.assertRaises(TypeError, deldict, g) + self.assertRaises(TypeError, setdict, g, 42) + + def test_running_greenlet_has_no_run(self): + has_run = [] + def func(): + has_run.append( + hasattr(greenlet.getcurrent(), 'run') + ) + + g = RawGreenlet(func) + g.switch() + self.assertEqual(has_run, [False]) + + def test_deepcopy(self): + import copy + self.assertRaises(TypeError, copy.copy, RawGreenlet()) + self.assertRaises(TypeError, copy.deepcopy, RawGreenlet()) + + def test_parent_restored_on_kill(self): + hub = RawGreenlet(lambda: None) + main = greenlet.getcurrent() + result = [] + def worker(): + try: + # Wait to be killed by going back to the test. + main.switch() + except greenlet.GreenletExit: + # Resurrect and switch to parent + result.append(greenlet.getcurrent().parent) + result.append(greenlet.getcurrent()) + hub.switch() + g = RawGreenlet(worker, parent=hub) + g.switch() + # delete the only reference, thereby raising GreenletExit + del g + self.assertTrue(result) + self.assertIs(result[0], main) + self.assertIs(result[1].parent, hub) + # Delete them, thereby breaking the cycle between the greenlet + # and the frame, which otherwise would never be collectable + # XXX: We should be able to automatically fix this. + del result[:] + hub = None + main = None + + def test_parent_return_failure(self): + # No run causes AttributeError on switch + g1 = RawGreenlet() + # Greenlet that implicitly switches to parent + g2 = RawGreenlet(lambda: None, parent=g1) + # AttributeError should propagate to us, no fatal errors + with self.assertRaises(AttributeError): + g2.switch() + + def test_throw_exception_not_lost(self): + class mygreenlet(RawGreenlet): + def __getattribute__(self, name): + try: + raise Exception # pylint:disable=broad-exception-raised + except: # pylint:disable=bare-except + pass + return RawGreenlet.__getattribute__(self, name) + g = mygreenlet(lambda: None) + self.assertRaises(SomeError, g.throw, SomeError()) + + @fails_leakcheck + def _do_test_throw_to_dead_thread_doesnt_crash(self, wait_for_cleanup=False): + result = [] + def worker(): + greenlet.getcurrent().parent.switch() + + def creator(): + g = RawGreenlet(worker) + g.switch() + result.append(g) + if wait_for_cleanup: + # Let this greenlet eventually be cleaned up. + g.switch() + greenlet.getcurrent() + t = threading.Thread(target=creator) + t.start() + t.join(10) + del t + # But, depending on the operating system, the thread + # deallocator may not actually have run yet! So we can't be + # sure about the error message unless we wait. + if wait_for_cleanup: + self.wait_for_pending_cleanups() + with self.assertRaises(greenlet.error) as exc: + result[0].throw(SomeError) + + if not wait_for_cleanup: + s = str(exc.exception) + self.assertTrue( + s == "cannot switch to a different thread (which happens to have exited)" + or 'Cannot switch' in s + ) + else: + self.assertEqual( + str(exc.exception), + "cannot switch to a different thread (which happens to have exited)", + ) + + if hasattr(result[0].gr_frame, 'clear'): + # The frame is actually executing (it thinks), we can't clear it. + with self.assertRaises(RuntimeError): + result[0].gr_frame.clear() + # Unfortunately, this doesn't actually clear the references, they're in the + # fast local array. + if not wait_for_cleanup: + # f_locals has no clear method in Python 3.13 + if hasattr(result[0].gr_frame.f_locals, 'clear'): + result[0].gr_frame.f_locals.clear() + else: + self.assertIsNone(result[0].gr_frame) + + del creator + worker = None + del result[:] + # XXX: we ought to be able to automatically fix this. + # See issue 252 + self.expect_greenlet_leak = True # direct us not to wait for it to go away + + @fails_leakcheck + def test_throw_to_dead_thread_doesnt_crash(self): + self._do_test_throw_to_dead_thread_doesnt_crash() + + def test_throw_to_dead_thread_doesnt_crash_wait(self): + self._do_test_throw_to_dead_thread_doesnt_crash(True) + + @fails_leakcheck + def test_recursive_startup(self): + class convoluted(RawGreenlet): + def __init__(self): + RawGreenlet.__init__(self) + self.count = 0 + def __getattribute__(self, name): + if name == 'run' and self.count == 0: + self.count = 1 + self.switch(43) + return RawGreenlet.__getattribute__(self, name) + def run(self, value): + while True: + self.parent.switch(value) + g = convoluted() + self.assertEqual(g.switch(42), 43) + # Exits the running greenlet, otherwise it leaks + # XXX: We should be able to automatically fix this + #g.throw(greenlet.GreenletExit) + #del g + self.expect_greenlet_leak = True + + def test_threaded_updatecurrent(self): + # released when main thread should execute + lock1 = threading.Lock() + lock1.acquire() + # released when another thread should execute + lock2 = threading.Lock() + lock2.acquire() + class finalized(object): + def __del__(self): + # happens while in green_updatecurrent() in main greenlet + # should be very careful not to accidentally call it again + # at the same time we must make sure another thread executes + lock2.release() + lock1.acquire() + # now ts_current belongs to another thread + def deallocator(): + greenlet.getcurrent().parent.switch() + def fthread(): + lock2.acquire() + greenlet.getcurrent() + del g[0] + lock1.release() + lock2.acquire() + greenlet.getcurrent() + lock1.release() + main = greenlet.getcurrent() + g = [RawGreenlet(deallocator)] + g[0].bomb = finalized() + g[0].switch() + t = threading.Thread(target=fthread) + t.start() + # let another thread grab ts_current and deallocate g[0] + lock2.release() + lock1.acquire() + # this is the corner stone + # getcurrent() will notice that ts_current belongs to another thread + # and start the update process, which would notice that g[0] should + # be deallocated, and that will execute an object's finalizer. Now, + # that object will let another thread run so it can grab ts_current + # again, which would likely crash the interpreter if there's no + # check for this case at the end of green_updatecurrent(). This test + # passes if getcurrent() returns correct result, but it's likely + # to randomly crash if it's not anyway. + self.assertEqual(greenlet.getcurrent(), main) + # wait for another thread to complete, just in case + t.join(10) + + def test_dealloc_switch_args_not_lost(self): + seen = [] + def worker(): + # wait for the value + value = greenlet.getcurrent().parent.switch() + # delete all references to ourself + del worker[0] + initiator.parent = greenlet.getcurrent().parent + # switch to main with the value, but because + # ts_current is the last reference to us we + # return here immediately, where we resurrect ourself. + try: + greenlet.getcurrent().parent.switch(value) + finally: + seen.append(greenlet.getcurrent()) + def initiator(): + return 42 # implicitly falls thru to parent + + worker = [RawGreenlet(worker)] + + worker[0].switch() # prime worker + initiator = RawGreenlet(initiator, worker[0]) + value = initiator.switch() + self.assertTrue(seen) + self.assertEqual(value, 42) + + def test_tuple_subclass(self): + # The point of this test is to see what happens when a custom + # tuple subclass is used as an object passed directly to the C + # function ``green_switch``; part of ``green_switch`` checks + # the ``len()`` of the ``args`` tuple, and that can call back + # into Python. Here, when it calls back into Python, we + # recursively enter ``green_switch`` again. + + # This test is really only relevant on Python 2. The builtin + # `apply` function directly passes the given args tuple object + # to the underlying function, whereas the Python 3 version + # unpacks and repacks into an actual tuple. This could still + # happen using the C API on Python 3 though. We should write a + # builtin version of apply() ourself. + def _apply(func, a, k): + func(*a, **k) + + class mytuple(tuple): + def __len__(self): + greenlet.getcurrent().switch() + return tuple.__len__(self) + args = mytuple() + kwargs = dict(a=42) + def switchapply(): + _apply(greenlet.getcurrent().parent.switch, args, kwargs) + g = RawGreenlet(switchapply) + self.assertEqual(g.switch(), kwargs) + + def test_abstract_subclasses(self): + AbstractSubclass = ABCMeta( + 'AbstractSubclass', + (RawGreenlet,), + {'run': abstractmethod(lambda self: None)}) + + class BadSubclass(AbstractSubclass): + pass + + class GoodSubclass(AbstractSubclass): + def run(self): + pass + + GoodSubclass() # should not raise + self.assertRaises(TypeError, BadSubclass) + + def test_implicit_parent_with_threads(self): + if not gc.isenabled(): + return # cannot test with disabled gc + N = gc.get_threshold()[0] + if N < 50: + return # cannot test with such a small N + def attempt(): + lock1 = threading.Lock() + lock1.acquire() + lock2 = threading.Lock() + lock2.acquire() + recycled = [False] + def another_thread(): + lock1.acquire() # wait for gc + greenlet.getcurrent() # update ts_current + lock2.release() # release gc + t = threading.Thread(target=another_thread) + t.start() + class gc_callback(object): + def __del__(self): + lock1.release() + lock2.acquire() + recycled[0] = True + class garbage(object): + def __init__(self): + self.cycle = self + self.callback = gc_callback() + l = [] + x = range(N*2) + current = greenlet.getcurrent() + g = garbage() + for _ in x: + g = None # lose reference to garbage + if recycled[0]: + # gc callback called prematurely + t.join(10) + return False + last = RawGreenlet() + if recycled[0]: + break # yes! gc called in green_new + l.append(last) # increase allocation counter + else: + # gc callback not called when expected + gc.collect() + if recycled[0]: + t.join(10) + return False + self.assertEqual(last.parent, current) + for g in l: + self.assertEqual(g.parent, current) + return True + for _ in range(5): + if attempt(): + break + + def test_issue_245_reference_counting_subclass_no_threads(self): + # https://github.com/python-greenlet/greenlet/issues/245 + # Before the fix, this crashed pretty reliably on + # Python 3.10, at least on macOS; but much less reliably on other + # interpreters (memory layout must have changed). + # The threaded test crashed more reliably on more interpreters. + from greenlet import getcurrent + from greenlet import GreenletExit + + class Greenlet(RawGreenlet): + pass + + initial_refs = sys.getrefcount(Greenlet) + # This has to be an instance variable because + # Python 2 raises a SyntaxError if we delete a local + # variable referenced in an inner scope. + self.glets = [] # pylint:disable=attribute-defined-outside-init + + def greenlet_main(): + try: + getcurrent().parent.switch() + except GreenletExit: + self.glets.append(getcurrent()) + + # Before the + for _ in range(10): + Greenlet(greenlet_main).switch() + + del self.glets + if RUNNING_ON_FREETHREAD_BUILD: + # Free-threaded builds make types immortal, which gives us + # weird numbers here, and we actually do APPEAR to end + # up with one more reference than we started with, at least on 3.14. + # If we change the code in green_dealloc to avoid increffing the type + # (which fixed this initial bug), then our leakchecks find other objects + # that have leaked, including a tuple, a dict, and a type. So that's not the + # right solution. Instead we change the test: + # XXX: FIXME: Is there a better way? + self.assertGreaterEqual(sys.getrefcount(Greenlet), initial_refs) + else: + self.assertEqual(sys.getrefcount(Greenlet), initial_refs) + + @unittest.skipIf( + PY313 and RUNNING_ON_MANYLINUX, + "The manylinux images appear to hang on this test on 3.13rc2" + # Or perhaps I just got tired of waiting for the 450s timeout. + # Still, it shouldn't take anywhere near that long. Does not reproduce in + # Ubuntu images, on macOS or Windows. + ) + def test_issue_245_reference_counting_subclass_threads(self): + # https://github.com/python-greenlet/greenlet/issues/245 + from threading import Thread + from threading import Event + + from greenlet import getcurrent + + class MyGreenlet(RawGreenlet): + pass + + glets = [] + ref_cleared = Event() + + def greenlet_main(): + getcurrent().parent.switch() + + def thread_main(greenlet_running_event): + mine = MyGreenlet(greenlet_main) + glets.append(mine) + # The greenlets being deleted must be active + mine.switch() + # Don't keep any reference to it in this thread + del mine + # Let main know we published our greenlet. + greenlet_running_event.set() + # Wait for main to let us know the references are + # gone and the greenlet objects no longer reachable + ref_cleared.wait(10) + # The creating thread must call getcurrent() (or a few other + # greenlet APIs) because that's when the thread-local list of dead + # greenlets gets cleared. + getcurrent() + + # We start with 3 references to the subclass: + # - This module + # - Its __mro__ + # - The __subclassess__ attribute of greenlet + # - (If we call gc.get_referents(), we find four entries, including + # some other tuple ``(greenlet)`` that I'm not sure about but must be part + # of the machinery.) + # + # On Python 3.10 it's often enough to just run 3 threads; on Python 2.7, + # more threads are needed, and the results are still + # non-deterministic. Presumably the memory layouts are different + initial_refs = sys.getrefcount(MyGreenlet) + thread_ready_events = [] + thread_count = initial_refs + 45 + if RUNNING_ON_FREETHREAD_BUILD: + # types are immortal, so this is a HUGE number most likely, + # and we can't create that many threads. + thread_count = 50 + for _ in range(thread_count): + event = Event() + thread = Thread(target=thread_main, args=(event,)) + thread_ready_events.append(event) + thread.start() + + + for done_event in thread_ready_events: + done_event.wait(10) + + + del glets[:] + ref_cleared.set() + # Let any other thread run; it will crash the interpreter + # if not fixed (or silently corrupt memory and we possibly crash + # later). + self.wait_for_pending_cleanups() + self.assertEqual(sys.getrefcount(MyGreenlet), initial_refs) + + def test_falling_off_end_switches_to_unstarted_parent_raises_error(self): + def no_args(): + return 13 + + parent_never_started = RawGreenlet(no_args) + + def leaf(): + return 42 + + child = RawGreenlet(leaf, parent_never_started) + + # Because the run function takes to arguments + with self.assertRaises(TypeError): + child.switch() + + def test_falling_off_end_switches_to_unstarted_parent_works(self): + def one_arg(x): + return (x, 24) + + parent_never_started = RawGreenlet(one_arg) + + def leaf(): + return 42 + + child = RawGreenlet(leaf, parent_never_started) + + result = child.switch() + self.assertEqual(result, (42, 24)) + + def test_switch_to_dead_greenlet_with_unstarted_perverse_parent(self): + class Parent(RawGreenlet): + def __getattribute__(self, name): + if name == 'run': + raise SomeError + + + parent_never_started = Parent() + seen = [] + child = RawGreenlet(lambda: seen.append(42), parent_never_started) + # Because we automatically start the parent when the child is + # finished + with self.assertRaises(SomeError): + child.switch() + + self.assertEqual(seen, [42]) + + with self.assertRaises(SomeError): + child.switch() + self.assertEqual(seen, [42]) + + def test_switch_to_dead_greenlet_reparent(self): + seen = [] + parent_never_started = RawGreenlet(lambda: seen.append(24)) + child = RawGreenlet(lambda: seen.append(42)) + + child.switch() + self.assertEqual(seen, [42]) + + child.parent = parent_never_started + # This actually is the same as switching to the parent. + result = child.switch() + self.assertIsNone(result) + self.assertEqual(seen, [42, 24]) + + def test_can_access_f_back_of_suspended_greenlet(self): + # This tests our frame rewriting to work around Python 3.12+ having + # some interpreter frames on the C stack. It will crash in the absence + # of that logic. + main = greenlet.getcurrent() + + def outer(): + inner() + + def inner(): + main.switch(sys._getframe(0)) + + hub = RawGreenlet(outer) + # start it + hub.switch() + + # start another greenlet to make sure we aren't relying on + # anything in `hub` still being on the C stack + unrelated = RawGreenlet(lambda: None) + unrelated.switch() + + # now it is suspended + self.assertIsNotNone(hub.gr_frame) + self.assertEqual(hub.gr_frame.f_code.co_name, "inner") + self.assertIsNotNone(hub.gr_frame.f_back) + self.assertEqual(hub.gr_frame.f_back.f_code.co_name, "outer") + # The next line is what would crash + self.assertIsNone(hub.gr_frame.f_back.f_back) + + def test_get_stack_with_nested_c_calls(self): + from functools import partial + from . import _test_extension_cpp + + def recurse(v): + if v > 0: + return v * _test_extension_cpp.test_call(partial(recurse, v - 1)) + return greenlet.getcurrent().parent.switch() + + gr = RawGreenlet(recurse) + gr.switch(5) + frame = gr.gr_frame + for i in range(5): + self.assertEqual(frame.f_locals["v"], i) + frame = frame.f_back + self.assertEqual(frame.f_locals["v"], 5) + self.assertIsNone(frame.f_back) + self.assertEqual(gr.switch(10), 1200) # 1200 = 5! * 10 + + def test_frames_always_exposed(self): + # On Python 3.12 this will crash if we don't set the + # gr_frames_always_exposed attribute. More background: + # https://github.com/python-greenlet/greenlet/issues/388 + main = greenlet.getcurrent() + + def outer(): + inner(sys._getframe(0)) + + def inner(frame): + main.switch(frame) + + gr = RawGreenlet(outer) + frame = gr.switch() + + # Do something else to clobber the part of the C stack used by `gr`, + # so we can't skate by on "it just happened to still be there" + unrelated = RawGreenlet(lambda: None) + unrelated.switch() + + self.assertEqual(frame.f_code.co_name, "outer") + # The next line crashes on 3.12 if we haven't exposed the frames. + self.assertIsNone(frame.f_back) + + +class TestGreenletSetParentErrors(TestCase): + def test_threaded_reparent(self): + data = {} + created_event = threading.Event() + done_event = threading.Event() + + def run(): + data['g'] = RawGreenlet(lambda: None) + created_event.set() + done_event.wait(10) + + def blank(): + greenlet.getcurrent().parent.switch() + + thread = threading.Thread(target=run) + thread.start() + created_event.wait(10) + g = RawGreenlet(blank) + g.switch() + with self.assertRaises(ValueError) as exc: + g.parent = data['g'] + done_event.set() + thread.join(10) + + self.assertEqual(str(exc.exception), "parent cannot be on a different thread") + + def test_unexpected_reparenting(self): + another = [] + def worker(): + g = RawGreenlet(lambda: None) + another.append(g) + g.switch() + t = threading.Thread(target=worker) + t.start() + t.join(10) + # The first time we switch (running g_initialstub(), which is + # when we look up the run attribute) we attempt to change the + # parent to one from another thread (which also happens to be + # dead). ``g_initialstub()`` should detect this and raise a + # greenlet error. + # + # EXCEPT: With the fix for #252, this is actually detected + # sooner, when setting the parent itself. Prior to that fix, + # the main greenlet from the background thread kept a valid + # value for ``run_info``, and appeared to be a valid parent + # until we actually started the greenlet. But now that it's + # cleared, this test is catching whether ``green_setparent`` + # can detect the dead thread. + # + # Further refactoring once again changes this back to a greenlet.error + # + # We need to wait for the cleanup to happen, but we're + # deliberately leaking a main greenlet here. + self.wait_for_pending_cleanups(initial_main_greenlets=self.main_greenlets_before_test + 1) + + class convoluted(RawGreenlet): + def __getattribute__(self, name): + if name == 'run': + self.parent = another[0] # pylint:disable=attribute-defined-outside-init + return RawGreenlet.__getattribute__(self, name) + g = convoluted(lambda: None) + with self.assertRaises(greenlet.error) as exc: + g.switch() + self.assertEqual(str(exc.exception), + "cannot switch to a different thread (which happens to have exited)") + del another[:] + + def test_unexpected_reparenting_thread_running(self): + # Like ``test_unexpected_reparenting``, except the background thread is + # actually still alive. + another = [] + switched_to_greenlet = threading.Event() + keep_main_alive = threading.Event() + def worker(): + g = RawGreenlet(lambda: None) + another.append(g) + g.switch() + switched_to_greenlet.set() + keep_main_alive.wait(10) + class convoluted(RawGreenlet): + def __getattribute__(self, name): + if name == 'run': + self.parent = another[0] # pylint:disable=attribute-defined-outside-init + return RawGreenlet.__getattribute__(self, name) + + t = threading.Thread(target=worker) + t.start() + + switched_to_greenlet.wait(10) + try: + g = convoluted(lambda: None) + + with self.assertRaises(greenlet.error) as exc: + g.switch() + self.assertIn("Cannot switch to a different thread", str(exc.exception)) + self.assertIn("Expected", str(exc.exception)) + self.assertIn("Current", str(exc.exception)) + finally: + keep_main_alive.set() + t.join(10) + # XXX: Should handle this automatically. + del another[:] + + def test_cannot_delete_parent(self): + worker = RawGreenlet(lambda: None) + self.assertIs(worker.parent, greenlet.getcurrent()) + + with self.assertRaises(AttributeError) as exc: + del worker.parent + self.assertEqual(str(exc.exception), "can't delete attribute") + + def test_cannot_delete_parent_of_main(self): + with self.assertRaises(AttributeError) as exc: + del greenlet.getcurrent().parent + self.assertEqual(str(exc.exception), "can't delete attribute") + + + def test_main_greenlet_parent_is_none(self): + # assuming we're in a main greenlet here. + self.assertIsNone(greenlet.getcurrent().parent) + + def test_set_parent_wrong_types(self): + def bg(): + # Go back to main. + greenlet.getcurrent().parent.switch() + + def check(glet): + for p in None, 1, self, "42": + with self.assertRaises(TypeError) as exc: + glet.parent = p + + self.assertEqual( + str(exc.exception), + "GreenletChecker: Expected any type of greenlet, not " + type(p).__name__) + + # First, not running + g = RawGreenlet(bg) + self.assertFalse(g) + check(g) + + # Then when running. + g.switch() + self.assertTrue(g) + check(g) + + # Let it finish + g.switch() + + + def test_trivial_cycle(self): + glet = RawGreenlet(lambda: None) + with self.assertRaises(ValueError) as exc: + glet.parent = glet + self.assertEqual(str(exc.exception), "cyclic parent chain") + + def test_trivial_cycle_main(self): + # This used to produce a ValueError, but we catch it earlier than that now. + with self.assertRaises(AttributeError) as exc: + greenlet.getcurrent().parent = greenlet.getcurrent() + self.assertEqual(str(exc.exception), "cannot set the parent of a main greenlet") + + def test_deeper_cycle(self): + g1 = RawGreenlet(lambda: None) + g2 = RawGreenlet(lambda: None) + g3 = RawGreenlet(lambda: None) + + g1.parent = g2 + g2.parent = g3 + with self.assertRaises(ValueError) as exc: + g3.parent = g1 + self.assertEqual(str(exc.exception), "cyclic parent chain") + + +class TestRepr(TestCase): + + def assertEndsWith(self, got, suffix): + self.assertTrue(got.endswith(suffix), (got, suffix)) + + def test_main_while_running(self): + r = repr(greenlet.getcurrent()) + self.assertEndsWith(r, " current active started main>") + + def test_main_in_background(self): + main = greenlet.getcurrent() + def run(): + return repr(main) + + g = RawGreenlet(run) + r = g.switch() + self.assertEndsWith(r, ' suspended active started main>') + + def test_initial(self): + r = repr(RawGreenlet()) + self.assertEndsWith(r, ' pending>') + + def test_main_from_other_thread(self): + main = greenlet.getcurrent() + + class T(threading.Thread): + original_main = thread_main = None + main_glet = None + def run(self): + self.original_main = repr(main) + self.main_glet = greenlet.getcurrent() + self.thread_main = repr(self.main_glet) + + t = T() + t.start() + t.join(10) + + self.assertEndsWith(t.original_main, ' suspended active started main>') + self.assertEndsWith(t.thread_main, ' current active started main>') + # give the machinery time to notice the death of the thread, + # and clean it up. Note that we don't use + # ``expect_greenlet_leak`` or wait_for_pending_cleanups, + # because at this point we know we have an extra greenlet + # still reachable. + for _ in range(3): + time.sleep(0.001) + + # In the past, main greenlets, even from dead threads, never + # really appear dead. We have fixed that, and we also report + # that the thread is dead in the repr. (Do this multiple times + # to make sure that we don't self-modify and forget our state + # in the C++ code). + for _ in range(3): + self.assertTrue(t.main_glet.dead) + r = repr(t.main_glet) + self.assertEndsWith(r, ' (thread exited) dead>') + + def test_dead(self): + g = RawGreenlet(lambda: None) + g.switch() + self.assertEndsWith(repr(g), ' dead>') + self.assertNotIn('suspended', repr(g)) + self.assertNotIn('started', repr(g)) + self.assertNotIn('active', repr(g)) + + def test_formatting_produces_native_str(self): + # https://github.com/python-greenlet/greenlet/issues/218 + # %s formatting on Python 2 was producing unicode, not str. + + g_dead = RawGreenlet(lambda: None) + g_not_started = RawGreenlet(lambda: None) + g_cur = greenlet.getcurrent() + + for g in g_dead, g_not_started, g_cur: + + self.assertIsInstance( + '%s' % (g,), + str + ) + self.assertIsInstance( + '%r' % (g,), + str, + ) + + +class TestMainGreenlet(TestCase): + # Tests some implementation details, and relies on some + # implementation details. + + def _check_current_is_main(self): + # implementation detail + assert 'main' in repr(greenlet.getcurrent()) + + t = type(greenlet.getcurrent()) + assert 'main' not in repr(t) + return t + + def test_main_greenlet_type_can_be_subclassed(self): + main_type = self._check_current_is_main() + subclass = type('subclass', (main_type,), {}) + self.assertIsNotNone(subclass) + + def test_main_greenlet_is_greenlet(self): + self._check_current_is_main() + self.assertIsInstance(greenlet.getcurrent(), RawGreenlet) + + + +class TestBrokenGreenlets(TestCase): + # Tests for things that used to, or still do, terminate the interpreter. + # This often means doing unsavory things. + + def test_failed_to_initialstub(self): + def func(): + raise AssertionError("Never get here") + + + g = greenlet._greenlet.UnswitchableGreenlet(func) + g.force_switch_error = True + + with self.assertRaisesRegex(SystemError, + "Failed to switch stacks into a greenlet for the first time."): + g.switch() + + def test_failed_to_switch_into_running(self): + runs = [] + def func(): + runs.append(1) + greenlet.getcurrent().parent.switch() + runs.append(2) + greenlet.getcurrent().parent.switch() + runs.append(3) # pragma: no cover + + g = greenlet._greenlet.UnswitchableGreenlet(func) + g.switch() + self.assertEqual(runs, [1]) + g.switch() + self.assertEqual(runs, [1, 2]) + g.force_switch_error = True + + with self.assertRaisesRegex(SystemError, + "Failed to switch stacks into a running greenlet."): + g.switch() + + # If we stopped here, we would fail the leakcheck, because we've left + # the ``inner_bootstrap()`` C frame and its descendents hanging around, + # which have a bunch of Python references. They'll never get cleaned up + # if we don't let the greenlet finish. + g.force_switch_error = False + g.switch() + self.assertEqual(runs, [1, 2, 3]) + + def test_failed_to_slp_switch_into_running(self): + ex = self.assertScriptRaises('fail_slp_switch.py') + + self.assertIn('fail_slp_switch is running', ex.output) + self.assertIn(ex.returncode, self.get_expected_returncodes_for_aborted_process()) + + def test_reentrant_switch_two_greenlets(self): + # Before we started capturing the arguments in g_switch_finish, this could crash. + output = self.run_script('fail_switch_two_greenlets.py') + self.assertIn('In g1_run', output) + self.assertIn('TRACE', output) + self.assertIn('LEAVE TRACE', output) + self.assertIn('Falling off end of main', output) + self.assertIn('Falling off end of g1_run', output) + self.assertIn('Falling off end of g2', output) + + def test_reentrant_switch_three_greenlets(self): + # On debug builds of greenlet, this used to crash with an assertion error; + # on non-debug versions, it ran fine (which it should not do!). + # Now it always crashes correctly with a TypeError + ex = self.assertScriptRaises('fail_switch_three_greenlets.py', exitcodes=(1,)) + + self.assertIn('TypeError', ex.output) + self.assertIn('positional arguments', ex.output) + + def test_reentrant_switch_three_greenlets2(self): + # This actually passed on debug and non-debug builds. It + # should probably have been triggering some debug assertions + # but it didn't. + # + # I think the fixes for the above test also kicked in here. + output = self.run_script('fail_switch_three_greenlets2.py') + self.assertIn( + "RESULTS: [('trace', 'switch'), " + "('trace', 'switch'), ('g2 arg', 'g2 from tracefunc'), " + "('trace', 'switch'), ('main g1', 'from g2_run'), ('trace', 'switch'), " + "('g1 arg', 'g1 from main'), ('trace', 'switch'), ('main g2', 'from g1_run'), " + "('trace', 'switch'), ('g1 from parent', 'g1 from main 2'), ('trace', 'switch'), " + "('main g1.2', 'g1 done'), ('trace', 'switch'), ('g2 from parent', ()), " + "('trace', 'switch'), ('main g2.2', 'g2 done')]", + output + ) + + def test_reentrant_switch_GreenletAlreadyStartedInPython(self): + output = self.run_script('fail_initialstub_already_started.py') + + self.assertIn( + "RESULTS: ['Begin C', 'Switch to b from B.__getattribute__ in C', " + "('Begin B', ()), '_B_run switching to main', ('main from c', 'From B'), " + "'B.__getattribute__ back from main in C', ('Begin A', (None,)), " + "('A dead?', True, 'B dead?', True, 'C dead?', False), " + "'C done', ('main from c.2', None)]", + output + ) + + def test_reentrant_switch_run_callable_has_del(self): + output = self.run_script('fail_clearing_run_switches.py') + self.assertIn( + "RESULTS [" + "('G.__getattribute__', 'run'), ('RunCallable', '__del__'), " + "('main: g.switch()', 'from RunCallable'), ('run_func', 'enter')" + "]", + output + ) + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_greenlet_trash.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_greenlet_trash.py new file mode 100644 index 0000000..c1fc137 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_greenlet_trash.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +""" +Tests for greenlets interacting with the CPython trash can API. + +The CPython trash can API is not designed to be re-entered from a +single thread. But this can happen using greenlets, if something +during the object deallocation process switches greenlets, and this second +greenlet then causes the trash can to get entered again. Here, we do this +very explicitly, but in other cases (like gevent) it could be arbitrarily more +complicated: for example, a weakref callback might try to acquire a lock that's +already held by another greenlet; that would allow a greenlet switch to occur. + +See https://github.com/gevent/gevent/issues/1909 + +This test is fragile and relies on details of the CPython +implementation (like most of the rest of this package): + + - We enter the trashcan and deferred deallocation after + ``_PyTrash_UNWIND_LEVEL`` calls. This constant, defined in + CPython's object.c, is generally 50. That's basically how many objects are required to + get us into the deferred deallocation situation. + + - The test fails by hitting an ``assert()`` in object.c; if the + build didn't enable assert, then we don't catch this. + + - If the test fails in that way, the interpreter crashes. +""" +from __future__ import print_function, absolute_import, division + +import unittest + + +class TestTrashCanReEnter(unittest.TestCase): + + def test_it(self): + try: + # pylint:disable-next=no-name-in-module + from greenlet._greenlet import get_tstate_trash_delete_nesting # pylint:disable=unused-import + except ImportError: + import sys + # Python 3.13 has not "trash delete nesting" anymore (but "delete later") + assert sys.version_info[:2] >= (3, 13) + self.skipTest("get_tstate_trash_delete_nesting is not available.") + + # Try several times to trigger it, because it isn't 100% + # reliable. + for _ in range(10): + self.check_it() + + def check_it(self): # pylint:disable=too-many-statements + import greenlet + from greenlet._greenlet import get_tstate_trash_delete_nesting # pylint:disable=no-name-in-module + main = greenlet.getcurrent() + + assert get_tstate_trash_delete_nesting() == 0 + + # We expect to be in deferred deallocation after this many + # deallocations have occurred. TODO: I wish we had a better way to do + # this --- that was before get_tstate_trash_delete_nesting; perhaps + # we can use that API to do better? + TRASH_UNWIND_LEVEL = 50 + # How many objects to put in a container; it's the container that + # queues objects for deferred deallocation. + OBJECTS_PER_CONTAINER = 500 + + class Dealloc: # define the class here because we alter class variables each time we run. + """ + An object with a ``__del__`` method. When it starts getting deallocated + from a deferred trash can run, it switches greenlets, allocates more objects + which then also go in the trash can. If we don't save state appropriately, + nesting gets out of order and we can crash the interpreter. + """ + + #: Has our deallocation actually run and switched greenlets? + #: When it does, this will be set to the current greenlet. This should + #: be happening in the main greenlet, so we check that down below. + SPAWNED = False + + #: Has the background greenlet run? + BG_RAN = False + + BG_GLET = None + + #: How many of these things have ever been allocated. + CREATED = 0 + + #: How many of these things have ever been deallocated. + DESTROYED = 0 + + #: How many were destroyed not in the main greenlet. There should always + #: be some. + #: If the test is broken or things change in the trashcan implementation, + #: this may not be correct. + DESTROYED_BG = 0 + + def __init__(self, sequence_number): + """ + :param sequence_number: The ordinal of this object during + one particular creation run. This is used to detect (guess, really) + when we have entered the trash can's deferred deallocation. + """ + self.i = sequence_number + Dealloc.CREATED += 1 + + def __del__(self): + if self.i == TRASH_UNWIND_LEVEL and not self.SPAWNED: + Dealloc.SPAWNED = greenlet.getcurrent() + other = Dealloc.BG_GLET = greenlet.greenlet(background_greenlet) + x = other.switch() + assert x == 42 + # It's important that we don't switch back to the greenlet, + # we leave it hanging there in an incomplete state. But we don't let it + # get collected, either. If we complete it now, while we're still + # in the scope of the initial trash can, things work out and we + # don't see the problem. We need this greenlet to complete + # at some point in the future, after we've exited this trash can invocation. + del other + elif self.i == 40 and greenlet.getcurrent() is not main: + Dealloc.BG_RAN = True + try: + main.switch(42) + except greenlet.GreenletExit as ex: + # We expect this; all references to us go away + # while we're still running, and we need to finish deleting + # ourself. + Dealloc.BG_RAN = type(ex) + del ex + + # Record the fact that we're dead last of all. This ensures that + # we actually get returned too. + Dealloc.DESTROYED += 1 + if greenlet.getcurrent() is not main: + Dealloc.DESTROYED_BG += 1 + + + def background_greenlet(): + # We direct through a second function, instead of + # directly calling ``make_some()``, so that we have complete + # control over when these objects are destroyed: we need them + # to be destroyed in the context of the background greenlet + t = make_some() + del t # Triggere deletion. + + def make_some(): + t = () + i = OBJECTS_PER_CONTAINER + while i: + # Nest the tuples; it's the recursion that gets us + # into trash. + t = (Dealloc(i), t) + i -= 1 + return t + + + some = make_some() + self.assertEqual(Dealloc.CREATED, OBJECTS_PER_CONTAINER) + self.assertEqual(Dealloc.DESTROYED, 0) + + # If we're going to crash, it should be on the following line. + # We only crash if ``assert()`` is enabled, of course. + del some + + # For non-debug builds of CPython, we won't crash. The best we can do is check + # the nesting level explicitly. + self.assertEqual(0, get_tstate_trash_delete_nesting()) + + # Discard this, raising GreenletExit into where it is waiting. + Dealloc.BG_GLET = None + # The same nesting level maintains. + self.assertEqual(0, get_tstate_trash_delete_nesting()) + + # We definitely cleaned some up in the background + self.assertGreater(Dealloc.DESTROYED_BG, 0) + + # Make sure all the cleanups happened. + self.assertIs(Dealloc.SPAWNED, main) + self.assertTrue(Dealloc.BG_RAN) + self.assertEqual(Dealloc.BG_RAN, greenlet.GreenletExit) + self.assertEqual(Dealloc.CREATED, Dealloc.DESTROYED ) + self.assertEqual(Dealloc.CREATED, OBJECTS_PER_CONTAINER * 2) + + import gc + gc.collect() + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_leaks.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_leaks.py new file mode 100644 index 0000000..e09da7d --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_leaks.py @@ -0,0 +1,457 @@ +# -*- coding: utf-8 -*- +""" +Testing scenarios that may have leaked. +""" +from __future__ import print_function, absolute_import, division + +import sys +import gc + +import time +import weakref +import threading + + +import greenlet +from . import TestCase +from . import PY314 +from . import RUNNING_ON_FREETHREAD_BUILD +from .leakcheck import fails_leakcheck +from .leakcheck import ignores_leakcheck +from .leakcheck import RUNNING_ON_MANYLINUX + + +# pylint:disable=protected-access + +assert greenlet.GREENLET_USE_GC # Option to disable this was removed in 1.0 + +class HasFinalizerTracksInstances(object): + EXTANT_INSTANCES = set() + def __init__(self, msg): + self.msg = sys.intern(msg) + self.EXTANT_INSTANCES.add(id(self)) + def __del__(self): + self.EXTANT_INSTANCES.remove(id(self)) + def __repr__(self): + return "" % ( + id(self), self.msg + ) + @classmethod + def reset(cls): + cls.EXTANT_INSTANCES.clear() + + +def fails_leakcheck_except_on_free_thraded(func): + if RUNNING_ON_FREETHREAD_BUILD: + # These all seem to pass on free threading because + # of the changes to the garbage collector + return func + return fails_leakcheck(func) + + +class TestLeaks(TestCase): + + def test_arg_refs(self): + args = ('a', 'b', 'c') + refcount_before = sys.getrefcount(args) + # pylint:disable=unnecessary-lambda + g = greenlet.greenlet( + lambda *args: greenlet.getcurrent().parent.switch(*args)) + for _ in range(100): + g.switch(*args) + self.assertEqual(sys.getrefcount(args), refcount_before) + + def test_kwarg_refs(self): + kwargs = {} + self.assertEqual(sys.getrefcount(kwargs), 2 if not PY314 else 1) + # pylint:disable=unnecessary-lambda + g = greenlet.greenlet( + lambda **gkwargs: greenlet.getcurrent().parent.switch(**gkwargs)) + for _ in range(100): + g.switch(**kwargs) + # Python 3.14 elides reference counting operations + # in some cases. See https://github.com/python/cpython/pull/130708 + self.assertEqual(sys.getrefcount(kwargs), 2 if not PY314 else 1) + + + @staticmethod + def __recycle_threads(): + # By introducing a thread that does sleep we allow other threads, + # that have triggered their __block condition, but did not have a + # chance to deallocate their thread state yet, to finally do so. + # The way it works is by requiring a GIL switch (different thread), + # which does a GIL release (sleep), which might do a GIL switch + # to finished threads and allow them to clean up. + def worker(): + time.sleep(0.001) + t = threading.Thread(target=worker) + t.start() + time.sleep(0.001) + t.join(10) + + def test_threaded_leak(self): + gg = [] + def worker(): + # only main greenlet present + gg.append(weakref.ref(greenlet.getcurrent())) + for _ in range(2): + t = threading.Thread(target=worker) + t.start() + t.join(10) + del t + greenlet.getcurrent() # update ts_current + self.__recycle_threads() + greenlet.getcurrent() # update ts_current + gc.collect() + greenlet.getcurrent() # update ts_current + for g in gg: + self.assertIsNone(g()) + + def test_threaded_adv_leak(self): + gg = [] + def worker(): + # main and additional *finished* greenlets + ll = greenlet.getcurrent().ll = [] + def additional(): + ll.append(greenlet.getcurrent()) + for _ in range(2): + greenlet.greenlet(additional).switch() + gg.append(weakref.ref(greenlet.getcurrent())) + for _ in range(2): + t = threading.Thread(target=worker) + t.start() + t.join(10) + del t + greenlet.getcurrent() # update ts_current + self.__recycle_threads() + greenlet.getcurrent() # update ts_current + gc.collect() + greenlet.getcurrent() # update ts_current + for g in gg: + self.assertIsNone(g()) + + def assertClocksUsed(self): + used = greenlet._greenlet.get_clocks_used_doing_optional_cleanup() + self.assertGreaterEqual(used, 0) + # we don't lose the value + greenlet._greenlet.enable_optional_cleanup(True) + used2 = greenlet._greenlet.get_clocks_used_doing_optional_cleanup() + self.assertEqual(used, used2) + self.assertGreater(greenlet._greenlet.CLOCKS_PER_SEC, 1) + + def _check_issue251(self, + manually_collect_background=True, + explicit_reference_to_switch=False): + # See https://github.com/python-greenlet/greenlet/issues/251 + # Killing a greenlet (probably not the main one) + # in one thread from another thread would + # result in leaking a list (the ts_delkey list). + # We no longer use lists to hold that stuff, though. + + # For the test to be valid, even empty lists have to be tracked by the + # GC + + assert gc.is_tracked([]) + HasFinalizerTracksInstances.reset() + greenlet.getcurrent() + greenlets_before = self.count_objects(greenlet.greenlet, exact_kind=False) + + background_glet_running = threading.Event() + background_glet_killed = threading.Event() + background_greenlets = [] + + # XXX: Switching this to a greenlet subclass that overrides + # run results in all callers failing the leaktest; that + # greenlet instance is leaked. There's a bound method for + # run() living on the stack of the greenlet in g_initialstub, + # and since we don't manually switch back to the background + # greenlet to let it "fall off the end" and exit the + # g_initialstub function, it never gets cleaned up. Making the + # garbage collector aware of this bound method (making it an + # attribute of the greenlet structure and traversing into it) + # doesn't help, for some reason. + def background_greenlet(): + # Throw control back to the main greenlet. + jd = HasFinalizerTracksInstances("DELETING STACK OBJECT") + greenlet._greenlet.set_thread_local( + 'test_leaks_key', + HasFinalizerTracksInstances("DELETING THREAD STATE")) + # Explicitly keeping 'switch' in a local variable + # breaks this test in all versions + if explicit_reference_to_switch: + s = greenlet.getcurrent().parent.switch + s([jd]) + else: + greenlet.getcurrent().parent.switch([jd]) + + bg_main_wrefs = [] + + def background_thread(): + glet = greenlet.greenlet(background_greenlet) + bg_main_wrefs.append(weakref.ref(glet.parent)) + + background_greenlets.append(glet) + glet.switch() # Be sure it's active. + # Control is ours again. + del glet # Delete one reference from the thread it runs in. + background_glet_running.set() + background_glet_killed.wait(10) + + # To trigger the background collection of the dead + # greenlet, thus clearing out the contents of the list, we + # need to run some APIs. See issue 252. + if manually_collect_background: + greenlet.getcurrent() + + + t = threading.Thread(target=background_thread) + t.start() + background_glet_running.wait(10) + greenlet.getcurrent() + lists_before = self.count_objects(list, exact_kind=True) + + assert len(background_greenlets) == 1 + self.assertFalse(background_greenlets[0].dead) + # Delete the last reference to the background greenlet + # from a different thread. This puts it in the background thread's + # ts_delkey list. + del background_greenlets[:] + background_glet_killed.set() + + # Now wait for the background thread to die. + t.join(10) + del t + # As part of the fix for 252, we need to cycle the ceval.c + # interpreter loop to be sure it has had a chance to process + # the pending call. + self.wait_for_pending_cleanups() + + lists_after = self.count_objects(list, exact_kind=True) + greenlets_after = self.count_objects(greenlet.greenlet, exact_kind=False) + + # On 2.7, we observe that lists_after is smaller than + # lists_before. No idea what lists got cleaned up. All the + # Python 3 versions match exactly. + self.assertLessEqual(lists_after, lists_before) + # On versions after 3.6, we've successfully cleaned up the + # greenlet references thanks to the internal "vectorcall" + # protocol; prior to that, there is a reference path through + # the ``greenlet.switch`` method still on the stack that we + # can't reach to clean up. The C code goes through terrific + # lengths to clean that up. + if not explicit_reference_to_switch \ + and greenlet._greenlet.get_clocks_used_doing_optional_cleanup() is not None: + # If cleanup was disabled, though, we may not find it. + self.assertEqual(greenlets_after, greenlets_before) + if manually_collect_background: + # TODO: Figure out how to make this work! + # The one on the stack is still leaking somehow + # in the non-manually-collect state. + self.assertEqual(HasFinalizerTracksInstances.EXTANT_INSTANCES, set()) + else: + # The explicit reference prevents us from collecting it + # and it isn't always found by the GC either for some + # reason. The entire frame is leaked somehow, on some + # platforms (e.g., MacPorts builds of Python (all + # versions!)), but not on other platforms (the linux and + # windows builds on GitHub actions and Appveyor). So we'd + # like to write a test that proves that the main greenlet + # sticks around, and we can on my machine (macOS 11.6, + # MacPorts builds of everything) but we can't write that + # same test on other platforms. However, hopefully iteration + # done by leakcheck will find it. + pass + + if greenlet._greenlet.get_clocks_used_doing_optional_cleanup() is not None: + self.assertClocksUsed() + + def test_issue251_killing_cross_thread_leaks_list(self): + self._check_issue251() + + def test_issue251_with_cleanup_disabled(self): + greenlet._greenlet.enable_optional_cleanup(False) + try: + self._check_issue251() + finally: + greenlet._greenlet.enable_optional_cleanup(True) + + @fails_leakcheck_except_on_free_thraded + def test_issue251_issue252_need_to_collect_in_background(self): + # Between greenlet 1.1.2 and the next version, this was still + # failing because the leak of the list still exists when we + # don't call a greenlet API before exiting the thread. The + # proximate cause is that neither of the two greenlets from + # the background thread are actually being destroyed, even + # though the GC is in fact visiting both objects. It's not + # clear where that leak is? For some reason the thread-local + # dict holding it isn't being cleaned up. + # + # The leak, I think, is in the CPYthon internal function that + # calls into green_switch(). The argument tuple is still on + # the C stack somewhere and can't be reached? That doesn't + # make sense, because the tuple should be collectable when + # this object goes away. + # + # Note that this test sometimes spuriously passes on Linux, + # for some reason, but I've never seen it pass on macOS. + self._check_issue251(manually_collect_background=False) + + @fails_leakcheck_except_on_free_thraded + def test_issue251_issue252_need_to_collect_in_background_cleanup_disabled(self): + self.expect_greenlet_leak = True + greenlet._greenlet.enable_optional_cleanup(False) + try: + self._check_issue251(manually_collect_background=False) + finally: + greenlet._greenlet.enable_optional_cleanup(True) + + @fails_leakcheck_except_on_free_thraded + def test_issue251_issue252_explicit_reference_not_collectable(self): + self._check_issue251( + manually_collect_background=False, + explicit_reference_to_switch=True) + + UNTRACK_ATTEMPTS = 100 + + def _only_test_some_versions(self): + # We're only looking for this problem specifically on 3.11, + # and this set of tests is relatively fragile, depending on + # OS and memory management details. So we want to run it on 3.11+ + # (obviously) but not every older 3.x version in order to reduce + # false negatives. At the moment, those false results seem to have + # resolved, so we are actually running this on 3.8+ + assert sys.version_info[0] >= 3 + if sys.version_info[:2] < (3, 8): + self.skipTest('Only observed on 3.11') + if RUNNING_ON_MANYLINUX: + self.skipTest("Slow and not worth repeating here") + + @ignores_leakcheck + # Because we're just trying to track raw memory, not objects, and running + # the leakcheck makes an already slow test slower. + def test_untracked_memory_doesnt_increase(self): + # See https://github.com/gevent/gevent/issues/1924 + # and https://github.com/python-greenlet/greenlet/issues/328 + self._only_test_some_versions() + def f(): + return 1 + + ITER = 10000 + def run_it(): + for _ in range(ITER): + greenlet.greenlet(f).switch() + + # Establish baseline + for _ in range(3): + run_it() + + # uss: (Linux, macOS, Windows): aka "Unique Set Size", this is + # the memory which is unique to a process and which would be + # freed if the process was terminated right now. + uss_before = self.get_process_uss() + + for count in range(self.UNTRACK_ATTEMPTS): + uss_before = max(uss_before, self.get_process_uss()) + run_it() + + uss_after = self.get_process_uss() + if uss_after <= uss_before and count > 1: + break + + self.assertLessEqual(uss_after, uss_before) + + def _check_untracked_memory_thread(self, deallocate_in_thread=True): + self._only_test_some_versions() + # Like the above test, but what if there are a bunch of + # unfinished greenlets in a thread that dies? + # Does it matter if we deallocate in the thread or not? + EXIT_COUNT = [0] + + def f(): + try: + greenlet.getcurrent().parent.switch() + except greenlet.GreenletExit: + EXIT_COUNT[0] += 1 + raise + return 1 + + ITER = 10000 + def run_it(): + glets = [] + for _ in range(ITER): + # Greenlet starts, switches back to us. + # We keep a strong reference to the greenlet though so it doesn't + # get a GreenletExit exception. + g = greenlet.greenlet(f) + glets.append(g) + g.switch() + + return glets + + test = self + + class ThreadFunc: + uss_before = uss_after = 0 + glets = () + ITER = 2 + def __call__(self): + self.uss_before = test.get_process_uss() + + for _ in range(self.ITER): + self.glets += tuple(run_it()) + + for g in self.glets: + test.assertIn('suspended active', str(g)) + # Drop them. + if deallocate_in_thread: + self.glets = () + self.uss_after = test.get_process_uss() + + # Establish baseline + uss_before = uss_after = None + for count in range(self.UNTRACK_ATTEMPTS): + EXIT_COUNT[0] = 0 + thread_func = ThreadFunc() + t = threading.Thread(target=thread_func) + t.start() + t.join(30) + self.assertFalse(t.is_alive()) + + if uss_before is None: + uss_before = thread_func.uss_before + + uss_before = max(uss_before, thread_func.uss_before) + if deallocate_in_thread: + self.assertEqual(thread_func.glets, ()) + self.assertEqual(EXIT_COUNT[0], ITER * thread_func.ITER) + + del thread_func # Deallocate the greenlets; but this won't raise into them + del t + if not deallocate_in_thread: + self.assertEqual(EXIT_COUNT[0], 0) + if deallocate_in_thread: + self.wait_for_pending_cleanups() + + uss_after = self.get_process_uss() + # See if we achieve a non-growth state at some point. Break when we do. + if uss_after <= uss_before and count > 1: + break + + self.wait_for_pending_cleanups() + uss_after = self.get_process_uss() + self.assertLessEqual(uss_after, uss_before, "after attempts %d" % (count,)) + + @ignores_leakcheck + # Because we're just trying to track raw memory, not objects, and running + # the leakcheck makes an already slow test slower. + def test_untracked_memory_doesnt_increase_unfinished_thread_dealloc_in_thread(self): + self._check_untracked_memory_thread(deallocate_in_thread=True) + + @ignores_leakcheck + # Because the main greenlets from the background threads do not exit in a timely fashion, + # we fail the object-based leakchecks. + def test_untracked_memory_doesnt_increase_unfinished_thread_dealloc_in_main(self): + self._check_untracked_memory_thread(deallocate_in_thread=False) + +if __name__ == '__main__': + __import__('unittest').main() diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_stack_saved.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_stack_saved.py new file mode 100644 index 0000000..b362bf9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_stack_saved.py @@ -0,0 +1,19 @@ +import greenlet +from . import TestCase + + +class Test(TestCase): + + def test_stack_saved(self): + main = greenlet.getcurrent() + self.assertEqual(main._stack_saved, 0) + + def func(): + main.switch(main._stack_saved) + + g = greenlet.greenlet(func) + x = g.switch() + self.assertGreater(x, 0) + self.assertGreater(g._stack_saved, 0) + g.switch() + self.assertEqual(g._stack_saved, 0) diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_throw.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_throw.py new file mode 100644 index 0000000..f4f9a14 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_throw.py @@ -0,0 +1,128 @@ +import sys + + +from greenlet import greenlet +from . import TestCase + +def switch(*args): + return greenlet.getcurrent().parent.switch(*args) + + +class ThrowTests(TestCase): + def test_class(self): + def f(): + try: + switch("ok") + except RuntimeError: + switch("ok") + return + switch("fail") + g = greenlet(f) + res = g.switch() + self.assertEqual(res, "ok") + res = g.throw(RuntimeError) + self.assertEqual(res, "ok") + + def test_val(self): + def f(): + try: + switch("ok") + except RuntimeError: + val = sys.exc_info()[1] + if str(val) == "ciao": + switch("ok") + return + switch("fail") + + g = greenlet(f) + res = g.switch() + self.assertEqual(res, "ok") + res = g.throw(RuntimeError("ciao")) + self.assertEqual(res, "ok") + + g = greenlet(f) + res = g.switch() + self.assertEqual(res, "ok") + res = g.throw(RuntimeError, "ciao") + self.assertEqual(res, "ok") + + def test_kill(self): + def f(): + switch("ok") + switch("fail") + g = greenlet(f) + res = g.switch() + self.assertEqual(res, "ok") + res = g.throw() + self.assertTrue(isinstance(res, greenlet.GreenletExit)) + self.assertTrue(g.dead) + res = g.throw() # immediately eaten by the already-dead greenlet + self.assertTrue(isinstance(res, greenlet.GreenletExit)) + + def test_throw_goes_to_original_parent(self): + main = greenlet.getcurrent() + + def f1(): + try: + main.switch("f1 ready to catch") + except IndexError: + return "caught" + return "normal exit" + + def f2(): + main.switch("from f2") + + g1 = greenlet(f1) + g2 = greenlet(f2, parent=g1) + with self.assertRaises(IndexError): + g2.throw(IndexError) + self.assertTrue(g2.dead) + self.assertTrue(g1.dead) + + g1 = greenlet(f1) + g2 = greenlet(f2, parent=g1) + res = g1.switch() + self.assertEqual(res, "f1 ready to catch") + res = g2.throw(IndexError) + self.assertEqual(res, "caught") + self.assertTrue(g2.dead) + self.assertTrue(g1.dead) + + g1 = greenlet(f1) + g2 = greenlet(f2, parent=g1) + res = g1.switch() + self.assertEqual(res, "f1 ready to catch") + res = g2.switch() + self.assertEqual(res, "from f2") + res = g2.throw(IndexError) + self.assertEqual(res, "caught") + self.assertTrue(g2.dead) + self.assertTrue(g1.dead) + + def test_non_traceback_param(self): + with self.assertRaises(TypeError) as exc: + greenlet.getcurrent().throw( + Exception, + Exception(), + self + ) + self.assertEqual(str(exc.exception), + "throw() third argument must be a traceback object") + + def test_instance_of_wrong_type(self): + with self.assertRaises(TypeError) as exc: + greenlet.getcurrent().throw( + Exception(), + BaseException() + ) + + self.assertEqual(str(exc.exception), + "instance exception may not have a separate value") + + def test_not_throwable(self): + with self.assertRaises(TypeError) as exc: + greenlet.getcurrent().throw( + "abc" + ) + self.assertEqual(str(exc.exception), + "exceptions must be classes, or instances, not str") diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_tracing.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_tracing.py new file mode 100644 index 0000000..235fbcd --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_tracing.py @@ -0,0 +1,299 @@ +from __future__ import print_function +import sys +import sysconfig +import greenlet +import unittest + +from . import TestCase +from . import PY312 + +# https://discuss.python.org/t/cpython-3-12-greenlet-and-tracing-profiling-how-to-not-crash-and-get-correct-results/33144/2 +# When build variables are available, OPT is the best way of detecting +# the build with assertions enabled. Otherwise, fallback to detecting PyDEBUG +# build. +ASSERTION_BUILD_PY312 = ( + PY312 and ( + "-DNDEBUG" not in sysconfig.get_config_var("OPT").split() + if sysconfig.get_config_var("OPT") is not None + else hasattr(sys, 'gettotalrefcount') + ), + "Broken on assertion-enabled builds of Python 3.12" +) + +class SomeError(Exception): + pass + +class GreenletTracer(object): + oldtrace = None + + def __init__(self, error_on_trace=False): + self.actions = [] + self.error_on_trace = error_on_trace + + def __call__(self, *args): + self.actions.append(args) + if self.error_on_trace: + raise SomeError + + def __enter__(self): + self.oldtrace = greenlet.settrace(self) + return self.actions + + def __exit__(self, *args): + greenlet.settrace(self.oldtrace) + + +class TestGreenletTracing(TestCase): + """ + Tests of ``greenlet.settrace()`` + """ + + def test_a_greenlet_tracing(self): + main = greenlet.getcurrent() + def dummy(): + pass + def dummyexc(): + raise SomeError() + + with GreenletTracer() as actions: + g1 = greenlet.greenlet(dummy) + g1.switch() + g2 = greenlet.greenlet(dummyexc) + self.assertRaises(SomeError, g2.switch) + + self.assertEqual(actions, [ + ('switch', (main, g1)), + ('switch', (g1, main)), + ('switch', (main, g2)), + ('throw', (g2, main)), + ]) + + def test_b_exception_disables_tracing(self): + main = greenlet.getcurrent() + def dummy(): + main.switch() + g = greenlet.greenlet(dummy) + g.switch() + with GreenletTracer(error_on_trace=True) as actions: + self.assertRaises(SomeError, g.switch) + self.assertEqual(greenlet.gettrace(), None) + + self.assertEqual(actions, [ + ('switch', (main, g)), + ]) + + def test_set_same_tracer_twice(self): + # https://github.com/python-greenlet/greenlet/issues/332 + # Our logic in asserting that the tracefunction should + # gain a reference was incorrect if the same tracefunction was set + # twice. + tracer = GreenletTracer() + with tracer: + greenlet.settrace(tracer) + + +class PythonTracer(object): + oldtrace = None + + def __init__(self): + self.actions = [] + + def __call__(self, frame, event, arg): + # Record the co_name so we have an idea what function we're in. + self.actions.append((event, frame.f_code.co_name)) + + def __enter__(self): + self.oldtrace = sys.setprofile(self) + return self.actions + + def __exit__(self, *args): + sys.setprofile(self.oldtrace) + +def tpt_callback(): + return 42 + +class TestPythonTracing(TestCase): + """ + Tests of the interaction of ``sys.settrace()`` + with greenlet facilities. + + NOTE: Most of this is probably CPython specific. + """ + + maxDiff = None + + def test_trace_events_trivial(self): + with PythonTracer() as actions: + tpt_callback() + # If we use the sys.settrace instead of setprofile, we get + # this: + + # self.assertEqual(actions, [ + # ('call', 'tpt_callback'), + # ('call', '__exit__'), + # ]) + + self.assertEqual(actions, [ + ('return', '__enter__'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('call', '__exit__'), + ('c_call', '__exit__'), + ]) + + def _trace_switch(self, glet): + with PythonTracer() as actions: + glet.switch() + return actions + + def _check_trace_events_func_already_set(self, glet): + actions = self._trace_switch(glet) + self.assertEqual(actions, [ + ('return', '__enter__'), + ('c_call', '_trace_switch'), + ('call', 'run'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('return', 'run'), + ('c_return', '_trace_switch'), + ('call', '__exit__'), + ('c_call', '__exit__'), + ]) + + def test_trace_events_into_greenlet_func_already_set(self): + def run(): + return tpt_callback() + + self._check_trace_events_func_already_set(greenlet.greenlet(run)) + + def test_trace_events_into_greenlet_subclass_already_set(self): + class X(greenlet.greenlet): + def run(self): + return tpt_callback() + self._check_trace_events_func_already_set(X()) + + def _check_trace_events_from_greenlet_sets_profiler(self, g, tracer): + g.switch() + tpt_callback() + tracer.__exit__() + self.assertEqual(tracer.actions, [ + ('return', '__enter__'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('return', 'run'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('call', '__exit__'), + ('c_call', '__exit__'), + ]) + + + def test_trace_events_from_greenlet_func_sets_profiler(self): + tracer = PythonTracer() + def run(): + tracer.__enter__() + return tpt_callback() + + self._check_trace_events_from_greenlet_sets_profiler(greenlet.greenlet(run), + tracer) + + def test_trace_events_from_greenlet_subclass_sets_profiler(self): + tracer = PythonTracer() + class X(greenlet.greenlet): + def run(self): + tracer.__enter__() + return tpt_callback() + + self._check_trace_events_from_greenlet_sets_profiler(X(), tracer) + + @unittest.skipIf(*ASSERTION_BUILD_PY312) + def test_trace_events_multiple_greenlets_switching(self): + tracer = PythonTracer() + + g1 = None + g2 = None + + def g1_run(): + tracer.__enter__() + tpt_callback() + g2.switch() + tpt_callback() + return 42 + + def g2_run(): + tpt_callback() + tracer.__exit__() + tpt_callback() + g1.switch() + + g1 = greenlet.greenlet(g1_run) + g2 = greenlet.greenlet(g2_run) + + x = g1.switch() + self.assertEqual(x, 42) + tpt_callback() # ensure not in the trace + self.assertEqual(tracer.actions, [ + ('return', '__enter__'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('c_call', 'g1_run'), + ('call', 'g2_run'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('call', '__exit__'), + ('c_call', '__exit__'), + ]) + + @unittest.skipIf(*ASSERTION_BUILD_PY312) + def test_trace_events_multiple_greenlets_switching_siblings(self): + # Like the first version, but get both greenlets running first + # as "siblings" and then establish the tracing. + tracer = PythonTracer() + + g1 = None + g2 = None + + def g1_run(): + greenlet.getcurrent().parent.switch() + tracer.__enter__() + tpt_callback() + g2.switch() + tpt_callback() + return 42 + + def g2_run(): + greenlet.getcurrent().parent.switch() + + tpt_callback() + tracer.__exit__() + tpt_callback() + g1.switch() + + g1 = greenlet.greenlet(g1_run) + g2 = greenlet.greenlet(g2_run) + + # Start g1 + g1.switch() + # And it immediately returns control to us. + # Start g2 + g2.switch() + # Which also returns. Now kick of the real part of the + # test. + x = g1.switch() + self.assertEqual(x, 42) + + tpt_callback() # ensure not in the trace + self.assertEqual(tracer.actions, [ + ('return', '__enter__'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('c_call', 'g1_run'), + ('call', 'tpt_callback'), + ('return', 'tpt_callback'), + ('call', '__exit__'), + ('c_call', '__exit__'), + ]) + + +if __name__ == '__main__': + unittest.main() diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_version.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_version.py new file mode 100644 index 0000000..96c17cf --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_version.py @@ -0,0 +1,41 @@ +#! /usr/bin/env python +from __future__ import absolute_import +from __future__ import print_function + +import sys +import os +from unittest import TestCase as NonLeakingTestCase + +import greenlet + +# No reason to run this multiple times under leakchecks, +# it doesn't do anything. +class VersionTests(NonLeakingTestCase): + def test_version(self): + def find_dominating_file(name): + if os.path.exists(name): + return name + + tried = [] + here = os.path.abspath(os.path.dirname(__file__)) + for i in range(10): + up = ['..'] * i + path = [here] + up + [name] + fname = os.path.join(*path) + fname = os.path.abspath(fname) + tried.append(fname) + if os.path.exists(fname): + return fname + raise AssertionError("Could not find file " + name + "; checked " + str(tried)) + + try: + setup_py = find_dominating_file('setup.py') + except AssertionError as e: + self.skipTest("Unable to find setup.py; must be out of tree. " + str(e)) + + + invoke_setup = "%s %s --version" % (sys.executable, setup_py) + with os.popen(invoke_setup) as f: + sversion = f.read().strip() + + self.assertEqual(sversion, greenlet.__version__) diff --git a/venv/lib/python3.12/site-packages/greenlet/tests/test_weakref.py b/venv/lib/python3.12/site-packages/greenlet/tests/test_weakref.py new file mode 100644 index 0000000..05a38a7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/tests/test_weakref.py @@ -0,0 +1,35 @@ +import gc +import weakref + + +import greenlet +from . import TestCase + +class WeakRefTests(TestCase): + def test_dead_weakref(self): + def _dead_greenlet(): + g = greenlet.greenlet(lambda: None) + g.switch() + return g + o = weakref.ref(_dead_greenlet()) + gc.collect() + self.assertEqual(o(), None) + + def test_inactive_weakref(self): + o = weakref.ref(greenlet.greenlet()) + gc.collect() + self.assertEqual(o(), None) + + def test_dealloc_weakref(self): + seen = [] + def worker(): + try: + greenlet.getcurrent().parent.switch() + finally: + seen.append(g()) + g = greenlet.greenlet(worker) + g.switch() + g2 = greenlet.greenlet(lambda: None, g) + g = weakref.ref(g2) + g2 = None + self.assertEqual(seen, [None]) diff --git a/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/METADATA b/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/METADATA new file mode 100644 index 0000000..8a2f639 --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/METADATA @@ -0,0 +1,202 @@ +Metadata-Version: 2.4 +Name: h11 +Version: 0.16.0 +Summary: A pure-Python, bring-your-own-I/O implementation of HTTP/1.1 +Home-page: https://github.com/python-hyper/h11 +Author: Nathaniel J. Smith +Author-email: njs@pobox.com +License: MIT +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: System :: Networking +Requires-Python: >=3.8 +License-File: LICENSE.txt +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: requires-python +Dynamic: summary + +h11 +=== + +.. image:: https://travis-ci.org/python-hyper/h11.svg?branch=master + :target: https://travis-ci.org/python-hyper/h11 + :alt: Automated test status + +.. image:: https://codecov.io/gh/python-hyper/h11/branch/master/graph/badge.svg + :target: https://codecov.io/gh/python-hyper/h11 + :alt: Test coverage + +.. image:: https://readthedocs.org/projects/h11/badge/?version=latest + :target: http://h11.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + +This is a little HTTP/1.1 library written from scratch in Python, +heavily inspired by `hyper-h2 `_. + +It's a "bring-your-own-I/O" library; h11 contains no IO code +whatsoever. This means you can hook h11 up to your favorite network +API, and that could be anything you want: synchronous, threaded, +asynchronous, or your own implementation of `RFC 6214 +`_ -- h11 won't judge you. +(Compare this to the current state of the art, where every time a `new +network API `_ comes along then someone +gets to start over reimplementing the entire HTTP protocol from +scratch.) Cory Benfield made an `excellent blog post describing the +benefits of this approach +`_, or if you like video +then here's his `PyCon 2016 talk on the same theme +`_. + +This also means that h11 is not immediately useful out of the box: +it's a toolkit for building programs that speak HTTP, not something +that could directly replace ``requests`` or ``twisted.web`` or +whatever. But h11 makes it much easier to implement something like +``requests`` or ``twisted.web``. + +At a high level, working with h11 goes like this: + +1) First, create an ``h11.Connection`` object to track the state of a + single HTTP/1.1 connection. + +2) When you read data off the network, pass it to + ``conn.receive_data(...)``; you'll get back a list of objects + representing high-level HTTP "events". + +3) When you want to send a high-level HTTP event, create the + corresponding "event" object and pass it to ``conn.send(...)``; + this will give you back some bytes that you can then push out + through the network. + +For example, a client might instantiate and then send a +``h11.Request`` object, then zero or more ``h11.Data`` objects for the +request body (e.g., if this is a POST), and then a +``h11.EndOfMessage`` to indicate the end of the message. Then the +server would then send back a ``h11.Response``, some ``h11.Data``, and +its own ``h11.EndOfMessage``. If either side violates the protocol, +you'll get a ``h11.ProtocolError`` exception. + +h11 is suitable for implementing both servers and clients, and has a +pleasantly symmetric API: the events you send as a client are exactly +the ones that you receive as a server and vice-versa. + +`Here's an example of a tiny HTTP client +`_ + +It also has `a fine manual `_. + +FAQ +--- + +*Whyyyyy?* + +I wanted to play with HTTP in `Curio +`__ and `Trio +`__, which at the time didn't have any +HTTP libraries. So I thought, no big deal, Python has, like, a dozen +different implementations of HTTP, surely I can find one that's +reusable. I didn't find one, but I did find Cory's call-to-arms +blog-post. So I figured, well, fine, if I have to implement HTTP from +scratch, at least I can make sure no-one *else* has to ever again. + +*Should I use it?* + +Maybe. You should be aware that it's a very young project. But, it's +feature complete and has an exhaustive test-suite and complete docs, +so the next step is for people to try using it and see how it goes +:-). If you do then please let us know -- if nothing else we'll want +to talk to you before making any incompatible changes! + +*What are the features/limitations?* + +Roughly speaking, it's trying to be a robust, complete, and non-hacky +implementation of the first "chapter" of the HTTP/1.1 spec: `RFC 7230: +HTTP/1.1 Message Syntax and Routing +`_. That is, it mostly focuses on +implementing HTTP at the level of taking bytes on and off the wire, +and the headers related to that, and tries to be anal about spec +conformance. It doesn't know about higher-level concerns like URL +routing, conditional GETs, cross-origin cookie policies, or content +negotiation. But it does know how to take care of framing, +cross-version differences in keep-alive handling, and the "obsolete +line folding" rule, so you can focus your energies on the hard / +interesting parts for your application, and it tries to support the +full specification in the sense that any useful HTTP/1.1 conformant +application should be able to use h11. + +It's pure Python, and has no dependencies outside of the standard +library. + +It has a test suite with 100.0% coverage for both statements and +branches. + +Currently it supports Python 3 (testing on 3.8-3.12) and PyPy 3. +The last Python 2-compatible version was h11 0.11.x. +(Originally it had a Cython wrapper for `http-parser +`_ and a beautiful nested state +machine implemented with ``yield from`` to postprocess the output. But +I had to take these out -- the new *parser* needs fewer lines-of-code +than the old *parser wrapper*, is written in pure Python, uses no +exotic language syntax, and has more features. It's sad, really; that +old state machine was really slick. I just need a few sentences here +to mourn that.) + +I don't know how fast it is. I haven't benchmarked or profiled it yet, +so it's probably got a few pointless hot spots, and I've been trying +to err on the side of simplicity and robustness instead of +micro-optimization. But at the architectural level I tried hard to +avoid fundamentally bad decisions, e.g., I believe that all the +parsing algorithms remain linear-time even in the face of pathological +input like slowloris, and there are no byte-by-byte loops. (I also +believe that it maintains bounded memory usage in the face of +arbitrary/pathological input.) + +The whole library is ~800 lines-of-code. You can read and understand +the whole thing in less than an hour. Most of the energy invested in +this so far has been spent on trying to keep things simple by +minimizing special-cases and ad hoc state manipulation; even though it +is now quite small and simple, I'm still annoyed that I haven't +figured out how to make it even smaller and simpler. (Unfortunately, +HTTP does not lend itself to simplicity.) + +The API is ~feature complete and I don't expect the general outlines +to change much, but you can't judge an API's ergonomics until you +actually document and use it, so I'd expect some changes in the +details. + +*How do I try it?* + +.. code-block:: sh + + $ pip install h11 + $ git clone git@github.com:python-hyper/h11 + $ cd h11/examples + $ python basic-client.py + +and go from there. + +*License?* + +MIT + +*Code of conduct?* + +Contributors are requested to follow our `code of conduct +`_ in +all project spaces. diff --git a/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/RECORD b/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/RECORD new file mode 100644 index 0000000..a8f8e63 --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/RECORD @@ -0,0 +1,29 @@ +h11-0.16.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +h11-0.16.0.dist-info/METADATA,sha256=KPMmCYrAn8unm48YD5YIfIQf4kViFct7hyqcfVzRnWQ,8348 +h11-0.16.0.dist-info/RECORD,, +h11-0.16.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91 +h11-0.16.0.dist-info/licenses/LICENSE.txt,sha256=N9tbuFkm2yikJ6JYZ_ELEjIAOuob5pzLhRE4rbjm82E,1124 +h11-0.16.0.dist-info/top_level.txt,sha256=F7dC4jl3zeh8TGHEPaWJrMbeuoWbS379Gwdi-Yvdcis,4 +h11/__init__.py,sha256=iO1KzkSO42yZ6ffg-VMgbx_ZVTWGUY00nRYEWn-s3kY,1507 +h11/__pycache__/__init__.cpython-312.pyc,, +h11/__pycache__/_abnf.cpython-312.pyc,, +h11/__pycache__/_connection.cpython-312.pyc,, +h11/__pycache__/_events.cpython-312.pyc,, +h11/__pycache__/_headers.cpython-312.pyc,, +h11/__pycache__/_readers.cpython-312.pyc,, +h11/__pycache__/_receivebuffer.cpython-312.pyc,, +h11/__pycache__/_state.cpython-312.pyc,, +h11/__pycache__/_util.cpython-312.pyc,, +h11/__pycache__/_version.cpython-312.pyc,, +h11/__pycache__/_writers.cpython-312.pyc,, +h11/_abnf.py,sha256=ybixr0xsupnkA6GFAyMubuXF6Tc1lb_hF890NgCsfNc,4815 +h11/_connection.py,sha256=k9YRVf6koZqbttBW36xSWaJpWdZwa-xQVU9AHEo9DuI,26863 +h11/_events.py,sha256=I97aXoal1Wu7dkL548BANBUCkOIbe-x5CioYA9IBY14,11792 +h11/_headers.py,sha256=P7D-lBNxHwdLZPLimmYwrPG-9ZkjElvvJZJdZAgSP-4,10412 +h11/_readers.py,sha256=a4RypORUCC3d0q_kxPuBIM7jTD8iLt5X91TH0FsduN4,8590 +h11/_receivebuffer.py,sha256=xrspsdsNgWFxRfQcTXxR8RrdjRXXTK0Io5cQYWpJ1Ws,5252 +h11/_state.py,sha256=_5LG_BGR8FCcFQeBPH-TMHgm_-B-EUcWCnQof_9XjFE,13231 +h11/_util.py,sha256=LWkkjXyJaFlAy6Lt39w73UStklFT5ovcvo0TkY7RYuk,4888 +h11/_version.py,sha256=GVSsbPSPDcOuF6ptfIiXnVJoaEm3ygXbMnqlr_Giahw,686 +h11/_writers.py,sha256=oFKm6PtjeHfbj4RLX7VB7KDc1gIY53gXG3_HR9ltmTA,5081 +h11/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7 diff --git a/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/WHEEL b/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/WHEEL new file mode 100644 index 0000000..1eb3c49 --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (78.1.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt b/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..8f080ea --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016 Nathaniel J. Smith and other 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/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/top_level.txt b/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/top_level.txt new file mode 100644 index 0000000..0d24def --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/top_level.txt @@ -0,0 +1 @@ +h11 diff --git a/venv/lib/python3.12/site-packages/h11/__init__.py b/venv/lib/python3.12/site-packages/h11/__init__.py new file mode 100644 index 0000000..989e92c --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11/__init__.py @@ -0,0 +1,62 @@ +# A highish-level implementation of the HTTP/1.1 wire protocol (RFC 7230), +# containing no networking code at all, loosely modelled on hyper-h2's generic +# implementation of HTTP/2 (and in particular the h2.connection.H2Connection +# class). There's still a bunch of subtle details you need to get right if you +# want to make this actually useful, because it doesn't implement all the +# semantics to check that what you're asking to write to the wire is sensible, +# but at least it gets you out of dealing with the wire itself. + +from h11._connection import Connection, NEED_DATA, PAUSED +from h11._events import ( + ConnectionClosed, + Data, + EndOfMessage, + Event, + InformationalResponse, + Request, + Response, +) +from h11._state import ( + CLIENT, + CLOSED, + DONE, + ERROR, + IDLE, + MIGHT_SWITCH_PROTOCOL, + MUST_CLOSE, + SEND_BODY, + SEND_RESPONSE, + SERVER, + SWITCHED_PROTOCOL, +) +from h11._util import LocalProtocolError, ProtocolError, RemoteProtocolError +from h11._version import __version__ + +PRODUCT_ID = "python-h11/" + __version__ + + +__all__ = ( + "Connection", + "NEED_DATA", + "PAUSED", + "ConnectionClosed", + "Data", + "EndOfMessage", + "Event", + "InformationalResponse", + "Request", + "Response", + "CLIENT", + "CLOSED", + "DONE", + "ERROR", + "IDLE", + "MUST_CLOSE", + "SEND_BODY", + "SEND_RESPONSE", + "SERVER", + "SWITCHED_PROTOCOL", + "ProtocolError", + "LocalProtocolError", + "RemoteProtocolError", +) diff --git a/venv/lib/python3.12/site-packages/h11/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/h11/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b538577c1ee73f061e35a2210840775716e6bf1d GIT binary patch literal 1056 zcma))zi-n(6vyrSuoF9Oel<;-QedbM8Ct6_fe=!OEgC6_BPRuM3@10ak_ew2Y`0Q6 z!CwIrJO2hFOkGa`A$4MF)ge+R?zs)9NJyOIkKes__wM`d-B(Rh1U|le{ysWM3&IIZ z<|A=soPXrTdtoRHMOUCA5@`Y@Xc8qw@Fm<7O`|lGkVIu9(+tYcEXq;^DO5#jye8@9 zsD?DUfEH*TTOw;mTot-Gl+mqerk?!?V(Uq!Gf^1r~~O7*Cye__d>#Co4h$7VZ<_X zS6yXNqt!G!efV4aD4y2pm^_hfTQ*BIjh4wO?dH>+KJGnl_8U96Yg>J*VYQgj-tF~q zyp3ghX2-x!EaL^!V$(KzU8~bGnbb4wXQs`Hm)m(|^8~!?Qp+DWZa45Ff8e`j5cmPp zu20K0p?*ZJIqM=*F+L_z|gQ;E;t6dosb>ijry($aRfEOhBY>y*@KE4reTma;6}UP>iaEV#)UH=Z`3saR%48CW^3yrMMZ^t%9OIA5=k8Bifj@^3owekGq)I<5xM_&{R zZz=_QxgeD%?c_%3aAvwtI=qd`c6{Su>9ANVnPq#txmCk;RX(Ux6~mdFSeg%_OoY~iv7>KI z<-__a)qQP8HfuJVa_N&p))&G#Jn@+6O>& zHPuU<@78M?_L5yvdfXN1F7^UoEELU4T&m*5FBI`op&RC7MBv4GN?xt!%VJgE#bQOT zZ#G1@>z36J53qJ1)@z&MUIQ+ZHaj&vBbqe}7xt9OjV6$B8Y2)9UQ%RM;O|E%it>5X$GcHKhvMTdA&ar@}O@h4#1q|egKfRAW~dj?SlzWk^i zrB~L>h!h zLpL685q2ITjgEic2>dY8b(FbIGMzi&#WQ~Hl#DyS3_ZC91~R2trsyM<5uWiVUpyLY zXIbX$HjifKe-;9tL`nI>C^78D{1i&(Pq)N2qBt})P72eU7(XJ~-Wml{uQz>ngEAdYJhx4dZH{j6Ir6fwlsl=741RJHi zHTy^M`_6rJL*wz*OmZbo-#(A;JiqV!zVCde|4m6rF^A`0|LQ+Z{I3a)`!DoGe_Yzj z=DdaD?sEbsSR!21;B)ZGLE9&ukqF%o@TIw&2mifzA9($xbTH&vVR{ATWRsJd#=ZI8CYy36Q zT7ND3UJ$8^*8A((dtqdEbdP^ew87sHZS*%rc|RZB>)#t~@;62I`S(Se{ms$+{{7Jw ze+$d+jI>7E{B2R6-xqE7x3jpS$bo2wza!e|@3e5^oKXDd7JrwOJI@KOH#os9wu|i_ zl)P_O`|s~&DZ7y3*^+Wl@cMg%Qn6Sp70bq~wJ0EziHFAQLirnZ|6!p*?A?~?6kkAo zr%*|`MW=X#KGm{XSRhoP^ifu8HEOMS->S9b*i5aj?hE?jE6&(X$7V9lv%yFtcq1Za z?4h8nWbFOn5W>MQ-krl!N;n=1Ml#O$Em4ZZgF?nJl9-~*BQsNC#&Io%^e-^GoUbtB zJsXe3#1IuZ8;Q%J0AfKUm~jupgyFGEqAUl;QD)$l7*jHp7h_{_DH>EOxFX6^@t7=T z3a^MiN{Cdq9_6!TyyK!02*sn(U|>QF3Zj&$o(ReT@%9vI3-la3h{Tu@jwQs5SKgA< zX9dlH)BOWiu4Y_USZSHs?z3b{u81Kqd`mo&7#kBM42UOi_4>t;vljwqhlhp+&W@N7 z*DjyG0vefuvx650hLB)(^{Nt7FdqHGLj&mBl`F$nGL@Gup1&}n6T5t6cx3qO@L;Cs z>cCKc;LLFU4>AQ;2d;d7;7X=ct#F_}pyxt&%Le12VC1qCSK^^~WI&SQQl|Wh7>z4p zZlv=nMlu`|Bh<<3S1yjQPF|P7iYTd_v}KCTmS&2E22fM~>5d7!jHoG( zxJ{z+oAcch@u=91F5H;u4ow6VWu_Zbf2%tZzR^83qfEqOhr4ocQrFZBtD6OfL3Y!D;0-QqcS`h?#bYjuqM{NEMS`;Yt-I8L;zLpw5jjKw zBiBoBl){3e^zwp{NbzCrI~q*~S?Eg@?n!$po)+$!Pb|ci z##ReE9~Z0?9zk5%Cm9#054Bq3j_thVm^8V$M)n-q(gzG4d;#v?W=1 zi<7M{b3e6Y4Zc~z$d}-xGjo>70(4@_BVZcjCJepxW*fS>2X8idlWGNf{t_YUZR^cC z#1hgeinfJgiq9^cr3l6kWqPv$db8w=N2@rXc0!h^+dLkXRZ0Rk z;=)W|ObSNP3+WmX{{VmTe?#y(_k0)U_AWSncH+U&cP}LCI#aIBq`fog>U_SNbGqjT z{^-YVO{5ANp1Dic-Fs5*Jxg^d_kpx$&ryO zXIFZLpOqi@(!m|NY{AQhlk--td-#-xUpl+uX-zs?lb+V+1zcVG^8ODyA9W^=Us|ml zO4_S_U09p;>|L5%dUeIqlXUhZJw0hp19DckE+yW(^YBjc$obW(3$G9Vy09wk<=;KL z^xESerCN`#?(0i7y_oczOgc{@x7O})#`CSuJdKRML+G#Z7*OynOt`tX2o}Fhw2wjZ zi4L)V!5^DgND^_;dhEff*o*2OVvMhxFZz4VSit%#P4ExwVY#MlB_iMmb9X* zv=WxK3nhxPVm-n>fH}M03pl5@joM`wN)THr>=Mh+vT{9*fiSz^L0Uyt36_iX#*387 zoRo60jPtt}SzK9c=0SR{)4YWw7sE%JH#&0+4+a9q& zME@8qD}h5}%o>Giv4M@ZU8q3|dGx7DEJLsMQf_=Uu~IgzY%TKa`z?9ukf&MPkM^`^ zlm#7(O6&j5&xlXf|MMcW=w7j&cL5-!AOQPB-R2Pi;WWtwwC8$l|q< zjY#XlobveW7WRtmsIeV2HsSlh8HaDb)Q)BZ&tQF?j`8sulfafdm_9fqCnA8Uv2k9R z5P6atA|D^)gE9WX$jD{h+%UU}i%ANcmd52i_D0|02`M1OBVr$aag3jdCt4+u4@Uu^ zqYS%^^Fclo3FEzkp8|T}`vgGdzEQxSE){EzcA=y}j!5RGB8w8X=IvBeq&ccJj><=a zw*z4aS!#tM1{jnH$l+O$ZzJ(%ww*GMpwB1}jwYgfED^mSO4RuqGm0ql(<1d|Ivk0x z(TVdn7?M^;C#T6~bv&yMeTwl@QV_~QNW{2?Wj-30mBE`@en_6U|a%P*{dW_@2nNI%7QP%%)?9B0X)guUEtVkhJ@pl8fw)|VY`Vh3G} zAl0ZJFqgLTEe~RyTcYq);FK~wR)@HtWniEkX&E{6(u`J>{w<4^TaQ!f^|!266O3RF zpf?xr7~eR2^X7ar`owY?t)Ah0wxQWZRTUKlUgcRnF7Ybgb?~vc!k_b%XX*kwFN8g) zO4^4YQ~dp4Bq1`LPrA+`ur$M}2$32nqA(C*GAOevKx%O~9#CbK1bj-V(@g!7YSeEM z(|9EyQx4j!q$<1tl}|HOTOtBXcMvjVYMt2{1S`xma}BL$npCkB2+M)#V3_1%01ADM zHV$3ZE{l;d))VO!v_YoD4ymFdB`A~OSXc=JX3Ni-8lN8d9_9ZR2wvwlin#;*mU;V{ zv-O#){K2_*&n4>*q+AD{mhDb9zVzhwlSp#UcUQ{>lkUNE4L@)HdC9Mx?zF4?ty?MA zo~I4X^ZkE+_;dRB`S8=~meuMuWN`x~)$CrcZckOWFZX_U^3lmB&7TymR$rjBbalOv za_Z5kC;gwau2#QHDS$hC=PzEPw(Q<0;95KAZQsT&uJ;(dHKq^t>S>1&^O>_-1w$G) zqC2X3NCUC?9RxXgOXp0o_>h7V%-!Thc6-B;J636;R*`n)=2164NuA@BWnirruh9h#; z)^s#ar>_+}*i#PgBa%wAB0F$%iLX4Br3$8@$-xV(^BBM}DsZNOl)# z>j1h`k){q|Rn`z@#XtL|K|J;FRLXNW={)=mgLno6c3OevR+`{$TO>C&;k@hbD)-1T zgayHR!S)3)Uw`(Z-7^8Ky&Z+`Y)5V{zd1K4r}AzDuXE2lVWos?}0~zFD1lW;kxT?Cv=65>Y?pTN2 zrml0fvTMFL?R3BCzUy9h?oT=Q1D~WD_v!n8Ma_D7-%5F3(%F~v^daNAvnAzhS(^RK zd5E!6#!j0^j>*$ts6R!Wzav8d(tcZp%4P$UGR%o+g!arC3I!O2lYWF~pJVGJl}t(T z5YBl5A{|;t3Cd(+GayalUAl!J7Xt1InTrt!bjJctx zn4~8S^~8+B`M@4JC&v)j z()TE!eSn!sCDNHxELSrn#>grgkf*~+XoBWuDlP{j@*|bS*wAM4R;D7L$~L_OQ~YNe zvnM!D20P?0JHx>=yF6qlU90O(xw;<@toIJ2dIvr&U+EouTHcg&H)-h)+ET8j&%KR6 zIxjwaaed!~6#jcJq${cyUs$j2NY!_&RUBHDS1JyDUfq)Pwrn`K%EMn40A8y58L^!H z&|bs#Ge0IdXF23&r94YP-%sw?psyRt0n@EyS?LXCGA9T*LUY!9%RpOPjXSWjp|$Fr zfGyq7UG>&jIE9Sg>_IJL1K;upR{e}X`rFiG>fD-!gFTtMgHsZPG*I@SXQgPnp?~Y~ z*nN$0K#BY#Ob}?x1KNgwGAYd2b*&$L&6A!p(xv%FI@<<7R+CA7OV#*_-VA>;eWgh+ zr8MhrV>U4EVWn07u8_9Q9ouFF!2uyGlmCby(E?f*d5>jC3$Jzc#Hr*tspN@b9vr6a`&YN!qN-BxcR~LR6{S$qDfC) zoL%ww(%y>u?tAWa?}?Q6#7Dzx-odAy#wEvlB@auMXV-wVnv%|X9A*>MbW_WE z(}`5mi6;ZAO=ptsMgtbAaO8uEW%2#`lxuH#UmI;(^ZjdX{uwsB z-utKSox+CpuChGv7e9XLE`RE-c?R~5At?752$?CRLsc-p(Rd>&G zoAqchY*{6xZw}raTzB`T+`Vh=BN#^Qq>|dX)^hTRxYp92KGH|j59^zA{lQqjY1)P;`JQPeH<{cNz@VQ8h3TRj5esD; z?1V;TZpO&w(YFm|W9V0;7pbV6vE<)nrI>vr1@??}76Z(W3W7R#)u`>D6$t!s<@>$?x8 zb{|~z_WZKfu*5ZPun;DR;yC;9@gYYenzE(Fey<<@=W+ zsq({*N0Lyq+(? zoA3{kTCi$p$hSQCx57EA2?=$$g009{^C)HL1SUb&hCD0NXhZ9cZOBs!Z7|kvRvQe> zgtP=zzblfWXoPB!r0Hfy3=$nkNpT9;u0b$Q8bzRMdbcQo)>MwB$996*=4|x8lhXcu zS~_76z|wI;2EF;x-Io^q%k~dT9+jl_99eN4N!K^7*LS7ryOxy??>xHmMEvCXYW*dc z_Xi<-)?M`}SN-Dba`T7XkGfNheJifM>;kVndhKJ!C)2C-!#EZg+^})(6RIMBYK@1a z4wF3nc=_#$fDVB$55`;?wBV3L7Ukd#R6q`qu;K(29v+PR4H3tV+Mx_iA#WrUc3df>xp)|$*o3DP>`QJ9@ZlvXVW z>s9Tks`gdy0WykSNOfLFdM+lN7u7AyPSo-n7p6c2+m}V^!~((l)<<$FwAk1Khd^y1 z;^Y>l;(QBg&N2&IX2=>a1k;R-olELOxEY(z9!x|1JS_<7RKie5W=x#$ixOOVjoDGp z0<)%LgihmyIL`O!=n_Zt@a)9Fyb#AKn9A}MClgp_vJ%HiHKro0bnzo` z9!qX~95zmEZJ}rfO&>Vm78YrohqG%W&*6JklX6`uXvMPC+17`?MEgeBUe`(IUYg&= z=oTjiDlvi;Nyb`Ml5rp{M%HYd1G8$R67*b!i*XpE1>b1Zg`=Iyw=)ii@nj{Q$zd$k zh|$p-O--Yt9mYVYw0Pn%@@T;LhG9$z!IOkcx)25`yO4)3FNXz@2?v!=C}|rVFrnrj z%eTQSLp(7Sj)=Z4eu#XkX}Z)}z-{pu;IZmpazhLS6I)yvsA<^sV?t08_{+>Imp^^^ zqRfsF^;sd}U38Q@ij(batxot zMh*sYES52_Xab8#HNpoW`5@7BG6Lwc709wiMY8sW<*2#x^Z{qnLH-+*TeVYYrICY=a=eCB0kjB=_4G0ebW6T_OlnE)G7@t6AFr8$|r=<-j z#E(hwD8_&d+vql87!+;XwAIIi(WokFi4LZM8cp6c{B&HpnP+jHNx*9;%-o7pX3`q3 zihrt@C?HZ@r<&}?>N9Qk`as=+NnQZM$2tdw&(We0aHJoPX{Qg=r5IdgXfkf#$a9(| zgE`+2v6!%}chMktQO5i+{+tQ;S_6-Tu+j?_}jW)uasQOh?_S;KLK zIbmn{Tv4Znu#7-c-Bg2P5EM+#sMVm=)FpW$o`?uL$)B8NpyN1A!Jdt|Z{7A65N2a{a5Qtyic7c2#vI*{U)oO_ev8W>sKN`!PetjvUKrB@t7L zv#C_4Jr9K_#B`P*l;Nj85ue^hDkw>C_Xg{+;iI0T>a3npb@a5+9TI98t#)>$f-UAY zNI~D|C?8EklrSL`8Ulzu8NwVe{50!OvZLy~TBu@ike9Y%owVy_0P*G+Fz3ru-nSxOY*n%qr$fw&C(w|Tiby7Nj zK-ZLhMiJXIrA$3;HUWs@Y7;x{)EAt3e$tY)6u%+gpVG?wN1zWKssQTux|dIR`FRIC zsobS+_1`_YII_N{XJt>%)AHK+OVGvFDtsxIZ~5BVk@Km}^J**{0bn14@3`tguGzp? z$<&^nRqr9D#m}EimshWsx2}}8F88l@oL}iU4;xiQ{bKJsFTVZaa@|^aZ+bWX&fwdF z>$`hWyL(o5AHFy6`RGO-LoBd&_gPAB(9ZtM9ly`GH-pjdY1g!&+_@h2~)xFL2FB<5%(-y~^$5 z6gmlHPctW-2QP4wwCcA!h|R;lIxU>W5NogpriUi|U2hrAhOCqO^;q*$pBK7C^Hc5z zoKSGb`V;F6t_9HNv}KyRZTkT?ZSfTb&q2?*oV7dOhT}HbgrV@@Z;a_Z8W(9U${l=S z3P2tpCQBtMR(CUohZVq6+Kk1F9V?r%Zbng=5>L4I0bAr$B`eH1uZ_(Xc9p)@Y8LxFOSRNH81? zIqp`F-Us&zeI4&iGOIoCFM~udID!;kuqQ2fy6V6#bAxlp!hk03c!$Ag6c!PgD}qWZ z3A-DKaUOC6S_;^7A~*$y5E+h}A}k}!`k-ZjwK6U#drgt_Z5qXD0$J8!x&mUfp^xxW z_CR(aFC608NyRAfx#s@Hu3nE)3nA&u;>>rMxg&MLNFb0L-fB|J8o=2DXM?d;1(VIx z2$dhfvS3wWI94esFs!oK>ML3{?cR$LkMoguY&`rS_EB6)E@kzyrsAq0b_Hd2AET4N z7C?8`MY1Ajb{=z$4*AMegvnz!lv*fQq=2BKdN=ux>5W<6Y4er{CueL>np8OIr#OZ` zOU__&2K1?yW+`7W8AC|4heV0C-OOHi&#{#fz@2)DF&}yNF!QVG$fe^|f~pxC)=iFO zKU2(LDwC(PmF9}?GNb<)q0#?|(mV*l&2dAblt0-A-y;JaQK%aAMO9Ra`lDtYjF4RR6Hn3 zdG@{NP_d_3ZwNbD%|}z%{1ajZ60TJP$o!n^%vUyC3mvlYWu|M zz;okQxOwpJy|175np4B*#&TT@H$;8kk%k4QD=`0 zA-kmeTdI)$92vLO@HaGzgrG5tPOeA;>%ZSREFdTS1?t$|0ct$!z#v|jCV=*AeN0D2 zrgK_maJ6)jN^N;ePZDT%w%|WSwHQyB-5`VbiSYOYZYbXpBN~bV=fd#C?z}N`E$!Vv z8gwQ@5(pBB$p_PVwJXW#oF2mb5`m5T0%=R_4|IA*FE|571rcpBC-3rHXN0Y(%oQ6~56ER%zCV|KH zbq1Y9c6Af?!+QvVF-e!z)Zh1p1*H4`XuBF>T!PE-b+ilubP~ucZvjHnnqfL)pNxlN zIU>A>U}hkKpLod;Q$2BYqW-X@nk2YCm$OgtNXye=+8=OGk#0zJSJf4`kW(*5~jkIuf zRixUiSAWajs`oXiXyJaUYsj|X1R^gA5LY;KiZIZm-gTvA1IAa;A%l9PtKb(|5=@%~ z3?+5NU@0BFcT?Iu-+*n|x4P|vm@ zPO7Mj7zI+#RBEU}ba?em7_wExkd!qGKr(%oT^>gp8GamsC6PH;@R(7C#Z*UZS`wKS zT*efGu|Q%|t8OuDVvkgWD4y*`9#-+_gqUwf+-5{uz=WNGMH^LD4J`J|Ws>kX^5sB8<#HFsCq zUHTiDeB9qTU4N#E`>PXm2!B=8aHiLm^ZPv{q?n-U!^@5^Bd@cv$$E5^X!bwj3@SWE zN3nnkZ05C1Uu87d0O*iYU8ZcYP)1sDq+pFhoi&zrBKwZz2=Nj-$b}NOO*_inY{Jo~ zD1@;yLeGHt06MKfGoRX|UG@Cd>Jonag)OrDmlj=Y38{UY&AxM&6@Qx664^T0S}H18 zch;wz^^5)QTzdP`XU=xEkC5qI)xC2tGf7-FQL~_tm9p!Gky*p@Om!Y@o&x{*x_+~O)+*D{F0Aj zoYS4K*zS{OpZP;ien~GB+(gf%#vFQuTj=F!Jz9Je+BC;+QQ@ zzCBB_;&+8EDaJ0PkZ&4$&`0Cfhja$HQ#e>Ow(sN+XE1b;Z`!Bf7962p7Rt(GZ zT%n%F?1|}LL;LO|uQe{~1F=n6U$s}nHgAtLf7=h*!>*xo(mCW|SnB-12zcTWzDmOs z3{mhB1>Z$5B9ZPQk%zcU6GL9qo$7CDy@;=n)u$-^dlZo8bp}5$7L?;L7z8d;9P`j- zS(x6>{G0?zxJCgDzUr&}2YAaAn?BkZ8*D1tFMq*ZI|N^kj4db#xS}2%gAh19?8d+J zK4NBjvUZtox6AoSN*o7<^Xx>o1^ZXh(?ujNrVd^F^xVp!D>&n-Kd^i> z*?ZyBrq#Mj3xzmZBV$5q3g=))SIQ5i_wK*<^5^)?C6=e}(~i};>kEZ+yOv$fZdviP zJZk^EcK6zz&gJpPcUJcdtk#~Je>v^0T6Z_V&vo(6n!6L<-W<9+w0LaI-TbtwWoi0x z$w#+7o?Jckz16BK^XEVJRHrNI@tX(hH65v%j&$wb_1dmfZCARyp8Uw47dgDe8!%xM zYl?#UlOw8kXOGm4*^mxW(1QTK9T5=XA^e=*-u{7er>_l;1TLL^C2(=*?C_<_g99T2 zfr0Piw+{nXFZu^0g4a?j1y?8-r}9qdQaA@uW~2g&o1}o@n>eM~Z@^zrk|MbW_KJ~>|Juc{nA$)UbSSr0Y4EfyQ5d7M9IBI za<7(baa^-J?){3RPc4zX7L+*-Z?xGQhnISnoey8y;PCob*rbn*;vz@I^8O7DACE6Q z8Tq*FFMpWo9o(ev8#P^yKD`KDpCB7PKJMS7kBw1FnWO93uCj$=cVin4ykiu+HH(hB zuWc04Hz!wCz3}6u?6ztiYTs_D=u4TTRgmETRfI3YfcsI-*i#@A+D+A%fceZ zWy`aQn#GF6iMJb;E0!l7H6&|~rYeqYI4CKLKKrT;HG8en#_?s#Vq)pqa{tobNj3J+ z5WlPs1ifxlbU3<~DmOTMEcb5G$HuFcI!EQRBKqY*Ve$CA_>!`G?jerB52uQHHyudZ zICjcWwK%Y}9}NmWYJAfCD+fNcLU_YqX>=Dc@SwMJVfos_ z;ccXA9j%lBA8Lk8dfhnE=qP*a*x>N-r0wJ0PaS`CBGos%N#8fmSe%Xn8;6cNil5xx z;PCP3h3E9a~s3OLun*9)H) zSS&>g&ZP_go5RaH3yX(G9&D^=aS!6D)?xEvoF zWd|*Mx}tW&MxRZs%Y~0?l5NLRO?@opnWK3A@PcDOTs(6xyi~a)Kdehu9Y}dQlCF-F XqjS@NQeP{Uxk5|vR{(hyb&367!=-3j literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/h11/__pycache__/_events.cpython-312.pyc b/venv/lib/python3.12/site-packages/h11/__pycache__/_events.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d11e4ef95c4a5b78449f2d6e71304d5ccd10d5b5 GIT binary patch literal 13207 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 U=7UqE6%Iet#>QnTV8`lz0jYSFjsO4v literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/h11/__pycache__/_headers.cpython-312.pyc b/venv/lib/python3.12/site-packages/h11/__pycache__/_headers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7cc69ca2c82834f8b22b49f02d0f288321c08a57 GIT binary patch literal 7983 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/h11/__pycache__/_readers.cpython-312.pyc b/venv/lib/python3.12/site-packages/h11/__pycache__/_readers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a86b4bd3e4650912124ad9afcf1d01dba64128f GIT binary patch literal 9639 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/h11/__pycache__/_receivebuffer.cpython-312.pyc b/venv/lib/python3.12/site-packages/h11/__pycache__/_receivebuffer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d520493a833d010c1f6c4f55da6d7327a54ac792 GIT binary patch literal 4685 zcmcH+TWk~A_0G$&$4TtOP9Whmc{MQ`NCT{PiGb}wpoBtb7wC4~$7tdiCu95wGm{WU zjvB3~fxyB8$n@3<%rzJzRM09hm|QJFXiJ2MkzV9mza1Q+HK ze3(xNVSynkH_C)vEYV1;zI@HrtnxtjZ>`JNBi6yf9+rHYsKPzCr$Q6cOvtr?48tML z=CA6eWxKDBTY4&~#BJeL5)ixYBQ2uoliK--XjC&ylw7R)K#Vx|I#4JrHh%-iB8Z4j z2(zkBxEt_V)uV~9 zcEK4D)^(+Iz2?H(fa(RR4>-ycR;>k!pHdneF3QQObwFXX!|j6?WLb%@A*UM$(q1 z7>1Iz1zA-r#cq(3+LR^7^`s`7uoW4zo8`nr+|tM68YP=0`U+$7rWTKy__xyQD}7_B zgw_{H#YfV8kuk-x(tVR!aj*XL@$t0nmSsJuTe3Xeyu;+j%k*!;Uz!I1%#xQ)hadhh%ikB*+S;@HbNAu3Z**t* zFWnuRuuYs7nsNQM7O5x%=%@~=PP}Q6WXKc~18n!mFw=E}L|D19ks&w83NsvHj9Q?D zSOY!XfS@8K-Y-c;15ESi8?2*%G1uE6-9x-cV8Keg(n|8Yd zC7*$V6(JkvPc05C4E&#jv;~YBv$~ibS*E)n=)T>8mNveHqN10eK7;c&7sCtTRiBi1 zOH`q@5J^oWt?KUj6F7$MTFqHil3`lO4EyHYpRs41Z19Fmkzv7OuvCON=XK!x#K0WC z!a0uK2Dr_oCM*+8Ep53wBn?A}SbGAtM85#s=6e8U$$EX`gVjakugwe2ocaka~E>l;X>gk+6rQYmp&4h}gFYxN9r@o;q4h}G;Oz*6NvyCtiEW3noLy9ZQq z!19!NtKU_&$o8U^7T~;V8V=5IaLQZ0VQbEC<#RVksAl>*6@!#5iN=cRhG|Jy-+CoI zD(RM_n^Gi|fbwR6k@aL%lhT5@fnl?WnP{sUup0~aBe%_Wp6g9Z!Q-R7fuQgw0} zxecn>8`hVONH%Y z_Fx8hNZz5odbo=fv4Xqbe>y1^g-U;y4Kvf7+Z)frWR65qxrrMSDVjg zz3blQT>CTc!Ixb<%fZL3E3LnhvxCoT+rh(%F5+v=eK?oOa%E?`vFdBfyW93U8+J4p zbeji3Rc{Vcm2CxQq0SKi<5me%l`R{WA)`=)feWtPJ)ooG;U?j&H}N}fvMSD8&4sJ~ z4^Z8^|9eM>Fta^Y#YI(@h0Z}o_4~>asr>d(1NX1U2I-55VeQ^ER9EF}4fd8a|5wpp zrlLOxA1;RVXV-vsyi^xv_zXWte!LHE6EZ>!n~_~Zb(eWf=6+K~P;1VT`z<`l@Tv!; zfSF1YVW(JX3zsV5YzkM%rwl_7BCpC|P0H{Pu-@q(i8Sv5Cd8@QVqCIP zG&Y?G9*HZ-+od>GlB(o1&5{Bnh;K;J7~zut;>mJS{#jIcnP*H&=HR-Nn0?-<2ro6Xi3$)t6O6s zmy-a5gknX;jDBSAM0SA+Y4dc#P;nICgnnRW=u@8Qwzq{nWf_BOOdq@xN`y$DxDLz> z%#Y09$)3yIdgg7%a(iv=TCVMR?ZLG`Q+8)Xci@iTAd9+p=w($Mz8-(B!`FY}MZ z74ga7i&NJNr>?L1znc}-y#DOjmq(8+E58psx%r3iZ^OSk^2h!{|KRG;ihF7Cj4|T-T~!$_nejmVXe|b0|9q{jgKczVvmh z1^4F~7cLhC*n#rrZ<_yTE}gr$)c%X1=f2+Mz%yTO-rZZEFz@SKuWgubU+BnQS@#9A zwR>|D#um*@mjGELo;T(c^k|j64Zd=NbPx-DhK^2Ra18l`q`A9fiu;7zWkS63ZMD$` z43|oMfGrMTuD@s)DPvDx`;KmI`{YWEOzNo#GoFU>Fsa0K6+VvcOlYR_MK*nUr}h7b zqYptI^Ed$T3Rdv6Kn@S?$0j+aESz0BRS0zFYrFsT(g$j1JS|@Z_7^#rUz=FZK5Yn+ zFFX?WOGe`0NtpPCLqYt;Nh%3VmTggn4o01T4@B9nmE}7VO5E8o8iCiqhQm0505hTi zX@wZ5QUkx>py(wn;}9+-1hi*D%NX4VP9Q)d86P0H0APhsQ_>)zU(6Kr!H)TB0QaCo zb3#v1WQE{T*Rrsj&iA}uXg^&fKzN$k#Hr|Jgd@4mO#)NVg?PZhf!>yT7xA{D%Pj>BJ* z^tfhwuHCwNbLi^O@I?a~YDWW?5yL!9wes41#<9ma<2ukV=b5NJuqnfQO*~%{@o%K3 uKzi1_t$FdlY)x^PA;H7*C#XZjmfIiquJo=3-pdpJKV30~0jEGn|KPtF<^iJs literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/h11/__pycache__/_state.cpython-312.pyc b/venv/lib/python3.12/site-packages/h11/__pycache__/_state.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dda06417cb1f31c94ad1dcce5f16a69803ccf76c GIT binary patch literal 8449 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" + r":" + r"{OWS}" + r"(?P{field_value})" + r"{OWS}".format(**globals()) +) + +# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#request.line +# +# request-line = method SP request-target SP HTTP-version CRLF +# method = token +# HTTP-version = HTTP-name "/" DIGIT "." DIGIT +# HTTP-name = %x48.54.54.50 ; "HTTP", case-sensitive +# +# request-target is complicated (see RFC 7230 sec 5.3) -- could be path, full +# URL, host+port (for connect), or even "*", but in any case we are guaranteed +# that it contists of the visible printing characters. +method = token +request_target = r"{vchar}+".format(**globals()) +http_version = r"HTTP/(?P[0-9]\.[0-9])" +request_line = ( + r"(?P{method})" + r" " + r"(?P{request_target})" + r" " + r"{http_version}".format(**globals()) +) + +# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#status.line +# +# status-line = HTTP-version SP status-code SP reason-phrase CRLF +# status-code = 3DIGIT +# reason-phrase = *( HTAB / SP / VCHAR / obs-text ) +status_code = r"[0-9]{3}" +reason_phrase = r"([ \t]|{vchar_or_obs_text})*".format(**globals()) +status_line = ( + r"{http_version}" + r" " + r"(?P{status_code})" + # However, there are apparently a few too many servers out there that just + # leave out the reason phrase: + # https://github.com/scrapy/scrapy/issues/345#issuecomment-281756036 + # https://github.com/seanmonstar/httparse/issues/29 + # so make it optional. ?: is a non-capturing group. + r"(?: (?P{reason_phrase}))?".format(**globals()) +) + +HEXDIG = r"[0-9A-Fa-f]" +# Actually +# +# chunk-size = 1*HEXDIG +# +# but we impose an upper-limit to avoid ridiculosity. len(str(2**64)) == 20 +chunk_size = r"({HEXDIG}){{1,20}}".format(**globals()) +# Actually +# +# chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) +# +# but we aren't parsing the things so we don't really care. +chunk_ext = r";.*" +chunk_header = ( + r"(?P{chunk_size})" + r"(?P{chunk_ext})?" + r"{OWS}\r\n".format( + **globals() + ) # Even though the specification does not allow for extra whitespaces, + # we are lenient with trailing whitespaces because some servers on the wild use it. +) diff --git a/venv/lib/python3.12/site-packages/h11/_connection.py b/venv/lib/python3.12/site-packages/h11/_connection.py new file mode 100644 index 0000000..e37d82a --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11/_connection.py @@ -0,0 +1,659 @@ +# This contains the main Connection class. Everything in h11 revolves around +# this. +from typing import ( + Any, + Callable, + cast, + Dict, + List, + Optional, + overload, + Tuple, + Type, + Union, +) + +from ._events import ( + ConnectionClosed, + Data, + EndOfMessage, + Event, + InformationalResponse, + Request, + Response, +) +from ._headers import get_comma_header, has_expect_100_continue, set_comma_header +from ._readers import READERS, ReadersType +from ._receivebuffer import ReceiveBuffer +from ._state import ( + _SWITCH_CONNECT, + _SWITCH_UPGRADE, + CLIENT, + ConnectionState, + DONE, + ERROR, + MIGHT_SWITCH_PROTOCOL, + SEND_BODY, + SERVER, + SWITCHED_PROTOCOL, +) +from ._util import ( # Import the internal things we need + LocalProtocolError, + RemoteProtocolError, + Sentinel, +) +from ._writers import WRITERS, WritersType + +# Everything in __all__ gets re-exported as part of the h11 public API. +__all__ = ["Connection", "NEED_DATA", "PAUSED"] + + +class NEED_DATA(Sentinel, metaclass=Sentinel): + pass + + +class PAUSED(Sentinel, metaclass=Sentinel): + pass + + +# If we ever have this much buffered without it making a complete parseable +# event, we error out. The only time we really buffer is when reading the +# request/response line + headers together, so this is effectively the limit on +# the size of that. +# +# Some precedents for defaults: +# - node.js: 80 * 1024 +# - tomcat: 8 * 1024 +# - IIS: 16 * 1024 +# - Apache: <8 KiB per line> +DEFAULT_MAX_INCOMPLETE_EVENT_SIZE = 16 * 1024 + + +# RFC 7230's rules for connection lifecycles: +# - If either side says they want to close the connection, then the connection +# must close. +# - HTTP/1.1 defaults to keep-alive unless someone says Connection: close +# - HTTP/1.0 defaults to close unless both sides say Connection: keep-alive +# (and even this is a mess -- e.g. if you're implementing a proxy then +# sending Connection: keep-alive is forbidden). +# +# We simplify life by simply not supporting keep-alive with HTTP/1.0 peers. So +# our rule is: +# - If someone says Connection: close, we will close +# - If someone uses HTTP/1.0, we will close. +def _keep_alive(event: Union[Request, Response]) -> bool: + connection = get_comma_header(event.headers, b"connection") + if b"close" in connection: + return False + if getattr(event, "http_version", b"1.1") < b"1.1": + return False + return True + + +def _body_framing( + request_method: bytes, event: Union[Request, Response] +) -> Tuple[str, Union[Tuple[()], Tuple[int]]]: + # Called when we enter SEND_BODY to figure out framing information for + # this body. + # + # These are the only two events that can trigger a SEND_BODY state: + assert type(event) in (Request, Response) + # Returns one of: + # + # ("content-length", count) + # ("chunked", ()) + # ("http/1.0", ()) + # + # which are (lookup key, *args) for constructing body reader/writer + # objects. + # + # Reference: https://tools.ietf.org/html/rfc7230#section-3.3.3 + # + # Step 1: some responses always have an empty body, regardless of what the + # headers say. + if type(event) is Response: + if ( + event.status_code in (204, 304) + or request_method == b"HEAD" + or (request_method == b"CONNECT" and 200 <= event.status_code < 300) + ): + return ("content-length", (0,)) + # Section 3.3.3 also lists another case -- responses with status_code + # < 200. For us these are InformationalResponses, not Responses, so + # they can't get into this function in the first place. + assert event.status_code >= 200 + + # Step 2: check for Transfer-Encoding (T-E beats C-L): + transfer_encodings = get_comma_header(event.headers, b"transfer-encoding") + if transfer_encodings: + assert transfer_encodings == [b"chunked"] + return ("chunked", ()) + + # Step 3: check for Content-Length + content_lengths = get_comma_header(event.headers, b"content-length") + if content_lengths: + return ("content-length", (int(content_lengths[0]),)) + + # Step 4: no applicable headers; fallback/default depends on type + if type(event) is Request: + return ("content-length", (0,)) + else: + return ("http/1.0", ()) + + +################################################################ +# +# The main Connection class +# +################################################################ + + +class Connection: + """An object encapsulating the state of an HTTP connection. + + Args: + our_role: If you're implementing a client, pass :data:`h11.CLIENT`. If + you're implementing a server, pass :data:`h11.SERVER`. + + max_incomplete_event_size (int): + The maximum number of bytes we're willing to buffer of an + incomplete event. In practice this mostly sets a limit on the + maximum size of the request/response line + headers. If this is + exceeded, then :meth:`next_event` will raise + :exc:`RemoteProtocolError`. + + """ + + def __init__( + self, + our_role: Type[Sentinel], + max_incomplete_event_size: int = DEFAULT_MAX_INCOMPLETE_EVENT_SIZE, + ) -> None: + self._max_incomplete_event_size = max_incomplete_event_size + # State and role tracking + if our_role not in (CLIENT, SERVER): + raise ValueError(f"expected CLIENT or SERVER, not {our_role!r}") + self.our_role = our_role + self.their_role: Type[Sentinel] + if our_role is CLIENT: + self.their_role = SERVER + else: + self.their_role = CLIENT + self._cstate = ConnectionState() + + # Callables for converting data->events or vice-versa given the + # current state + self._writer = self._get_io_object(self.our_role, None, WRITERS) + self._reader = self._get_io_object(self.their_role, None, READERS) + + # Holds any unprocessed received data + self._receive_buffer = ReceiveBuffer() + # If this is true, then it indicates that the incoming connection was + # closed *after* the end of whatever's in self._receive_buffer: + self._receive_buffer_closed = False + + # Extra bits of state that don't fit into the state machine. + # + # These two are only used to interpret framing headers for figuring + # out how to read/write response bodies. their_http_version is also + # made available as a convenient public API. + self.their_http_version: Optional[bytes] = None + self._request_method: Optional[bytes] = None + # This is pure flow-control and doesn't at all affect the set of legal + # transitions, so no need to bother ConnectionState with it: + self.client_is_waiting_for_100_continue = False + + @property + def states(self) -> Dict[Type[Sentinel], Type[Sentinel]]: + """A dictionary like:: + + {CLIENT: , SERVER: } + + See :ref:`state-machine` for details. + + """ + return dict(self._cstate.states) + + @property + def our_state(self) -> Type[Sentinel]: + """The current state of whichever role we are playing. See + :ref:`state-machine` for details. + """ + return self._cstate.states[self.our_role] + + @property + def their_state(self) -> Type[Sentinel]: + """The current state of whichever role we are NOT playing. See + :ref:`state-machine` for details. + """ + return self._cstate.states[self.their_role] + + @property + def they_are_waiting_for_100_continue(self) -> bool: + return self.their_role is CLIENT and self.client_is_waiting_for_100_continue + + def start_next_cycle(self) -> None: + """Attempt to reset our connection state for a new request/response + cycle. + + If both client and server are in :data:`DONE` state, then resets them + both to :data:`IDLE` state in preparation for a new request/response + cycle on this same connection. Otherwise, raises a + :exc:`LocalProtocolError`. + + See :ref:`keepalive-and-pipelining`. + + """ + old_states = dict(self._cstate.states) + self._cstate.start_next_cycle() + self._request_method = None + # self.their_http_version gets left alone, since it presumably lasts + # beyond a single request/response cycle + assert not self.client_is_waiting_for_100_continue + self._respond_to_state_changes(old_states) + + def _process_error(self, role: Type[Sentinel]) -> None: + old_states = dict(self._cstate.states) + self._cstate.process_error(role) + self._respond_to_state_changes(old_states) + + def _server_switch_event(self, event: Event) -> Optional[Type[Sentinel]]: + if type(event) is InformationalResponse and event.status_code == 101: + return _SWITCH_UPGRADE + if type(event) is Response: + if ( + _SWITCH_CONNECT in self._cstate.pending_switch_proposals + and 200 <= event.status_code < 300 + ): + return _SWITCH_CONNECT + return None + + # All events go through here + def _process_event(self, role: Type[Sentinel], event: Event) -> None: + # First, pass the event through the state machine to make sure it + # succeeds. + old_states = dict(self._cstate.states) + if role is CLIENT and type(event) is Request: + if event.method == b"CONNECT": + self._cstate.process_client_switch_proposal(_SWITCH_CONNECT) + if get_comma_header(event.headers, b"upgrade"): + self._cstate.process_client_switch_proposal(_SWITCH_UPGRADE) + server_switch_event = None + if role is SERVER: + server_switch_event = self._server_switch_event(event) + self._cstate.process_event(role, type(event), server_switch_event) + + # Then perform the updates triggered by it. + + if type(event) is Request: + self._request_method = event.method + + if role is self.their_role and type(event) in ( + Request, + Response, + InformationalResponse, + ): + event = cast(Union[Request, Response, InformationalResponse], event) + self.their_http_version = event.http_version + + # Keep alive handling + # + # RFC 7230 doesn't really say what one should do if Connection: close + # shows up on a 1xx InformationalResponse. I think the idea is that + # this is not supposed to happen. In any case, if it does happen, we + # ignore it. + if type(event) in (Request, Response) and not _keep_alive( + cast(Union[Request, Response], event) + ): + self._cstate.process_keep_alive_disabled() + + # 100-continue + if type(event) is Request and has_expect_100_continue(event): + self.client_is_waiting_for_100_continue = True + if type(event) in (InformationalResponse, Response): + self.client_is_waiting_for_100_continue = False + if role is CLIENT and type(event) in (Data, EndOfMessage): + self.client_is_waiting_for_100_continue = False + + self._respond_to_state_changes(old_states, event) + + def _get_io_object( + self, + role: Type[Sentinel], + event: Optional[Event], + io_dict: Union[ReadersType, WritersType], + ) -> Optional[Callable[..., Any]]: + # event may be None; it's only used when entering SEND_BODY + state = self._cstate.states[role] + if state is SEND_BODY: + # Special case: the io_dict has a dict of reader/writer factories + # that depend on the request/response framing. + framing_type, args = _body_framing( + cast(bytes, self._request_method), cast(Union[Request, Response], event) + ) + return io_dict[SEND_BODY][framing_type](*args) # type: ignore[index] + else: + # General case: the io_dict just has the appropriate reader/writer + # for this state + return io_dict.get((role, state)) # type: ignore[return-value] + + # This must be called after any action that might have caused + # self._cstate.states to change. + def _respond_to_state_changes( + self, + old_states: Dict[Type[Sentinel], Type[Sentinel]], + event: Optional[Event] = None, + ) -> None: + # Update reader/writer + if self.our_state != old_states[self.our_role]: + self._writer = self._get_io_object(self.our_role, event, WRITERS) + if self.their_state != old_states[self.their_role]: + self._reader = self._get_io_object(self.their_role, event, READERS) + + @property + def trailing_data(self) -> Tuple[bytes, bool]: + """Data that has been received, but not yet processed, represented as + a tuple with two elements, where the first is a byte-string containing + the unprocessed data itself, and the second is a bool that is True if + the receive connection was closed. + + See :ref:`switching-protocols` for discussion of why you'd want this. + """ + return (bytes(self._receive_buffer), self._receive_buffer_closed) + + def receive_data(self, data: bytes) -> None: + """Add data to our internal receive buffer. + + This does not actually do any processing on the data, just stores + it. To trigger processing, you have to call :meth:`next_event`. + + Args: + data (:term:`bytes-like object`): + The new data that was just received. + + Special case: If *data* is an empty byte-string like ``b""``, + then this indicates that the remote side has closed the + connection (end of file). Normally this is convenient, because + standard Python APIs like :meth:`file.read` or + :meth:`socket.recv` use ``b""`` to indicate end-of-file, while + other failures to read are indicated using other mechanisms + like raising :exc:`TimeoutError`. When using such an API you + can just blindly pass through whatever you get from ``read`` + to :meth:`receive_data`, and everything will work. + + But, if you have an API where reading an empty string is a + valid non-EOF condition, then you need to be aware of this and + make sure to check for such strings and avoid passing them to + :meth:`receive_data`. + + Returns: + Nothing, but after calling this you should call :meth:`next_event` + to parse the newly received data. + + Raises: + RuntimeError: + Raised if you pass an empty *data*, indicating EOF, and then + pass a non-empty *data*, indicating more data that somehow + arrived after the EOF. + + (Calling ``receive_data(b"")`` multiple times is fine, + and equivalent to calling it once.) + + """ + if data: + if self._receive_buffer_closed: + raise RuntimeError("received close, then received more data?") + self._receive_buffer += data + else: + self._receive_buffer_closed = True + + def _extract_next_receive_event( + self, + ) -> Union[Event, Type[NEED_DATA], Type[PAUSED]]: + state = self.their_state + # We don't pause immediately when they enter DONE, because even in + # DONE state we can still process a ConnectionClosed() event. But + # if we have data in our buffer, then we definitely aren't getting + # a ConnectionClosed() immediately and we need to pause. + if state is DONE and self._receive_buffer: + return PAUSED + if state is MIGHT_SWITCH_PROTOCOL or state is SWITCHED_PROTOCOL: + return PAUSED + assert self._reader is not None + event = self._reader(self._receive_buffer) + if event is None: + if not self._receive_buffer and self._receive_buffer_closed: + # In some unusual cases (basically just HTTP/1.0 bodies), EOF + # triggers an actual protocol event; in that case, we want to + # return that event, and then the state will change and we'll + # get called again to generate the actual ConnectionClosed(). + if hasattr(self._reader, "read_eof"): + event = self._reader.read_eof() + else: + event = ConnectionClosed() + if event is None: + event = NEED_DATA + return event # type: ignore[no-any-return] + + def next_event(self) -> Union[Event, Type[NEED_DATA], Type[PAUSED]]: + """Parse the next event out of our receive buffer, update our internal + state, and return it. + + This is a mutating operation -- think of it like calling :func:`next` + on an iterator. + + Returns: + : One of three things: + + 1) An event object -- see :ref:`events`. + + 2) The special constant :data:`NEED_DATA`, which indicates that + you need to read more data from your socket and pass it to + :meth:`receive_data` before this method will be able to return + any more events. + + 3) The special constant :data:`PAUSED`, which indicates that we + are not in a state where we can process incoming data (usually + because the peer has finished their part of the current + request/response cycle, and you have not yet called + :meth:`start_next_cycle`). See :ref:`flow-control` for details. + + Raises: + RemoteProtocolError: + The peer has misbehaved. You should close the connection + (possibly after sending some kind of 4xx response). + + Once this method returns :class:`ConnectionClosed` once, then all + subsequent calls will also return :class:`ConnectionClosed`. + + If this method raises any exception besides :exc:`RemoteProtocolError` + then that's a bug -- if it happens please file a bug report! + + If this method raises any exception then it also sets + :attr:`Connection.their_state` to :data:`ERROR` -- see + :ref:`error-handling` for discussion. + + """ + + if self.their_state is ERROR: + raise RemoteProtocolError("Can't receive data when peer state is ERROR") + try: + event = self._extract_next_receive_event() + if event not in [NEED_DATA, PAUSED]: + self._process_event(self.their_role, cast(Event, event)) + if event is NEED_DATA: + if len(self._receive_buffer) > self._max_incomplete_event_size: + # 431 is "Request header fields too large" which is pretty + # much the only situation where we can get here + raise RemoteProtocolError( + "Receive buffer too long", error_status_hint=431 + ) + if self._receive_buffer_closed: + # We're still trying to complete some event, but that's + # never going to happen because no more data is coming + raise RemoteProtocolError("peer unexpectedly closed connection") + return event + except BaseException as exc: + self._process_error(self.their_role) + if isinstance(exc, LocalProtocolError): + exc._reraise_as_remote_protocol_error() + else: + raise + + @overload + def send(self, event: ConnectionClosed) -> None: + ... + + @overload + def send( + self, event: Union[Request, InformationalResponse, Response, Data, EndOfMessage] + ) -> bytes: + ... + + @overload + def send(self, event: Event) -> Optional[bytes]: + ... + + def send(self, event: Event) -> Optional[bytes]: + """Convert a high-level event into bytes that can be sent to the peer, + while updating our internal state machine. + + Args: + event: The :ref:`event ` to send. + + Returns: + If ``type(event) is ConnectionClosed``, then returns + ``None``. Otherwise, returns a :term:`bytes-like object`. + + Raises: + LocalProtocolError: + Sending this event at this time would violate our + understanding of the HTTP/1.1 protocol. + + If this method raises any exception then it also sets + :attr:`Connection.our_state` to :data:`ERROR` -- see + :ref:`error-handling` for discussion. + + """ + data_list = self.send_with_data_passthrough(event) + if data_list is None: + return None + else: + return b"".join(data_list) + + def send_with_data_passthrough(self, event: Event) -> Optional[List[bytes]]: + """Identical to :meth:`send`, except that in situations where + :meth:`send` returns a single :term:`bytes-like object`, this instead + returns a list of them -- and when sending a :class:`Data` event, this + list is guaranteed to contain the exact object you passed in as + :attr:`Data.data`. See :ref:`sendfile` for discussion. + + """ + if self.our_state is ERROR: + raise LocalProtocolError("Can't send data when our state is ERROR") + try: + if type(event) is Response: + event = self._clean_up_response_headers_for_sending(event) + # We want to call _process_event before calling the writer, + # because if someone tries to do something invalid then this will + # give a sensible error message, while our writers all just assume + # they will only receive valid events. But, _process_event might + # change self._writer. So we have to do a little dance: + writer = self._writer + self._process_event(self.our_role, event) + if type(event) is ConnectionClosed: + return None + else: + # In any situation where writer is None, process_event should + # have raised ProtocolError + assert writer is not None + data_list: List[bytes] = [] + writer(event, data_list.append) + return data_list + except: + self._process_error(self.our_role) + raise + + def send_failed(self) -> None: + """Notify the state machine that we failed to send the data it gave + us. + + This causes :attr:`Connection.our_state` to immediately become + :data:`ERROR` -- see :ref:`error-handling` for discussion. + + """ + self._process_error(self.our_role) + + # When sending a Response, we take responsibility for a few things: + # + # - Sometimes you MUST set Connection: close. We take care of those + # times. (You can also set it yourself if you want, and if you do then + # we'll respect that and close the connection at the right time. But you + # don't have to worry about that unless you want to.) + # + # - The user has to set Content-Length if they want it. Otherwise, for + # responses that have bodies (e.g. not HEAD), then we will automatically + # select the right mechanism for streaming a body of unknown length, + # which depends on depending on the peer's HTTP version. + # + # This function's *only* responsibility is making sure headers are set up + # right -- everything downstream just looks at the headers. There are no + # side channels. + def _clean_up_response_headers_for_sending(self, response: Response) -> Response: + assert type(response) is Response + + headers = response.headers + need_close = False + + # HEAD requests need some special handling: they always act like they + # have Content-Length: 0, and that's how _body_framing treats + # them. But their headers are supposed to match what we would send if + # the request was a GET. (Technically there is one deviation allowed: + # we're allowed to leave out the framing headers -- see + # https://tools.ietf.org/html/rfc7231#section-4.3.2 . But it's just as + # easy to get them right.) + method_for_choosing_headers = cast(bytes, self._request_method) + if method_for_choosing_headers == b"HEAD": + method_for_choosing_headers = b"GET" + framing_type, _ = _body_framing(method_for_choosing_headers, response) + if framing_type in ("chunked", "http/1.0"): + # This response has a body of unknown length. + # If our peer is HTTP/1.1, we use Transfer-Encoding: chunked + # If our peer is HTTP/1.0, we use no framing headers, and close the + # connection afterwards. + # + # Make sure to clear Content-Length (in principle user could have + # set both and then we ignored Content-Length b/c + # Transfer-Encoding overwrote it -- this would be naughty of them, + # but the HTTP spec says that if our peer does this then we have + # to fix it instead of erroring out, so we'll accord the user the + # same respect). + headers = set_comma_header(headers, b"content-length", []) + if self.their_http_version is None or self.their_http_version < b"1.1": + # Either we never got a valid request and are sending back an + # error (their_http_version is None), so we assume the worst; + # or else we did get a valid HTTP/1.0 request, so we know that + # they don't understand chunked encoding. + headers = set_comma_header(headers, b"transfer-encoding", []) + # This is actually redundant ATM, since currently we + # unconditionally disable keep-alive when talking to HTTP/1.0 + # peers. But let's be defensive just in case we add + # Connection: keep-alive support later: + if self._request_method != b"HEAD": + need_close = True + else: + headers = set_comma_header(headers, b"transfer-encoding", [b"chunked"]) + + if not self._cstate.keep_alive or need_close: + # Make sure Connection: close is set + connection = set(get_comma_header(headers, b"connection")) + connection.discard(b"keep-alive") + connection.add(b"close") + headers = set_comma_header(headers, b"connection", sorted(connection)) + + return Response( + headers=headers, + status_code=response.status_code, + http_version=response.http_version, + reason=response.reason, + ) diff --git a/venv/lib/python3.12/site-packages/h11/_events.py b/venv/lib/python3.12/site-packages/h11/_events.py new file mode 100644 index 0000000..ca1c3ad --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11/_events.py @@ -0,0 +1,369 @@ +# High level events that make up HTTP/1.1 conversations. Loosely inspired by +# the corresponding events in hyper-h2: +# +# http://python-hyper.org/h2/en/stable/api.html#events +# +# Don't subclass these. Stuff will break. + +import re +from abc import ABC +from dataclasses import dataclass +from typing import List, Tuple, Union + +from ._abnf import method, request_target +from ._headers import Headers, normalize_and_validate +from ._util import bytesify, LocalProtocolError, validate + +# Everything in __all__ gets re-exported as part of the h11 public API. +__all__ = [ + "Event", + "Request", + "InformationalResponse", + "Response", + "Data", + "EndOfMessage", + "ConnectionClosed", +] + +method_re = re.compile(method.encode("ascii")) +request_target_re = re.compile(request_target.encode("ascii")) + + +class Event(ABC): + """ + Base class for h11 events. + """ + + __slots__ = () + + +@dataclass(init=False, frozen=True) +class Request(Event): + """The beginning of an HTTP request. + + Fields: + + .. attribute:: method + + An HTTP method, e.g. ``b"GET"`` or ``b"POST"``. Always a byte + string. :term:`Bytes-like objects ` and native + strings containing only ascii characters will be automatically + converted to byte strings. + + .. attribute:: target + + The target of an HTTP request, e.g. ``b"/index.html"``, or one of the + more exotic formats described in `RFC 7320, section 5.3 + `_. Always a byte + string. :term:`Bytes-like objects ` and native + strings containing only ascii characters will be automatically + converted to byte strings. + + .. attribute:: headers + + Request headers, represented as a list of (name, value) pairs. See + :ref:`the header normalization rules ` for details. + + .. attribute:: http_version + + The HTTP protocol version, represented as a byte string like + ``b"1.1"``. See :ref:`the HTTP version normalization rules + ` for details. + + """ + + __slots__ = ("method", "headers", "target", "http_version") + + method: bytes + headers: Headers + target: bytes + http_version: bytes + + def __init__( + self, + *, + method: Union[bytes, str], + headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]], + target: Union[bytes, str], + http_version: Union[bytes, str] = b"1.1", + _parsed: bool = False, + ) -> None: + super().__init__() + if isinstance(headers, Headers): + object.__setattr__(self, "headers", headers) + else: + object.__setattr__( + self, "headers", normalize_and_validate(headers, _parsed=_parsed) + ) + if not _parsed: + object.__setattr__(self, "method", bytesify(method)) + object.__setattr__(self, "target", bytesify(target)) + object.__setattr__(self, "http_version", bytesify(http_version)) + else: + object.__setattr__(self, "method", method) + object.__setattr__(self, "target", target) + object.__setattr__(self, "http_version", http_version) + + # "A server MUST respond with a 400 (Bad Request) status code to any + # HTTP/1.1 request message that lacks a Host header field and to any + # request message that contains more than one Host header field or a + # Host header field with an invalid field-value." + # -- https://tools.ietf.org/html/rfc7230#section-5.4 + host_count = 0 + for name, value in self.headers: + if name == b"host": + host_count += 1 + if self.http_version == b"1.1" and host_count == 0: + raise LocalProtocolError("Missing mandatory Host: header") + if host_count > 1: + raise LocalProtocolError("Found multiple Host: headers") + + validate(method_re, self.method, "Illegal method characters") + validate(request_target_re, self.target, "Illegal target characters") + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(init=False, frozen=True) +class _ResponseBase(Event): + __slots__ = ("headers", "http_version", "reason", "status_code") + + headers: Headers + http_version: bytes + reason: bytes + status_code: int + + def __init__( + self, + *, + headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]], + status_code: int, + http_version: Union[bytes, str] = b"1.1", + reason: Union[bytes, str] = b"", + _parsed: bool = False, + ) -> None: + super().__init__() + if isinstance(headers, Headers): + object.__setattr__(self, "headers", headers) + else: + object.__setattr__( + self, "headers", normalize_and_validate(headers, _parsed=_parsed) + ) + if not _parsed: + object.__setattr__(self, "reason", bytesify(reason)) + object.__setattr__(self, "http_version", bytesify(http_version)) + if not isinstance(status_code, int): + raise LocalProtocolError("status code must be integer") + # Because IntEnum objects are instances of int, but aren't + # duck-compatible (sigh), see gh-72. + object.__setattr__(self, "status_code", int(status_code)) + else: + object.__setattr__(self, "reason", reason) + object.__setattr__(self, "http_version", http_version) + object.__setattr__(self, "status_code", status_code) + + self.__post_init__() + + def __post_init__(self) -> None: + pass + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(init=False, frozen=True) +class InformationalResponse(_ResponseBase): + """An HTTP informational response. + + Fields: + + .. attribute:: status_code + + The status code of this response, as an integer. For an + :class:`InformationalResponse`, this is always in the range [100, + 200). + + .. attribute:: headers + + Request headers, represented as a list of (name, value) pairs. See + :ref:`the header normalization rules ` for + details. + + .. attribute:: http_version + + The HTTP protocol version, represented as a byte string like + ``b"1.1"``. See :ref:`the HTTP version normalization rules + ` for details. + + .. attribute:: reason + + The reason phrase of this response, as a byte string. For example: + ``b"OK"``, or ``b"Not Found"``. + + """ + + def __post_init__(self) -> None: + if not (100 <= self.status_code < 200): + raise LocalProtocolError( + "InformationalResponse status_code should be in range " + "[100, 200), not {}".format(self.status_code) + ) + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(init=False, frozen=True) +class Response(_ResponseBase): + """The beginning of an HTTP response. + + Fields: + + .. attribute:: status_code + + The status code of this response, as an integer. For an + :class:`Response`, this is always in the range [200, + 1000). + + .. attribute:: headers + + Request headers, represented as a list of (name, value) pairs. See + :ref:`the header normalization rules ` for details. + + .. attribute:: http_version + + The HTTP protocol version, represented as a byte string like + ``b"1.1"``. See :ref:`the HTTP version normalization rules + ` for details. + + .. attribute:: reason + + The reason phrase of this response, as a byte string. For example: + ``b"OK"``, or ``b"Not Found"``. + + """ + + def __post_init__(self) -> None: + if not (200 <= self.status_code < 1000): + raise LocalProtocolError( + "Response status_code should be in range [200, 1000), not {}".format( + self.status_code + ) + ) + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(init=False, frozen=True) +class Data(Event): + """Part of an HTTP message body. + + Fields: + + .. attribute:: data + + A :term:`bytes-like object` containing part of a message body. Or, if + using the ``combine=False`` argument to :meth:`Connection.send`, then + any object that your socket writing code knows what to do with, and for + which calling :func:`len` returns the number of bytes that will be + written -- see :ref:`sendfile` for details. + + .. attribute:: chunk_start + + A marker that indicates whether this data object is from the start of a + chunked transfer encoding chunk. This field is ignored when when a Data + event is provided to :meth:`Connection.send`: it is only valid on + events emitted from :meth:`Connection.next_event`. You probably + shouldn't use this attribute at all; see + :ref:`chunk-delimiters-are-bad` for details. + + .. attribute:: chunk_end + + A marker that indicates whether this data object is the last for a + given chunked transfer encoding chunk. This field is ignored when when + a Data event is provided to :meth:`Connection.send`: it is only valid + on events emitted from :meth:`Connection.next_event`. You probably + shouldn't use this attribute at all; see + :ref:`chunk-delimiters-are-bad` for details. + + """ + + __slots__ = ("data", "chunk_start", "chunk_end") + + data: bytes + chunk_start: bool + chunk_end: bool + + def __init__( + self, data: bytes, chunk_start: bool = False, chunk_end: bool = False + ) -> None: + object.__setattr__(self, "data", data) + object.__setattr__(self, "chunk_start", chunk_start) + object.__setattr__(self, "chunk_end", chunk_end) + + # This is an unhashable type. + __hash__ = None # type: ignore + + +# XX FIXME: "A recipient MUST ignore (or consider as an error) any fields that +# are forbidden to be sent in a trailer, since processing them as if they were +# present in the header section might bypass external security filters." +# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#chunked.trailer.part +# Unfortunately, the list of forbidden fields is long and vague :-/ +@dataclass(init=False, frozen=True) +class EndOfMessage(Event): + """The end of an HTTP message. + + Fields: + + .. attribute:: headers + + Default value: ``[]`` + + Any trailing headers attached to this message, represented as a list of + (name, value) pairs. See :ref:`the header normalization rules + ` for details. + + Must be empty unless ``Transfer-Encoding: chunked`` is in use. + + """ + + __slots__ = ("headers",) + + headers: Headers + + def __init__( + self, + *, + headers: Union[ + Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]], None + ] = None, + _parsed: bool = False, + ) -> None: + super().__init__() + if headers is None: + headers = Headers([]) + elif not isinstance(headers, Headers): + headers = normalize_and_validate(headers, _parsed=_parsed) + + object.__setattr__(self, "headers", headers) + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(frozen=True) +class ConnectionClosed(Event): + """This event indicates that the sender has closed their outgoing + connection. + + Note that this does not necessarily mean that they can't *receive* further + data, because TCP connections are composed to two one-way channels which + can be closed independently. See :ref:`closing` for details. + + No fields. + """ + + pass diff --git a/venv/lib/python3.12/site-packages/h11/_headers.py b/venv/lib/python3.12/site-packages/h11/_headers.py new file mode 100644 index 0000000..31da3e2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11/_headers.py @@ -0,0 +1,282 @@ +import re +from typing import AnyStr, cast, List, overload, Sequence, Tuple, TYPE_CHECKING, Union + +from ._abnf import field_name, field_value +from ._util import bytesify, LocalProtocolError, validate + +if TYPE_CHECKING: + from ._events import Request + +try: + from typing import Literal +except ImportError: + from typing_extensions import Literal # type: ignore + +CONTENT_LENGTH_MAX_DIGITS = 20 # allow up to 1 billion TB - 1 + + +# Facts +# ----- +# +# Headers are: +# keys: case-insensitive ascii +# values: mixture of ascii and raw bytes +# +# "Historically, HTTP has allowed field content with text in the ISO-8859-1 +# charset [ISO-8859-1], supporting other charsets only through use of +# [RFC2047] encoding. In practice, most HTTP header field values use only a +# subset of the US-ASCII charset [USASCII]. Newly defined header fields SHOULD +# limit their field values to US-ASCII octets. A recipient SHOULD treat other +# octets in field content (obs-text) as opaque data." +# And it deprecates all non-ascii values +# +# Leading/trailing whitespace in header names is forbidden +# +# Values get leading/trailing whitespace stripped +# +# Content-Disposition actually needs to contain unicode semantically; to +# accomplish this it has a terrifically weird way of encoding the filename +# itself as ascii (and even this still has lots of cross-browser +# incompatibilities) +# +# Order is important: +# "a proxy MUST NOT change the order of these field values when forwarding a +# message" +# (and there are several headers where the order indicates a preference) +# +# Multiple occurences of the same header: +# "A sender MUST NOT generate multiple header fields with the same field name +# in a message unless either the entire field value for that header field is +# defined as a comma-separated list [or the header is Set-Cookie which gets a +# special exception]" - RFC 7230. (cookies are in RFC 6265) +# +# So every header aside from Set-Cookie can be merged by b", ".join if it +# occurs repeatedly. But, of course, they can't necessarily be split by +# .split(b","), because quoting. +# +# Given all this mess (case insensitive, duplicates allowed, order is +# important, ...), there doesn't appear to be any standard way to handle +# headers in Python -- they're almost like dicts, but... actually just +# aren't. For now we punt and just use a super simple representation: headers +# are a list of pairs +# +# [(name1, value1), (name2, value2), ...] +# +# where all entries are bytestrings, names are lowercase and have no +# leading/trailing whitespace, and values are bytestrings with no +# leading/trailing whitespace. Searching and updating are done via naive O(n) +# methods. +# +# Maybe a dict-of-lists would be better? + +_content_length_re = re.compile(rb"[0-9]+") +_field_name_re = re.compile(field_name.encode("ascii")) +_field_value_re = re.compile(field_value.encode("ascii")) + + +class Headers(Sequence[Tuple[bytes, bytes]]): + """ + A list-like interface that allows iterating over headers as byte-pairs + of (lowercased-name, value). + + Internally we actually store the representation as three-tuples, + including both the raw original casing, in order to preserve casing + over-the-wire, and the lowercased name, for case-insensitive comparisions. + + r = Request( + method="GET", + target="/", + headers=[("Host", "example.org"), ("Connection", "keep-alive")], + http_version="1.1", + ) + assert r.headers == [ + (b"host", b"example.org"), + (b"connection", b"keep-alive") + ] + assert r.headers.raw_items() == [ + (b"Host", b"example.org"), + (b"Connection", b"keep-alive") + ] + """ + + __slots__ = "_full_items" + + def __init__(self, full_items: List[Tuple[bytes, bytes, bytes]]) -> None: + self._full_items = full_items + + def __bool__(self) -> bool: + return bool(self._full_items) + + def __eq__(self, other: object) -> bool: + return list(self) == list(other) # type: ignore + + def __len__(self) -> int: + return len(self._full_items) + + def __repr__(self) -> str: + return "" % repr(list(self)) + + def __getitem__(self, idx: int) -> Tuple[bytes, bytes]: # type: ignore[override] + _, name, value = self._full_items[idx] + return (name, value) + + def raw_items(self) -> List[Tuple[bytes, bytes]]: + return [(raw_name, value) for raw_name, _, value in self._full_items] + + +HeaderTypes = Union[ + List[Tuple[bytes, bytes]], + List[Tuple[bytes, str]], + List[Tuple[str, bytes]], + List[Tuple[str, str]], +] + + +@overload +def normalize_and_validate(headers: Headers, _parsed: Literal[True]) -> Headers: + ... + + +@overload +def normalize_and_validate(headers: HeaderTypes, _parsed: Literal[False]) -> Headers: + ... + + +@overload +def normalize_and_validate( + headers: Union[Headers, HeaderTypes], _parsed: bool = False +) -> Headers: + ... + + +def normalize_and_validate( + headers: Union[Headers, HeaderTypes], _parsed: bool = False +) -> Headers: + new_headers = [] + seen_content_length = None + saw_transfer_encoding = False + for name, value in headers: + # For headers coming out of the parser, we can safely skip some steps, + # because it always returns bytes and has already run these regexes + # over the data: + if not _parsed: + name = bytesify(name) + value = bytesify(value) + validate(_field_name_re, name, "Illegal header name {!r}", name) + validate(_field_value_re, value, "Illegal header value {!r}", value) + assert isinstance(name, bytes) + assert isinstance(value, bytes) + + raw_name = name + name = name.lower() + if name == b"content-length": + lengths = {length.strip() for length in value.split(b",")} + if len(lengths) != 1: + raise LocalProtocolError("conflicting Content-Length headers") + value = lengths.pop() + validate(_content_length_re, value, "bad Content-Length") + if len(value) > CONTENT_LENGTH_MAX_DIGITS: + raise LocalProtocolError("bad Content-Length") + if seen_content_length is None: + seen_content_length = value + new_headers.append((raw_name, name, value)) + elif seen_content_length != value: + raise LocalProtocolError("conflicting Content-Length headers") + elif name == b"transfer-encoding": + # "A server that receives a request message with a transfer coding + # it does not understand SHOULD respond with 501 (Not + # Implemented)." + # https://tools.ietf.org/html/rfc7230#section-3.3.1 + if saw_transfer_encoding: + raise LocalProtocolError( + "multiple Transfer-Encoding headers", error_status_hint=501 + ) + # "All transfer-coding names are case-insensitive" + # -- https://tools.ietf.org/html/rfc7230#section-4 + value = value.lower() + if value != b"chunked": + raise LocalProtocolError( + "Only Transfer-Encoding: chunked is supported", + error_status_hint=501, + ) + saw_transfer_encoding = True + new_headers.append((raw_name, name, value)) + else: + new_headers.append((raw_name, name, value)) + return Headers(new_headers) + + +def get_comma_header(headers: Headers, name: bytes) -> List[bytes]: + # Should only be used for headers whose value is a list of + # comma-separated, case-insensitive values. + # + # The header name `name` is expected to be lower-case bytes. + # + # Connection: meets these criteria (including cast insensitivity). + # + # Content-Length: technically is just a single value (1*DIGIT), but the + # standard makes reference to implementations that do multiple values, and + # using this doesn't hurt. Ditto, case insensitivity doesn't things either + # way. + # + # Transfer-Encoding: is more complex (allows for quoted strings), so + # splitting on , is actually wrong. For example, this is legal: + # + # Transfer-Encoding: foo; options="1,2", chunked + # + # and should be parsed as + # + # foo; options="1,2" + # chunked + # + # but this naive function will parse it as + # + # foo; options="1 + # 2" + # chunked + # + # However, this is okay because the only thing we are going to do with + # any Transfer-Encoding is reject ones that aren't just "chunked", so + # both of these will be treated the same anyway. + # + # Expect: the only legal value is the literal string + # "100-continue". Splitting on commas is harmless. Case insensitive. + # + out: List[bytes] = [] + for _, found_name, found_raw_value in headers._full_items: + if found_name == name: + found_raw_value = found_raw_value.lower() + for found_split_value in found_raw_value.split(b","): + found_split_value = found_split_value.strip() + if found_split_value: + out.append(found_split_value) + return out + + +def set_comma_header(headers: Headers, name: bytes, new_values: List[bytes]) -> Headers: + # The header name `name` is expected to be lower-case bytes. + # + # Note that when we store the header we use title casing for the header + # names, in order to match the conventional HTTP header style. + # + # Simply calling `.title()` is a blunt approach, but it's correct + # here given the cases where we're using `set_comma_header`... + # + # Connection, Content-Length, Transfer-Encoding. + new_headers: List[Tuple[bytes, bytes]] = [] + for found_raw_name, found_name, found_raw_value in headers._full_items: + if found_name != name: + new_headers.append((found_raw_name, found_raw_value)) + for new_value in new_values: + new_headers.append((name.title(), new_value)) + return normalize_and_validate(new_headers) + + +def has_expect_100_continue(request: "Request") -> bool: + # https://tools.ietf.org/html/rfc7231#section-5.1.1 + # "A server that receives a 100-continue expectation in an HTTP/1.0 request + # MUST ignore that expectation." + if request.http_version < b"1.1": + return False + expect = get_comma_header(request.headers, b"expect") + return b"100-continue" in expect diff --git a/venv/lib/python3.12/site-packages/h11/_readers.py b/venv/lib/python3.12/site-packages/h11/_readers.py new file mode 100644 index 0000000..576804c --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11/_readers.py @@ -0,0 +1,250 @@ +# Code to read HTTP data +# +# Strategy: each reader is a callable which takes a ReceiveBuffer object, and +# either: +# 1) consumes some of it and returns an Event +# 2) raises a LocalProtocolError (for consistency -- e.g. we call validate() +# and it might raise a LocalProtocolError, so simpler just to always use +# this) +# 3) returns None, meaning "I need more data" +# +# If they have a .read_eof attribute, then this will be called if an EOF is +# received -- but this is optional. Either way, the actual ConnectionClosed +# event will be generated afterwards. +# +# READERS is a dict describing how to pick a reader. It maps states to either: +# - a reader +# - or, for body readers, a dict of per-framing reader factories + +import re +from typing import Any, Callable, Dict, Iterable, NoReturn, Optional, Tuple, Type, Union + +from ._abnf import chunk_header, header_field, request_line, status_line +from ._events import Data, EndOfMessage, InformationalResponse, Request, Response +from ._receivebuffer import ReceiveBuffer +from ._state import ( + CLIENT, + CLOSED, + DONE, + IDLE, + MUST_CLOSE, + SEND_BODY, + SEND_RESPONSE, + SERVER, +) +from ._util import LocalProtocolError, RemoteProtocolError, Sentinel, validate + +__all__ = ["READERS"] + +header_field_re = re.compile(header_field.encode("ascii")) +obs_fold_re = re.compile(rb"[ \t]+") + + +def _obsolete_line_fold(lines: Iterable[bytes]) -> Iterable[bytes]: + it = iter(lines) + last: Optional[bytes] = None + for line in it: + match = obs_fold_re.match(line) + if match: + if last is None: + raise LocalProtocolError("continuation line at start of headers") + if not isinstance(last, bytearray): + # Cast to a mutable type, avoiding copy on append to ensure O(n) time + last = bytearray(last) + last += b" " + last += line[match.end() :] + else: + if last is not None: + yield last + last = line + if last is not None: + yield last + + +def _decode_header_lines( + lines: Iterable[bytes], +) -> Iterable[Tuple[bytes, bytes]]: + for line in _obsolete_line_fold(lines): + matches = validate(header_field_re, line, "illegal header line: {!r}", line) + yield (matches["field_name"], matches["field_value"]) + + +request_line_re = re.compile(request_line.encode("ascii")) + + +def maybe_read_from_IDLE_client(buf: ReceiveBuffer) -> Optional[Request]: + lines = buf.maybe_extract_lines() + if lines is None: + if buf.is_next_line_obviously_invalid_request_line(): + raise LocalProtocolError("illegal request line") + return None + if not lines: + raise LocalProtocolError("no request line received") + matches = validate( + request_line_re, lines[0], "illegal request line: {!r}", lines[0] + ) + return Request( + headers=list(_decode_header_lines(lines[1:])), _parsed=True, **matches + ) + + +status_line_re = re.compile(status_line.encode("ascii")) + + +def maybe_read_from_SEND_RESPONSE_server( + buf: ReceiveBuffer, +) -> Union[InformationalResponse, Response, None]: + lines = buf.maybe_extract_lines() + if lines is None: + if buf.is_next_line_obviously_invalid_request_line(): + raise LocalProtocolError("illegal request line") + return None + if not lines: + raise LocalProtocolError("no response line received") + matches = validate(status_line_re, lines[0], "illegal status line: {!r}", lines[0]) + http_version = ( + b"1.1" if matches["http_version"] is None else matches["http_version"] + ) + reason = b"" if matches["reason"] is None else matches["reason"] + status_code = int(matches["status_code"]) + class_: Union[Type[InformationalResponse], Type[Response]] = ( + InformationalResponse if status_code < 200 else Response + ) + return class_( + headers=list(_decode_header_lines(lines[1:])), + _parsed=True, + status_code=status_code, + reason=reason, + http_version=http_version, + ) + + +class ContentLengthReader: + def __init__(self, length: int) -> None: + self._length = length + self._remaining = length + + def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]: + if self._remaining == 0: + return EndOfMessage() + data = buf.maybe_extract_at_most(self._remaining) + if data is None: + return None + self._remaining -= len(data) + return Data(data=data) + + def read_eof(self) -> NoReturn: + raise RemoteProtocolError( + "peer closed connection without sending complete message body " + "(received {} bytes, expected {})".format( + self._length - self._remaining, self._length + ) + ) + + +chunk_header_re = re.compile(chunk_header.encode("ascii")) + + +class ChunkedReader: + def __init__(self) -> None: + self._bytes_in_chunk = 0 + # After reading a chunk, we have to throw away the trailing \r\n. + # This tracks the bytes that we need to match and throw away. + self._bytes_to_discard = b"" + self._reading_trailer = False + + def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]: + if self._reading_trailer: + lines = buf.maybe_extract_lines() + if lines is None: + return None + return EndOfMessage(headers=list(_decode_header_lines(lines))) + if self._bytes_to_discard: + data = buf.maybe_extract_at_most(len(self._bytes_to_discard)) + if data is None: + return None + if data != self._bytes_to_discard[: len(data)]: + raise LocalProtocolError( + f"malformed chunk footer: {data!r} (expected {self._bytes_to_discard!r})" + ) + self._bytes_to_discard = self._bytes_to_discard[len(data) :] + if self._bytes_to_discard: + return None + # else, fall through and read some more + assert self._bytes_to_discard == b"" + if self._bytes_in_chunk == 0: + # We need to refill our chunk count + chunk_header = buf.maybe_extract_next_line() + if chunk_header is None: + return None + matches = validate( + chunk_header_re, + chunk_header, + "illegal chunk header: {!r}", + chunk_header, + ) + # XX FIXME: we discard chunk extensions. Does anyone care? + self._bytes_in_chunk = int(matches["chunk_size"], base=16) + if self._bytes_in_chunk == 0: + self._reading_trailer = True + return self(buf) + chunk_start = True + else: + chunk_start = False + assert self._bytes_in_chunk > 0 + data = buf.maybe_extract_at_most(self._bytes_in_chunk) + if data is None: + return None + self._bytes_in_chunk -= len(data) + if self._bytes_in_chunk == 0: + self._bytes_to_discard = b"\r\n" + chunk_end = True + else: + chunk_end = False + return Data(data=data, chunk_start=chunk_start, chunk_end=chunk_end) + + def read_eof(self) -> NoReturn: + raise RemoteProtocolError( + "peer closed connection without sending complete message body " + "(incomplete chunked read)" + ) + + +class Http10Reader: + def __call__(self, buf: ReceiveBuffer) -> Optional[Data]: + data = buf.maybe_extract_at_most(999999999) + if data is None: + return None + return Data(data=data) + + def read_eof(self) -> EndOfMessage: + return EndOfMessage() + + +def expect_nothing(buf: ReceiveBuffer) -> None: + if buf: + raise LocalProtocolError("Got data when expecting EOF") + return None + + +ReadersType = Dict[ + Union[Type[Sentinel], Tuple[Type[Sentinel], Type[Sentinel]]], + Union[Callable[..., Any], Dict[str, Callable[..., Any]]], +] + +READERS: ReadersType = { + (CLIENT, IDLE): maybe_read_from_IDLE_client, + (SERVER, IDLE): maybe_read_from_SEND_RESPONSE_server, + (SERVER, SEND_RESPONSE): maybe_read_from_SEND_RESPONSE_server, + (CLIENT, DONE): expect_nothing, + (CLIENT, MUST_CLOSE): expect_nothing, + (CLIENT, CLOSED): expect_nothing, + (SERVER, DONE): expect_nothing, + (SERVER, MUST_CLOSE): expect_nothing, + (SERVER, CLOSED): expect_nothing, + SEND_BODY: { + "chunked": ChunkedReader, + "content-length": ContentLengthReader, + "http/1.0": Http10Reader, + }, +} diff --git a/venv/lib/python3.12/site-packages/h11/_receivebuffer.py b/venv/lib/python3.12/site-packages/h11/_receivebuffer.py new file mode 100644 index 0000000..e5c4e08 --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11/_receivebuffer.py @@ -0,0 +1,153 @@ +import re +import sys +from typing import List, Optional, Union + +__all__ = ["ReceiveBuffer"] + + +# Operations we want to support: +# - find next \r\n or \r\n\r\n (\n or \n\n are also acceptable), +# or wait until there is one +# - read at-most-N bytes +# Goals: +# - on average, do this fast +# - worst case, do this in O(n) where n is the number of bytes processed +# Plan: +# - store bytearray, offset, how far we've searched for a separator token +# - use the how-far-we've-searched data to avoid rescanning +# - while doing a stream of uninterrupted processing, advance offset instead +# of constantly copying +# WARNING: +# - I haven't benchmarked or profiled any of this yet. +# +# Note that starting in Python 3.4, deleting the initial n bytes from a +# bytearray is amortized O(n), thanks to some excellent work by Antoine +# Martin: +# +# https://bugs.python.org/issue19087 +# +# This means that if we only supported 3.4+, we could get rid of the code here +# involving self._start and self.compress, because it's doing exactly the same +# thing that bytearray now does internally. +# +# BUT unfortunately, we still support 2.7, and reading short segments out of a +# long buffer MUST be O(bytes read) to avoid DoS issues, so we can't actually +# delete this code. Yet: +# +# https://pythonclock.org/ +# +# (Two things to double-check first though: make sure PyPy also has the +# optimization, and benchmark to make sure it's a win, since we do have a +# slightly clever thing where we delay calling compress() until we've +# processed a whole event, which could in theory be slightly more efficient +# than the internal bytearray support.) +blank_line_regex = re.compile(b"\n\r?\n", re.MULTILINE) + + +class ReceiveBuffer: + def __init__(self) -> None: + self._data = bytearray() + self._next_line_search = 0 + self._multiple_lines_search = 0 + + def __iadd__(self, byteslike: Union[bytes, bytearray]) -> "ReceiveBuffer": + self._data += byteslike + return self + + def __bool__(self) -> bool: + return bool(len(self)) + + def __len__(self) -> int: + return len(self._data) + + # for @property unprocessed_data + def __bytes__(self) -> bytes: + return bytes(self._data) + + def _extract(self, count: int) -> bytearray: + # extracting an initial slice of the data buffer and return it + out = self._data[:count] + del self._data[:count] + + self._next_line_search = 0 + self._multiple_lines_search = 0 + + return out + + def maybe_extract_at_most(self, count: int) -> Optional[bytearray]: + """ + Extract a fixed number of bytes from the buffer. + """ + out = self._data[:count] + if not out: + return None + + return self._extract(count) + + def maybe_extract_next_line(self) -> Optional[bytearray]: + """ + Extract the first line, if it is completed in the buffer. + """ + # Only search in buffer space that we've not already looked at. + search_start_index = max(0, self._next_line_search - 1) + partial_idx = self._data.find(b"\r\n", search_start_index) + + if partial_idx == -1: + self._next_line_search = len(self._data) + return None + + # + 2 is to compensate len(b"\r\n") + idx = partial_idx + 2 + + return self._extract(idx) + + def maybe_extract_lines(self) -> Optional[List[bytearray]]: + """ + Extract everything up to the first blank line, and return a list of lines. + """ + # Handle the case where we have an immediate empty line. + if self._data[:1] == b"\n": + self._extract(1) + return [] + + if self._data[:2] == b"\r\n": + self._extract(2) + return [] + + # Only search in buffer space that we've not already looked at. + match = blank_line_regex.search(self._data, self._multiple_lines_search) + if match is None: + self._multiple_lines_search = max(0, len(self._data) - 2) + return None + + # Truncate the buffer and return it. + idx = match.span(0)[-1] + out = self._extract(idx) + lines = out.split(b"\n") + + for line in lines: + if line.endswith(b"\r"): + del line[-1] + + assert lines[-2] == lines[-1] == b"" + + del lines[-2:] + + return lines + + # In theory we should wait until `\r\n` before starting to validate + # incoming data. However it's interesting to detect (very) invalid data + # early given they might not even contain `\r\n` at all (hence only + # timeout will get rid of them). + # This is not a 100% effective detection but more of a cheap sanity check + # allowing for early abort in some useful cases. + # This is especially interesting when peer is messing up with HTTPS and + # sent us a TLS stream where we were expecting plain HTTP given all + # versions of TLS so far start handshake with a 0x16 message type code. + def is_next_line_obviously_invalid_request_line(self) -> bool: + try: + # HTTP header line must not contain non-printable characters + # and should not start with a space + return self._data[0] < 0x21 + except IndexError: + return False diff --git a/venv/lib/python3.12/site-packages/h11/_state.py b/venv/lib/python3.12/site-packages/h11/_state.py new file mode 100644 index 0000000..3ad444b --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11/_state.py @@ -0,0 +1,365 @@ +################################################################ +# The core state machine +################################################################ +# +# Rule 1: everything that affects the state machine and state transitions must +# live here in this file. As much as possible goes into the table-based +# representation, but for the bits that don't quite fit, the actual code and +# state must nonetheless live here. +# +# Rule 2: this file does not know about what role we're playing; it only knows +# about HTTP request/response cycles in the abstract. This ensures that we +# don't cheat and apply different rules to local and remote parties. +# +# +# Theory of operation +# =================== +# +# Possibly the simplest way to think about this is that we actually have 5 +# different state machines here. Yes, 5. These are: +# +# 1) The client state, with its complicated automaton (see the docs) +# 2) The server state, with its complicated automaton (see the docs) +# 3) The keep-alive state, with possible states {True, False} +# 4) The SWITCH_CONNECT state, with possible states {False, True} +# 5) The SWITCH_UPGRADE state, with possible states {False, True} +# +# For (3)-(5), the first state listed is the initial state. +# +# (1)-(3) are stored explicitly in member variables. The last +# two are stored implicitly in the pending_switch_proposals set as: +# (state of 4) == (_SWITCH_CONNECT in pending_switch_proposals) +# (state of 5) == (_SWITCH_UPGRADE in pending_switch_proposals) +# +# And each of these machines has two different kinds of transitions: +# +# a) Event-triggered +# b) State-triggered +# +# Event triggered is the obvious thing that you'd think it is: some event +# happens, and if it's the right event at the right time then a transition +# happens. But there are somewhat complicated rules for which machines can +# "see" which events. (As a rule of thumb, if a machine "sees" an event, this +# means two things: the event can affect the machine, and if the machine is +# not in a state where it expects that event then it's an error.) These rules +# are: +# +# 1) The client machine sees all h11.events objects emitted by the client. +# +# 2) The server machine sees all h11.events objects emitted by the server. +# +# It also sees the client's Request event. +# +# And sometimes, server events are annotated with a _SWITCH_* event. For +# example, we can have a (Response, _SWITCH_CONNECT) event, which is +# different from a regular Response event. +# +# 3) The keep-alive machine sees the process_keep_alive_disabled() event +# (which is derived from Request/Response events), and this event +# transitions it from True -> False, or from False -> False. There's no way +# to transition back. +# +# 4&5) The _SWITCH_* machines transition from False->True when we get a +# Request that proposes the relevant type of switch (via +# process_client_switch_proposals), and they go from True->False when we +# get a Response that has no _SWITCH_* annotation. +# +# So that's event-triggered transitions. +# +# State-triggered transitions are less standard. What they do here is couple +# the machines together. The way this works is, when certain *joint* +# configurations of states are achieved, then we automatically transition to a +# new *joint* state. So, for example, if we're ever in a joint state with +# +# client: DONE +# keep-alive: False +# +# then the client state immediately transitions to: +# +# client: MUST_CLOSE +# +# This is fundamentally different from an event-based transition, because it +# doesn't matter how we arrived at the {client: DONE, keep-alive: False} state +# -- maybe the client transitioned SEND_BODY -> DONE, or keep-alive +# transitioned True -> False. Either way, once this precondition is satisfied, +# this transition is immediately triggered. +# +# What if two conflicting state-based transitions get enabled at the same +# time? In practice there's only one case where this arises (client DONE -> +# MIGHT_SWITCH_PROTOCOL versus DONE -> MUST_CLOSE), and we resolve it by +# explicitly prioritizing the DONE -> MIGHT_SWITCH_PROTOCOL transition. +# +# Implementation +# -------------- +# +# The event-triggered transitions for the server and client machines are all +# stored explicitly in a table. Ditto for the state-triggered transitions that +# involve just the server and client state. +# +# The transitions for the other machines, and the state-triggered transitions +# that involve the other machines, are written out as explicit Python code. +# +# It'd be nice if there were some cleaner way to do all this. This isn't +# *too* terrible, but I feel like it could probably be better. +# +# WARNING +# ------- +# +# The script that generates the state machine diagrams for the docs knows how +# to read out the EVENT_TRIGGERED_TRANSITIONS and STATE_TRIGGERED_TRANSITIONS +# tables. But it can't automatically read the transitions that are written +# directly in Python code. So if you touch those, you need to also update the +# script to keep it in sync! +from typing import cast, Dict, Optional, Set, Tuple, Type, Union + +from ._events import * +from ._util import LocalProtocolError, Sentinel + +# Everything in __all__ gets re-exported as part of the h11 public API. +__all__ = [ + "CLIENT", + "SERVER", + "IDLE", + "SEND_RESPONSE", + "SEND_BODY", + "DONE", + "MUST_CLOSE", + "CLOSED", + "MIGHT_SWITCH_PROTOCOL", + "SWITCHED_PROTOCOL", + "ERROR", +] + + +class CLIENT(Sentinel, metaclass=Sentinel): + pass + + +class SERVER(Sentinel, metaclass=Sentinel): + pass + + +# States +class IDLE(Sentinel, metaclass=Sentinel): + pass + + +class SEND_RESPONSE(Sentinel, metaclass=Sentinel): + pass + + +class SEND_BODY(Sentinel, metaclass=Sentinel): + pass + + +class DONE(Sentinel, metaclass=Sentinel): + pass + + +class MUST_CLOSE(Sentinel, metaclass=Sentinel): + pass + + +class CLOSED(Sentinel, metaclass=Sentinel): + pass + + +class ERROR(Sentinel, metaclass=Sentinel): + pass + + +# Switch types +class MIGHT_SWITCH_PROTOCOL(Sentinel, metaclass=Sentinel): + pass + + +class SWITCHED_PROTOCOL(Sentinel, metaclass=Sentinel): + pass + + +class _SWITCH_UPGRADE(Sentinel, metaclass=Sentinel): + pass + + +class _SWITCH_CONNECT(Sentinel, metaclass=Sentinel): + pass + + +EventTransitionType = Dict[ + Type[Sentinel], + Dict[ + Type[Sentinel], + Dict[Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]], Type[Sentinel]], + ], +] + +EVENT_TRIGGERED_TRANSITIONS: EventTransitionType = { + CLIENT: { + IDLE: {Request: SEND_BODY, ConnectionClosed: CLOSED}, + SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE}, + DONE: {ConnectionClosed: CLOSED}, + MUST_CLOSE: {ConnectionClosed: CLOSED}, + CLOSED: {ConnectionClosed: CLOSED}, + MIGHT_SWITCH_PROTOCOL: {}, + SWITCHED_PROTOCOL: {}, + ERROR: {}, + }, + SERVER: { + IDLE: { + ConnectionClosed: CLOSED, + Response: SEND_BODY, + # Special case: server sees client Request events, in this form + (Request, CLIENT): SEND_RESPONSE, + }, + SEND_RESPONSE: { + InformationalResponse: SEND_RESPONSE, + Response: SEND_BODY, + (InformationalResponse, _SWITCH_UPGRADE): SWITCHED_PROTOCOL, + (Response, _SWITCH_CONNECT): SWITCHED_PROTOCOL, + }, + SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE}, + DONE: {ConnectionClosed: CLOSED}, + MUST_CLOSE: {ConnectionClosed: CLOSED}, + CLOSED: {ConnectionClosed: CLOSED}, + SWITCHED_PROTOCOL: {}, + ERROR: {}, + }, +} + +StateTransitionType = Dict[ + Tuple[Type[Sentinel], Type[Sentinel]], Dict[Type[Sentinel], Type[Sentinel]] +] + +# NB: there are also some special-case state-triggered transitions hard-coded +# into _fire_state_triggered_transitions below. +STATE_TRIGGERED_TRANSITIONS: StateTransitionType = { + # (Client state, Server state) -> new states + # Protocol negotiation + (MIGHT_SWITCH_PROTOCOL, SWITCHED_PROTOCOL): {CLIENT: SWITCHED_PROTOCOL}, + # Socket shutdown + (CLOSED, DONE): {SERVER: MUST_CLOSE}, + (CLOSED, IDLE): {SERVER: MUST_CLOSE}, + (ERROR, DONE): {SERVER: MUST_CLOSE}, + (DONE, CLOSED): {CLIENT: MUST_CLOSE}, + (IDLE, CLOSED): {CLIENT: MUST_CLOSE}, + (DONE, ERROR): {CLIENT: MUST_CLOSE}, +} + + +class ConnectionState: + def __init__(self) -> None: + # Extra bits of state that don't quite fit into the state model. + + # If this is False then it enables the automatic DONE -> MUST_CLOSE + # transition. Don't set this directly; call .keep_alive_disabled() + self.keep_alive = True + + # This is a subset of {UPGRADE, CONNECT}, containing the proposals + # made by the client for switching protocols. + self.pending_switch_proposals: Set[Type[Sentinel]] = set() + + self.states: Dict[Type[Sentinel], Type[Sentinel]] = {CLIENT: IDLE, SERVER: IDLE} + + def process_error(self, role: Type[Sentinel]) -> None: + self.states[role] = ERROR + self._fire_state_triggered_transitions() + + def process_keep_alive_disabled(self) -> None: + self.keep_alive = False + self._fire_state_triggered_transitions() + + def process_client_switch_proposal(self, switch_event: Type[Sentinel]) -> None: + self.pending_switch_proposals.add(switch_event) + self._fire_state_triggered_transitions() + + def process_event( + self, + role: Type[Sentinel], + event_type: Type[Event], + server_switch_event: Optional[Type[Sentinel]] = None, + ) -> None: + _event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]] = event_type + if server_switch_event is not None: + assert role is SERVER + if server_switch_event not in self.pending_switch_proposals: + raise LocalProtocolError( + "Received server _SWITCH_UPGRADE event without a pending proposal" + ) + _event_type = (event_type, server_switch_event) + if server_switch_event is None and _event_type is Response: + self.pending_switch_proposals = set() + self._fire_event_triggered_transitions(role, _event_type) + # Special case: the server state does get to see Request + # events. + if _event_type is Request: + assert role is CLIENT + self._fire_event_triggered_transitions(SERVER, (Request, CLIENT)) + self._fire_state_triggered_transitions() + + def _fire_event_triggered_transitions( + self, + role: Type[Sentinel], + event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]], + ) -> None: + state = self.states[role] + try: + new_state = EVENT_TRIGGERED_TRANSITIONS[role][state][event_type] + except KeyError: + event_type = cast(Type[Event], event_type) + raise LocalProtocolError( + "can't handle event type {} when role={} and state={}".format( + event_type.__name__, role, self.states[role] + ) + ) from None + self.states[role] = new_state + + def _fire_state_triggered_transitions(self) -> None: + # We apply these rules repeatedly until converging on a fixed point + while True: + start_states = dict(self.states) + + # It could happen that both these special-case transitions are + # enabled at the same time: + # + # DONE -> MIGHT_SWITCH_PROTOCOL + # DONE -> MUST_CLOSE + # + # For example, this will always be true of a HTTP/1.0 client + # requesting CONNECT. If this happens, the protocol switch takes + # priority. From there the client will either go to + # SWITCHED_PROTOCOL, in which case it's none of our business when + # they close the connection, or else the server will deny the + # request, in which case the client will go back to DONE and then + # from there to MUST_CLOSE. + if self.pending_switch_proposals: + if self.states[CLIENT] is DONE: + self.states[CLIENT] = MIGHT_SWITCH_PROTOCOL + + if not self.pending_switch_proposals: + if self.states[CLIENT] is MIGHT_SWITCH_PROTOCOL: + self.states[CLIENT] = DONE + + if not self.keep_alive: + for role in (CLIENT, SERVER): + if self.states[role] is DONE: + self.states[role] = MUST_CLOSE + + # Tabular state-triggered transitions + joint_state = (self.states[CLIENT], self.states[SERVER]) + changes = STATE_TRIGGERED_TRANSITIONS.get(joint_state, {}) + self.states.update(changes) + + if self.states == start_states: + # Fixed point reached + return + + def start_next_cycle(self) -> None: + if self.states != {CLIENT: DONE, SERVER: DONE}: + raise LocalProtocolError( + f"not in a reusable state. self.states={self.states}" + ) + # Can't reach DONE/DONE with any of these active, but still, let's be + # sure. + assert self.keep_alive + assert not self.pending_switch_proposals + self.states = {CLIENT: IDLE, SERVER: IDLE} diff --git a/venv/lib/python3.12/site-packages/h11/_util.py b/venv/lib/python3.12/site-packages/h11/_util.py new file mode 100644 index 0000000..6718445 --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11/_util.py @@ -0,0 +1,135 @@ +from typing import Any, Dict, NoReturn, Pattern, Tuple, Type, TypeVar, Union + +__all__ = [ + "ProtocolError", + "LocalProtocolError", + "RemoteProtocolError", + "validate", + "bytesify", +] + + +class ProtocolError(Exception): + """Exception indicating a violation of the HTTP/1.1 protocol. + + This as an abstract base class, with two concrete base classes: + :exc:`LocalProtocolError`, which indicates that you tried to do something + that HTTP/1.1 says is illegal, and :exc:`RemoteProtocolError`, which + indicates that the remote peer tried to do something that HTTP/1.1 says is + illegal. See :ref:`error-handling` for details. + + In addition to the normal :exc:`Exception` features, it has one attribute: + + .. attribute:: error_status_hint + + This gives a suggestion as to what status code a server might use if + this error occurred as part of a request. + + For a :exc:`RemoteProtocolError`, this is useful as a suggestion for + how you might want to respond to a misbehaving peer, if you're + implementing a server. + + For a :exc:`LocalProtocolError`, this can be taken as a suggestion for + how your peer might have responded to *you* if h11 had allowed you to + continue. + + The default is 400 Bad Request, a generic catch-all for protocol + violations. + + """ + + def __init__(self, msg: str, error_status_hint: int = 400) -> None: + if type(self) is ProtocolError: + raise TypeError("tried to directly instantiate ProtocolError") + Exception.__init__(self, msg) + self.error_status_hint = error_status_hint + + +# Strategy: there are a number of public APIs where a LocalProtocolError can +# be raised (send(), all the different event constructors, ...), and only one +# public API where RemoteProtocolError can be raised +# (receive_data()). Therefore we always raise LocalProtocolError internally, +# and then receive_data will translate this into a RemoteProtocolError. +# +# Internally: +# LocalProtocolError is the generic "ProtocolError". +# Externally: +# LocalProtocolError is for local errors and RemoteProtocolError is for +# remote errors. +class LocalProtocolError(ProtocolError): + def _reraise_as_remote_protocol_error(self) -> NoReturn: + # After catching a LocalProtocolError, use this method to re-raise it + # as a RemoteProtocolError. This method must be called from inside an + # except: block. + # + # An easy way to get an equivalent RemoteProtocolError is just to + # modify 'self' in place. + self.__class__ = RemoteProtocolError # type: ignore + # But the re-raising is somewhat non-trivial -- you might think that + # now that we've modified the in-flight exception object, that just + # doing 'raise' to re-raise it would be enough. But it turns out that + # this doesn't work, because Python tracks the exception type + # (exc_info[0]) separately from the exception object (exc_info[1]), + # and we only modified the latter. So we really do need to re-raise + # the new type explicitly. + # On py3, the traceback is part of the exception object, so our + # in-place modification preserved it and we can just re-raise: + raise self + + +class RemoteProtocolError(ProtocolError): + pass + + +def validate( + regex: Pattern[bytes], data: bytes, msg: str = "malformed data", *format_args: Any +) -> Dict[str, bytes]: + match = regex.fullmatch(data) + if not match: + if format_args: + msg = msg.format(*format_args) + raise LocalProtocolError(msg) + return match.groupdict() + + +# Sentinel values +# +# - Inherit identity-based comparison and hashing from object +# - Have a nice repr +# - Have a *bonus property*: type(sentinel) is sentinel +# +# The bonus property is useful if you want to take the return value from +# next_event() and do some sort of dispatch based on type(event). + +_T_Sentinel = TypeVar("_T_Sentinel", bound="Sentinel") + + +class Sentinel(type): + def __new__( + cls: Type[_T_Sentinel], + name: str, + bases: Tuple[type, ...], + namespace: Dict[str, Any], + **kwds: Any + ) -> _T_Sentinel: + assert bases == (Sentinel,) + v = super().__new__(cls, name, bases, namespace, **kwds) + v.__class__ = v # type: ignore + return v + + def __repr__(self) -> str: + return self.__name__ + + +# Used for methods, request targets, HTTP versions, header names, and header +# values. Accepts ascii-strings, or bytes/bytearray/memoryview/..., and always +# returns bytes. +def bytesify(s: Union[bytes, bytearray, memoryview, int, str]) -> bytes: + # Fast-path: + if type(s) is bytes: + return s + if isinstance(s, str): + s = s.encode("ascii") + if isinstance(s, int): + raise TypeError("expected bytes-like object, not int") + return bytes(s) diff --git a/venv/lib/python3.12/site-packages/h11/_version.py b/venv/lib/python3.12/site-packages/h11/_version.py new file mode 100644 index 0000000..76e7327 --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11/_version.py @@ -0,0 +1,16 @@ +# This file must be kept very simple, because it is consumed from several +# places -- it is imported by h11/__init__.py, execfile'd by setup.py, etc. + +# We use a simple scheme: +# 1.0.0 -> 1.0.0+dev -> 1.1.0 -> 1.1.0+dev +# where the +dev versions are never released into the wild, they're just what +# we stick into the VCS in between releases. +# +# This is compatible with PEP 440: +# http://legacy.python.org/dev/peps/pep-0440/ +# via the use of the "local suffix" "+dev", which is disallowed on index +# servers and causes 1.0.0+dev to sort after plain 1.0.0, which is what we +# want. (Contrast with the special suffix 1.0.0.dev, which sorts *before* +# 1.0.0.) + +__version__ = "0.16.0" diff --git a/venv/lib/python3.12/site-packages/h11/_writers.py b/venv/lib/python3.12/site-packages/h11/_writers.py new file mode 100644 index 0000000..939cdb9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11/_writers.py @@ -0,0 +1,145 @@ +# Code to read HTTP data +# +# Strategy: each writer takes an event + a write-some-bytes function, which is +# calls. +# +# WRITERS is a dict describing how to pick a reader. It maps states to either: +# - a writer +# - or, for body writers, a dict of framin-dependent writer factories + +from typing import Any, Callable, Dict, List, Tuple, Type, Union + +from ._events import Data, EndOfMessage, Event, InformationalResponse, Request, Response +from ._headers import Headers +from ._state import CLIENT, IDLE, SEND_BODY, SEND_RESPONSE, SERVER +from ._util import LocalProtocolError, Sentinel + +__all__ = ["WRITERS"] + +Writer = Callable[[bytes], Any] + + +def write_headers(headers: Headers, write: Writer) -> None: + # "Since the Host field-value is critical information for handling a + # request, a user agent SHOULD generate Host as the first header field + # following the request-line." - RFC 7230 + raw_items = headers._full_items + for raw_name, name, value in raw_items: + if name == b"host": + write(b"%s: %s\r\n" % (raw_name, value)) + for raw_name, name, value in raw_items: + if name != b"host": + write(b"%s: %s\r\n" % (raw_name, value)) + write(b"\r\n") + + +def write_request(request: Request, write: Writer) -> None: + if request.http_version != b"1.1": + raise LocalProtocolError("I only send HTTP/1.1") + write(b"%s %s HTTP/1.1\r\n" % (request.method, request.target)) + write_headers(request.headers, write) + + +# Shared between InformationalResponse and Response +def write_any_response( + response: Union[InformationalResponse, Response], write: Writer +) -> None: + if response.http_version != b"1.1": + raise LocalProtocolError("I only send HTTP/1.1") + status_bytes = str(response.status_code).encode("ascii") + # We don't bother sending ascii status messages like "OK"; they're + # optional and ignored by the protocol. (But the space after the numeric + # status code is mandatory.) + # + # XX FIXME: could at least make an effort to pull out the status message + # from stdlib's http.HTTPStatus table. Or maybe just steal their enums + # (either by import or copy/paste). We already accept them as status codes + # since they're of type IntEnum < int. + write(b"HTTP/1.1 %s %s\r\n" % (status_bytes, response.reason)) + write_headers(response.headers, write) + + +class BodyWriter: + def __call__(self, event: Event, write: Writer) -> None: + if type(event) is Data: + self.send_data(event.data, write) + elif type(event) is EndOfMessage: + self.send_eom(event.headers, write) + else: # pragma: no cover + assert False + + def send_data(self, data: bytes, write: Writer) -> None: + pass + + def send_eom(self, headers: Headers, write: Writer) -> None: + pass + + +# +# These are all careful not to do anything to 'data' except call len(data) and +# write(data). This allows us to transparently pass-through funny objects, +# like placeholder objects referring to files on disk that will be sent via +# sendfile(2). +# +class ContentLengthWriter(BodyWriter): + def __init__(self, length: int) -> None: + self._length = length + + def send_data(self, data: bytes, write: Writer) -> None: + self._length -= len(data) + if self._length < 0: + raise LocalProtocolError("Too much data for declared Content-Length") + write(data) + + def send_eom(self, headers: Headers, write: Writer) -> None: + if self._length != 0: + raise LocalProtocolError("Too little data for declared Content-Length") + if headers: + raise LocalProtocolError("Content-Length and trailers don't mix") + + +class ChunkedWriter(BodyWriter): + def send_data(self, data: bytes, write: Writer) -> None: + # if we encoded 0-length data in the naive way, it would look like an + # end-of-message. + if not data: + return + write(b"%x\r\n" % len(data)) + write(data) + write(b"\r\n") + + def send_eom(self, headers: Headers, write: Writer) -> None: + write(b"0\r\n") + write_headers(headers, write) + + +class Http10Writer(BodyWriter): + def send_data(self, data: bytes, write: Writer) -> None: + write(data) + + def send_eom(self, headers: Headers, write: Writer) -> None: + if headers: + raise LocalProtocolError("can't send trailers to HTTP/1.0 client") + # no need to close the socket ourselves, that will be taken care of by + # Connection: close machinery + + +WritersType = Dict[ + Union[Tuple[Type[Sentinel], Type[Sentinel]], Type[Sentinel]], + Union[ + Dict[str, Type[BodyWriter]], + Callable[[Union[InformationalResponse, Response], Writer], None], + Callable[[Request, Writer], None], + ], +] + +WRITERS: WritersType = { + (CLIENT, IDLE): write_request, + (SERVER, IDLE): write_any_response, + (SERVER, SEND_RESPONSE): write_any_response, + SEND_BODY: { + "chunked": ChunkedWriter, + "content-length": ContentLengthWriter, + "http/1.0": Http10Writer, + }, +} diff --git a/venv/lib/python3.12/site-packages/h11/py.typed b/venv/lib/python3.12/site-packages/h11/py.typed new file mode 100644 index 0000000..f5642f7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/h11/py.typed @@ -0,0 +1 @@ +Marker diff --git a/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt b/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt new file mode 100644 index 0000000..7b190ca --- /dev/null +++ b/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright 2011 Pallets + +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/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/METADATA b/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/METADATA new file mode 100644 index 0000000..ddf5464 --- /dev/null +++ b/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/METADATA @@ -0,0 +1,60 @@ +Metadata-Version: 2.1 +Name: itsdangerous +Version: 2.2.0 +Summary: Safely pass data to untrusted environments and back. +Maintainer-email: Pallets +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Typing :: Typed +Project-URL: Changes, https://itsdangerous.palletsprojects.com/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://itsdangerous.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Source, https://github.com/pallets/itsdangerous/ + +# ItsDangerous + +... so better sign this + +Various helpers to pass data to untrusted environments and to get it +back safe and sound. Data is cryptographically signed to ensure that a +token has not been tampered with. + +It's possible to customize how data is serialized. Data is compressed as +needed. A timestamp can be added and verified automatically while +loading a token. + + +## A Simple Example + +Here's how you could generate a token for transmitting a user's id and +name between web requests. + +```python +from itsdangerous import URLSafeSerializer +auth_s = URLSafeSerializer("secret key", "auth") +token = auth_s.dumps({"id": 5, "name": "itsdangerous"}) + +print(token) +# eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9.6YP6T0BaO67XP--9UzTrmurXSmg + +data = auth_s.loads(token) +print(data["name"]) +# itsdangerous +``` + + +## Donate + +The Pallets organization develops and supports ItsDangerous and other +popular packages. In order to grow the community of contributors and +users, and allow the maintainers to devote more time to the projects, +[please donate today][]. + +[please donate today]: https://palletsprojects.com/donate + diff --git a/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/RECORD b/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/RECORD new file mode 100644 index 0000000..245f43e --- /dev/null +++ b/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/RECORD @@ -0,0 +1,22 @@ +itsdangerous-2.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +itsdangerous-2.2.0.dist-info/LICENSE.txt,sha256=Y68JiRtr6K0aQlLtQ68PTvun_JSOIoNnvtfzxa4LCdc,1475 +itsdangerous-2.2.0.dist-info/METADATA,sha256=0rk0-1ZwihuU5DnwJVwPWoEI4yWOyCexih3JyZHblhE,1924 +itsdangerous-2.2.0.dist-info/RECORD,, +itsdangerous-2.2.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 +itsdangerous/__init__.py,sha256=4SK75sCe29xbRgQE1ZQtMHnKUuZYAf3bSpZOrff1IAY,1427 +itsdangerous/__pycache__/__init__.cpython-312.pyc,, +itsdangerous/__pycache__/_json.cpython-312.pyc,, +itsdangerous/__pycache__/encoding.cpython-312.pyc,, +itsdangerous/__pycache__/exc.cpython-312.pyc,, +itsdangerous/__pycache__/serializer.cpython-312.pyc,, +itsdangerous/__pycache__/signer.cpython-312.pyc,, +itsdangerous/__pycache__/timed.cpython-312.pyc,, +itsdangerous/__pycache__/url_safe.cpython-312.pyc,, +itsdangerous/_json.py,sha256=wPQGmge2yZ9328EHKF6gadGeyGYCJQKxtU-iLKE6UnA,473 +itsdangerous/encoding.py,sha256=wwTz5q_3zLcaAdunk6_vSoStwGqYWe307Zl_U87aRFM,1409 +itsdangerous/exc.py,sha256=Rr3exo0MRFEcPZltwecyK16VV1bE2K9_F1-d-ljcUn4,3201 +itsdangerous/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +itsdangerous/serializer.py,sha256=PmdwADLqkSyQLZ0jOKAgDsAW4k_H0TlA71Ei3z0C5aI,15601 +itsdangerous/signer.py,sha256=YO0CV7NBvHA6j549REHJFUjUojw2pHqwcUpQnU7yNYQ,9647 +itsdangerous/timed.py,sha256=6RvDMqNumGMxf0-HlpaZdN9PUQQmRvrQGplKhxuivUs,8083 +itsdangerous/url_safe.py,sha256=az4e5fXi_vs-YbWj8YZwn4wiVKfeD--GEKRT5Ueu4P4,2505 diff --git a/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/WHEEL b/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/WHEEL new file mode 100644 index 0000000..3b5e64b --- /dev/null +++ b/venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/venv/lib/python3.12/site-packages/itsdangerous/__init__.py b/venv/lib/python3.12/site-packages/itsdangerous/__init__.py new file mode 100644 index 0000000..ea55256 --- /dev/null +++ b/venv/lib/python3.12/site-packages/itsdangerous/__init__.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import typing as t + +from .encoding import base64_decode as base64_decode +from .encoding import base64_encode as base64_encode +from .encoding import want_bytes as want_bytes +from .exc import BadData as BadData +from .exc import BadHeader as BadHeader +from .exc import BadPayload as BadPayload +from .exc import BadSignature as BadSignature +from .exc import BadTimeSignature as BadTimeSignature +from .exc import SignatureExpired as SignatureExpired +from .serializer import Serializer as Serializer +from .signer import HMACAlgorithm as HMACAlgorithm +from .signer import NoneAlgorithm as NoneAlgorithm +from .signer import Signer as Signer +from .timed import TimedSerializer as TimedSerializer +from .timed import TimestampSigner as TimestampSigner +from .url_safe import URLSafeSerializer as URLSafeSerializer +from .url_safe import URLSafeTimedSerializer as URLSafeTimedSerializer + + +def __getattr__(name: str) -> t.Any: + if name == "__version__": + import importlib.metadata + import warnings + + warnings.warn( + "The '__version__' attribute is deprecated and will be removed in" + " ItsDangerous 2.3. Use feature detection or" + " 'importlib.metadata.version(\"itsdangerous\")' instead.", + DeprecationWarning, + stacklevel=2, + ) + return importlib.metadata.version("itsdangerous") + + raise AttributeError(name) diff --git a/venv/lib/python3.12/site-packages/itsdangerous/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/itsdangerous/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8fb6dd2efa62cf2aa27c085c247a95e48727a302 GIT binary patch literal 1612 zcmZXU&u<$=6vt=%V|Tsw+K%(XNr~D`D5QI+4M{<&sE|TaC=!h-(h6xWtFdQdZ@NFM z*|AeciG&abIC0?6Ub%tFU%?SBEj_f_$_S}&h}1)*c=bgM{1GwN6BcYre6xmPFZr%p9(5Y zMREyv+L@MI2A*+dBv1LX!JIQExdL2us*M`pjxIB)e+cJ)wf z-Byx34#S9J?nPm|d8iCUvw>s!;T4zAW<)5=8VfWW3C1oCx!dS-8pE*m2__quW568X zO^OL+U>IQB#(h7+1grvBTV5-~yvu~sGT3*#fS%D#jT`qn9;4zrV~a8m``(`PY2LiG zetq3kYm?nqUiU2y3?lC%7naPVa}P? z=yGa#u|;Ty(I)1USU4osuIKw!gIbIRQ4cgPv_9wY1`b=4Mcvq1t*zCpyD_!4smv~X z&uLQ>l@&2-#S1zS6aPi|3EkZ!C$dP4PuW!}cQ9$d>$Zz!P%{Clfzuu!^uMWGcclx{?g=@9E#aW z0JmsdpNzlmx?bpU*R6H>B10`W8i)Zh^bV{sFBaQ^?rZd1%YS$9r?&V)UwWZe|JKVt znBSX6sg(Jda-t&h!t?6tWwsObplE?uvT~;fO6KD(`pTR1<|K zy8H`(!9-CrQNv7=uIT+H6NSriq7(mJCTf-yL?@~fvUz%!Sy2dTEH5i7i792#O$j;6 z#m!|EAxsNF?=s(w;g+%)q0Nd;oYOCey({=4bg%VmK}5Pf{g|zRDK18Q9lE2mq9_Bj u{1R3EMOOymsSVKE1GF+g_5fWTpbzBp-T2_bBkQ+`PELsPzh61(kL$J%%CRKHjJH?eaQrk1!>MSR${naX|Mkh@( zXJ(%iS0#KKvw1d<7GV}&wM?;KST{P5`n zfK4PjZ`BsHjwOIKw9|T@1)fD$OHE%TOwC$Er~v9+F-bD4?40I|SBdJ(D@V1t#4AHN zGVzvJxa8G>IWJCRB?_ksw{H%{68E0 zZ1@<-)?5*)w$`W}OWkW|9tRNTHGYAiZ)P#=Wua(r<`Yp4f=6k$U@_rDMqIdB6Hhep zJy%y+N!dJU)FkhMFsVr!^%yKtJ({bg*5WeZ3qq(dISyA{;6ot(X;J3W4|;J2#5y{( z`#;(vd-ljyVf*U7ee+Z2dt$qL-=6x?YT?x0fcxqih+W&|&@xnX4Sr6=?}3eS)||Nn z_9?YElMNm=O<8ylCa%TLg9*Q=#Bc{)0F>(B2HR`y!Vz&;vif|vCMMu%kX|yMAM$msodwI g@St`|Tmo^dV~oF|(IYhc84dlidhqxO0+D(C2H(995&!@I literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/itsdangerous/__pycache__/encoding.cpython-312.pyc b/venv/lib/python3.12/site-packages/itsdangerous/__pycache__/encoding.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae0a8b945d42bf41779939d4033d58bd093fc40e GIT binary patch literal 2666 zcma(SU2GIZc;HCRB-o z35_U3wW=mUi4aEBh}2dCT>p_e68t=vs3uXOeXb_LfU5xORLQjZm#^b%2seE`X?-G325GknoyJJ+04duVBhWq7n9LndhJ85FK{xe42hU z;L#Gzq@L7(MJ=R(L;$x=1!9el1T%(R-L&m2H+ed1J426zqCl)VYLa6nH&cptgoGjh zu*NH|!{jRBWup+9M!b9#LjI+!VH#y1ginrXJRR_5W?<(qTsIU`+;G}S+YUGF6m=Dc zGgqT_DoZG^u;Yg0B5jWWnXxQ$9HE$D7^^4aTbaY4@a=`p_JsPCU&WV%lSrWphs4Kf7mg$_-yIZJr#N;?!ZaaD@(^ z(+OY}J=RdDVQ$-%;d@nWOEr-%PkeD=?%nzGrJ5a=@B_VO_QVQ^q12jJU?G&09{T_$ zl^*lMJ%hNAk|R@2aepoFH1Z`gRU7HvoM4{|ehlQ_KWaL(zt8YoXP5>!5}amaM-6F` zk!1#_7kQ6yW}F&~Iys9AzQ;5f4TDP!#~h^w&l)+08fn{j>&-Wu9?4h{wjGSA(lf3$ zA}@36bIcN3k|X_n-o>uQ#Kn`4NSddCBX$}r0?>_k`;wk4YX;gT_#BbJ;N=jiZJpOj zk*%|X_x1Wky|buyULP#!yB77{qTYL}ucQwY)B#op;uX#o$tInB2PT!yCYS`iS8kRa zEz*vt>uz6nv5rVEuOXC&v+|(q40H*;mc$w0JUQ0)4x>)QLq4C&J|c!0s znocU6cD3}Fon-n7!h@Z!_$#fKOK`oK`E$_u(;QJXb@ zctzMfMcwK%QAoOS(lv3WWBO7Ot_x~9dtu3}x+f>x@F7eevB z@7FY4$rtpFzYqp?ZBwCnZ>hHL_O?Rk<$snHpshMWtM4}LM|bLuY*y}8*Boh5?luKs zo>#h)2bp*P@_|FA^ILh(tEOd{BNjb-R{HkbZiDA0VVSo1xpNh%C=yMkVn0kOQ_&B7 zrkso*ur?~Ic}SKUc+QO^;ff^BdVR^&EWf%GJ2$bhv}!iWu^n*m9{3z##4Nh6x6U_~ z^!7!4dr{wh{hgBDT~NE9t|O7Il_qNq-8YUXZLk+wk~;KAlmzP#Km=Y| zBi$$L0d?IV%j6s`-W*Bhtiqs;qHH%Dun#^*JhGlaE#J0(-G2R}g|1RtZ%N;K>qt@W zFR1;}Drkto93Sl-@VZcn$3je$<*E+;Gp4<+=aqs~Qr3f*LI0vW$80w!uZM;wvV`>r z5EtMD06={XkgI?Z71Uk6ghv$-h_$)lWO6hIhtgz{iO|a;@@hi#$XtrM8lRdJPaNj1 zLNBId-N>qwJyI5j8FOIux>0uR)zE>7EXi5)5IYQf(axP80YDXD{5$G+ zh$6q0{|$el*5wd7a11Xi8jdVgHF)^hZ64k?An}7>cqIt@m4+aGX(bxQ2bQ;>miEQw zoyF#zg{EB#gFl?SadP36+nt5RLyL{ai;c&B8Y?vp6;Sj+w0%yy8o3;qC#7iTY-B0g tz8LK&Mmxl`c@AIIE^CFBf!p1S`-h7Ahf2{?vyl}ItsSS$bl literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/itsdangerous/__pycache__/exc.cpython-312.pyc b/venv/lib/python3.12/site-packages/itsdangerous/__pycache__/exc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8bb78126c2684f1eb9208700548e9ce9a750b52e GIT binary patch literal 3926 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/itsdangerous/__pycache__/url_safe.cpython-312.pyc b/venv/lib/python3.12/site-packages/itsdangerous/__pycache__/url_safe.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36f9015f9bbd95d4aea5e518184bbb8c1693272b GIT binary patch literal 3516 zcmbtWU2Ggz6~41Gv;RN-$*%1r#ZAvtH4%aJl?ss$DZ9; z?#$NN)tW{qh>Z}ac|dVRku1bRs(=(E9(Y1cB_4o2I5iU1kf5NJhrVrUS48Ox=iJ%# zI;Io>*Xujyo^$S*JLjD5-1EmoB1&NV;qAZ9{XRm-d${O~kWWY(?*MU)7{rh)Qj!Xi zgml0P6lC#K3W^~cilvr4r zNO#o&+1?LOH7j{ByTlT6fs-W17K=w!yYB zSh>xJp3;pM^qOVs28esK7tZLeKFPSLTV|bc;6)!U;{>lBheNbuR8?gR9 zIG&M$WRODOB?v%4W=cUd0xW3ACqZr~hWdsSAv^z!aFfhPM(}d75Hccy4qc8HfO}CY zL?%f#>SbQ~`qw7)8P+|B+C7fpAL>XftDs-<)4Y<&;S|3tpeWX7sA% zYTb{*P)EDVVxS!q%nB9$Q-nwx`(X8B3BVvNsSRl!WjlruxY6eFP(uPRklkcSGL$7X zLfm!&6Vi}6T1&x($}=DX`1xaAK(e#)pN(Kw-%WI6SHcbQ!@XcFI4d>epGm(Anxvup zT>6nTNk)m=&Ll$i6W+fRa@&B0tWg`GUj%QozpxSdZonW*q3?tirA2a4{w7(JK6Q8K zKkd$jC!X0rFXT9$;l=ui({|M|C}54Y1M7oH1h(QM2Vz*%s}7^~428h5i$zvJU_w}u zHaBO@vTkXNbDP)4yXG+MC>qTTp2WO} zrWGySaWu`rpd)B-TzYseKWCR%zGz$1wR{n>+^ywbVdYoyaFBeZ=FZvWqq!%Kyx zhoM3(=(Ehpo33N%4D;OfZx{eY=eICDxI|h>lG)RY z-i!79IW~MdHoR>7DK@&E8D7?xr(auWo?RarZa&vif|2x{e!4QS)}L*jxt}EQOe+vf z4BZ*nbM4iuudcXj1BYHyHj|_$eP!`>YINnyYHIBIv3FBrH;>;=jje{q9&C<~o}mwk z0*3nbuJ@cV@}jr8~ta;rZwo|kX!O^zRwZyi*TJ`y>Vly99#o{GqCM^vD< zP!2l@<|-gm+W0jPh;kRXVq6IV(x3`#!)*X+fb3#r5Mv&5Xvb)5)q*9Zi|>Zg!SXiV zmZlM?1O68Pn_OuqO94ZMx5PSzw^>vhGFJ_yq1HhN)kuY^u1-ATBaZh1GaKX>Subu> zOO;N(@-(u-9l&_9#mXEjLpH?mlJ#VJdY%s>l`tKxqsI&{x{VI`FgExIC_1=Zq%gh2 zDbRc$O#e3Xwx8znpy+%HCdj-ysa<}qoL);EXe#T2!_CB<^w6~nS1+s_TT35kM&FMm zoAn=j`HkYr{-2oFAA7y@ZtT!{|8Von`d->R_kQ2Vin7-Ccr*03*x-6*_wxCx<>uLY zsZ2Au0dIgikMD+ur%u;g=I}j0KUCe^c)2DZTaOExHt2+1W*o{Y;h#nQAZKosm&GEh z@W(p>eh8_9Fu`pa*0i!-Vw&bfHLYYDRSW63rhU7rTWv{5(+s<)X&jS=Ct>nZnzpS4 z0TE(WxX(+pt3;7mmZoJT-_=-EpZ1-M*8s-}3y$+LOb@8L+4z*0^2gXDl9Gl8@n~*6wU3Dqu zhy&MvL9tFb(2YO~A@21kCK^__PH$0!-pzDeEbwr1VAD~pK$UK9USzxo?Hh$^%AutL zzON+A=vHM;pJp!A9l8i^#^==36WS9~Q-_c^41}-)bPj00rhlk#VdueHDle#K&dwA%@1+LGgd($4;}hqj9YN?vtFkL+R5zD%=1u`aeA?%%JQ4 zN00KS&|k-K5_v|E`a)-V9to@zz7YA@G~#Tg3cv)Maj4pWQ@2)uEbt`PuW_n|7p?%C z7Z!~qNX1nz>YXP;iy>}$0e_BHF~`=+Q(bJ&ITl?{hIUba%nzwJ)1&8n7(VIa{Y(55 z>_9{aFt%h#lJ1gy?~%U0kl5eJ$XzljetYhc1HgyhX7IQ)uw1w t.Any: + return _json.loads(payload) + + @staticmethod + def dumps(obj: t.Any, **kwargs: t.Any) -> str: + kwargs.setdefault("ensure_ascii", False) + kwargs.setdefault("separators", (",", ":")) + return _json.dumps(obj, **kwargs) diff --git a/venv/lib/python3.12/site-packages/itsdangerous/encoding.py b/venv/lib/python3.12/site-packages/itsdangerous/encoding.py new file mode 100644 index 0000000..f5ca80f --- /dev/null +++ b/venv/lib/python3.12/site-packages/itsdangerous/encoding.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import base64 +import string +import struct +import typing as t + +from .exc import BadData + + +def want_bytes( + s: str | bytes, encoding: str = "utf-8", errors: str = "strict" +) -> bytes: + if isinstance(s, str): + s = s.encode(encoding, errors) + + return s + + +def base64_encode(string: str | bytes) -> bytes: + """Base64 encode a string of bytes or text. The resulting bytes are + safe to use in URLs. + """ + string = want_bytes(string) + return base64.urlsafe_b64encode(string).rstrip(b"=") + + +def base64_decode(string: str | bytes) -> bytes: + """Base64 decode a URL-safe string of bytes or text. The result is + bytes. + """ + string = want_bytes(string, encoding="ascii", errors="ignore") + string += b"=" * (-len(string) % 4) + + try: + return base64.urlsafe_b64decode(string) + except (TypeError, ValueError) as e: + raise BadData("Invalid base64-encoded data") from e + + +# The alphabet used by base64.urlsafe_* +_base64_alphabet = f"{string.ascii_letters}{string.digits}-_=".encode("ascii") + +_int64_struct = struct.Struct(">Q") +_int_to_bytes = _int64_struct.pack +_bytes_to_int = t.cast("t.Callable[[bytes], tuple[int]]", _int64_struct.unpack) + + +def int_to_bytes(num: int) -> bytes: + return _int_to_bytes(num).lstrip(b"\x00") + + +def bytes_to_int(bytestr: bytes) -> int: + return _bytes_to_int(bytestr.rjust(8, b"\x00"))[0] diff --git a/venv/lib/python3.12/site-packages/itsdangerous/exc.py b/venv/lib/python3.12/site-packages/itsdangerous/exc.py new file mode 100644 index 0000000..a75adcd --- /dev/null +++ b/venv/lib/python3.12/site-packages/itsdangerous/exc.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import typing as t +from datetime import datetime + + +class BadData(Exception): + """Raised if bad data of any sort was encountered. This is the base + for all exceptions that ItsDangerous defines. + + .. versionadded:: 0.15 + """ + + def __init__(self, message: str): + super().__init__(message) + self.message = message + + def __str__(self) -> str: + return self.message + + +class BadSignature(BadData): + """Raised if a signature does not match.""" + + def __init__(self, message: str, payload: t.Any | None = None): + super().__init__(message) + + #: The payload that failed the signature test. In some + #: situations you might still want to inspect this, even if + #: you know it was tampered with. + #: + #: .. versionadded:: 0.14 + self.payload: t.Any | None = payload + + +class BadTimeSignature(BadSignature): + """Raised if a time-based signature is invalid. This is a subclass + of :class:`BadSignature`. + """ + + def __init__( + self, + message: str, + payload: t.Any | None = None, + date_signed: datetime | None = None, + ): + super().__init__(message, payload) + + #: If the signature expired this exposes the date of when the + #: signature was created. This can be helpful in order to + #: tell the user how long a link has been gone stale. + #: + #: .. versionchanged:: 2.0 + #: The datetime value is timezone-aware rather than naive. + #: + #: .. versionadded:: 0.14 + self.date_signed = date_signed + + +class SignatureExpired(BadTimeSignature): + """Raised if a signature timestamp is older than ``max_age``. This + is a subclass of :exc:`BadTimeSignature`. + """ + + +class BadHeader(BadSignature): + """Raised if a signed header is invalid in some form. This only + happens for serializers that have a header that goes with the + signature. + + .. versionadded:: 0.24 + """ + + def __init__( + self, + message: str, + payload: t.Any | None = None, + header: t.Any | None = None, + original_error: Exception | None = None, + ): + super().__init__(message, payload) + + #: If the header is actually available but just malformed it + #: might be stored here. + self.header: t.Any | None = header + + #: If available, the error that indicates why the payload was + #: not valid. This might be ``None``. + self.original_error: Exception | None = original_error + + +class BadPayload(BadData): + """Raised if a payload is invalid. This could happen if the payload + is loaded despite an invalid signature, or if there is a mismatch + between the serializer and deserializer. The original exception + that occurred during loading is stored on as :attr:`original_error`. + + .. versionadded:: 0.15 + """ + + def __init__(self, message: str, original_error: Exception | None = None): + super().__init__(message) + + #: If available, the error that indicates why the payload was + #: not valid. This might be ``None``. + self.original_error: Exception | None = original_error diff --git a/venv/lib/python3.12/site-packages/itsdangerous/py.typed b/venv/lib/python3.12/site-packages/itsdangerous/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/itsdangerous/serializer.py b/venv/lib/python3.12/site-packages/itsdangerous/serializer.py new file mode 100644 index 0000000..5ddf387 --- /dev/null +++ b/venv/lib/python3.12/site-packages/itsdangerous/serializer.py @@ -0,0 +1,406 @@ +from __future__ import annotations + +import collections.abc as cabc +import json +import typing as t + +from .encoding import want_bytes +from .exc import BadPayload +from .exc import BadSignature +from .signer import _make_keys_list +from .signer import Signer + +if t.TYPE_CHECKING: + import typing_extensions as te + + # This should be either be str or bytes. To avoid having to specify the + # bound type, it falls back to a union if structural matching fails. + _TSerialized = te.TypeVar( + "_TSerialized", bound=t.Union[str, bytes], default=t.Union[str, bytes] + ) +else: + # Still available at runtime on Python < 3.13, but without the default. + _TSerialized = t.TypeVar("_TSerialized", bound=t.Union[str, bytes]) + + +class _PDataSerializer(t.Protocol[_TSerialized]): + def loads(self, payload: _TSerialized, /) -> t.Any: ... + # A signature with additional arguments is not handled correctly by type + # checkers right now, so an overload is used below for serializers that + # don't match this strict protocol. + def dumps(self, obj: t.Any, /) -> _TSerialized: ... + + +# Use TypeIs once it's available in typing_extensions or 3.13. +def is_text_serializer( + serializer: _PDataSerializer[t.Any], +) -> te.TypeGuard[_PDataSerializer[str]]: + """Checks whether a serializer generates text or binary.""" + return isinstance(serializer.dumps({}), str) + + +class Serializer(t.Generic[_TSerialized]): + """A serializer wraps a :class:`~itsdangerous.signer.Signer` to + enable serializing and securely signing data other than bytes. It + can unsign to verify that the data hasn't been changed. + + The serializer provides :meth:`dumps` and :meth:`loads`, similar to + :mod:`json`, and by default uses :mod:`json` internally to serialize + the data to bytes. + + The secret key should be a random string of ``bytes`` and should not + be saved to code or version control. Different salts should be used + to distinguish signing in different contexts. See :doc:`/concepts` + for information about the security of the secret key and salt. + + :param secret_key: The secret key to sign and verify with. Can be a + list of keys, oldest to newest, to support key rotation. + :param salt: Extra key to combine with ``secret_key`` to distinguish + signatures in different contexts. + :param serializer: An object that provides ``dumps`` and ``loads`` + methods for serializing data to a string. Defaults to + :attr:`default_serializer`, which defaults to :mod:`json`. + :param serializer_kwargs: Keyword arguments to pass when calling + ``serializer.dumps``. + :param signer: A ``Signer`` class to instantiate when signing data. + Defaults to :attr:`default_signer`, which defaults to + :class:`~itsdangerous.signer.Signer`. + :param signer_kwargs: Keyword arguments to pass when instantiating + the ``Signer`` class. + :param fallback_signers: List of signer parameters to try when + unsigning with the default signer fails. Each item can be a dict + of ``signer_kwargs``, a ``Signer`` class, or a tuple of + ``(signer, signer_kwargs)``. Defaults to + :attr:`default_fallback_signers`. + + .. versionchanged:: 2.0 + Added support for key rotation by passing a list to + ``secret_key``. + + .. versionchanged:: 2.0 + Removed the default SHA-512 fallback signer from + ``default_fallback_signers``. + + .. versionchanged:: 1.1 + Added support for ``fallback_signers`` and configured a default + SHA-512 fallback. This fallback is for users who used the yanked + 1.0.0 release which defaulted to SHA-512. + + .. versionchanged:: 0.14 + The ``signer`` and ``signer_kwargs`` parameters were added to + the constructor. + """ + + #: The default serialization module to use to serialize data to a + #: string internally. The default is :mod:`json`, but can be changed + #: to any object that provides ``dumps`` and ``loads`` methods. + default_serializer: _PDataSerializer[t.Any] = json + + #: The default ``Signer`` class to instantiate when signing data. + #: The default is :class:`itsdangerous.signer.Signer`. + default_signer: type[Signer] = Signer + + #: The default fallback signers to try when unsigning fails. + default_fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] = [] + + # Serializer[str] if no data serializer is provided, or if it returns str. + @t.overload + def __init__( + self: Serializer[str], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + serializer: None | _PDataSerializer[str] = None, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Serializer[bytes] with a bytes data serializer positional argument. + @t.overload + def __init__( + self: Serializer[bytes], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None, + serializer: _PDataSerializer[bytes], + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Serializer[bytes] with a bytes data serializer keyword argument. + @t.overload + def __init__( + self: Serializer[bytes], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + *, + serializer: _PDataSerializer[bytes], + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Fall back with a positional argument. If the strict signature of + # _PDataSerializer doesn't match, fall back to a union, requiring the user + # to specify the type. + @t.overload + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None, + serializer: t.Any, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Fall back with a keyword argument. + @t.overload + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + *, + serializer: t.Any, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + serializer: t.Any | None = None, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): + #: The list of secret keys to try for verifying signatures, from + #: oldest to newest. The newest (last) key is used for signing. + #: + #: This allows a key rotation system to keep a list of allowed + #: keys and remove expired ones. + self.secret_keys: list[bytes] = _make_keys_list(secret_key) + + if salt is not None: + salt = want_bytes(salt) + # if salt is None then the signer's default is used + + self.salt = salt + + if serializer is None: + serializer = self.default_serializer + + self.serializer: _PDataSerializer[_TSerialized] = serializer + self.is_text_serializer: bool = is_text_serializer(serializer) + + if signer is None: + signer = self.default_signer + + self.signer: type[Signer] = signer + self.signer_kwargs: dict[str, t.Any] = signer_kwargs or {} + + if fallback_signers is None: + fallback_signers = list(self.default_fallback_signers) + + self.fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] = fallback_signers + self.serializer_kwargs: dict[str, t.Any] = serializer_kwargs or {} + + @property + def secret_key(self) -> bytes: + """The newest (last) entry in the :attr:`secret_keys` list. This + is for compatibility from before key rotation support was added. + """ + return self.secret_keys[-1] + + def load_payload( + self, payload: bytes, serializer: _PDataSerializer[t.Any] | None = None + ) -> t.Any: + """Loads the encoded object. This function raises + :class:`.BadPayload` if the payload is not valid. The + ``serializer`` parameter can be used to override the serializer + stored on the class. The encoded ``payload`` should always be + bytes. + """ + if serializer is None: + use_serializer = self.serializer + is_text = self.is_text_serializer + else: + use_serializer = serializer + is_text = is_text_serializer(serializer) + + try: + if is_text: + return use_serializer.loads(payload.decode("utf-8")) # type: ignore[arg-type] + + return use_serializer.loads(payload) # type: ignore[arg-type] + except Exception as e: + raise BadPayload( + "Could not load the payload because an exception" + " occurred on unserializing the data.", + original_error=e, + ) from e + + def dump_payload(self, obj: t.Any) -> bytes: + """Dumps the encoded object. The return value is always bytes. + If the internal serializer returns text, the value will be + encoded as UTF-8. + """ + return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs)) + + def make_signer(self, salt: str | bytes | None = None) -> Signer: + """Creates a new instance of the signer to be used. The default + implementation uses the :class:`.Signer` base class. + """ + if salt is None: + salt = self.salt + + return self.signer(self.secret_keys, salt=salt, **self.signer_kwargs) + + def iter_unsigners(self, salt: str | bytes | None = None) -> cabc.Iterator[Signer]: + """Iterates over all signers to be tried for unsigning. Starts + with the configured signer, then constructs each signer + specified in ``fallback_signers``. + """ + if salt is None: + salt = self.salt + + yield self.make_signer(salt) + + for fallback in self.fallback_signers: + if isinstance(fallback, dict): + kwargs = fallback + fallback = self.signer + elif isinstance(fallback, tuple): + fallback, kwargs = fallback + else: + kwargs = self.signer_kwargs + + for secret_key in self.secret_keys: + yield fallback(secret_key, salt=salt, **kwargs) + + def dumps(self, obj: t.Any, salt: str | bytes | None = None) -> _TSerialized: + """Returns a signed string serialized with the internal + serializer. The return value can be either a byte or unicode + string depending on the format of the internal serializer. + """ + payload = want_bytes(self.dump_payload(obj)) + rv = self.make_signer(salt).sign(payload) + + if self.is_text_serializer: + return rv.decode("utf-8") # type: ignore[return-value] + + return rv # type: ignore[return-value] + + def dump(self, obj: t.Any, f: t.IO[t.Any], salt: str | bytes | None = None) -> None: + """Like :meth:`dumps` but dumps into a file. The file handle has + to be compatible with what the internal serializer expects. + """ + f.write(self.dumps(obj, salt)) + + def loads( + self, s: str | bytes, salt: str | bytes | None = None, **kwargs: t.Any + ) -> t.Any: + """Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the + signature validation fails. + """ + s = want_bytes(s) + last_exception = None + + for signer in self.iter_unsigners(salt): + try: + return self.load_payload(signer.unsign(s)) + except BadSignature as err: + last_exception = err + + raise t.cast(BadSignature, last_exception) + + def load(self, f: t.IO[t.Any], salt: str | bytes | None = None) -> t.Any: + """Like :meth:`loads` but loads from a file.""" + return self.loads(f.read(), salt) + + def loads_unsafe( + self, s: str | bytes, salt: str | bytes | None = None + ) -> tuple[bool, t.Any]: + """Like :meth:`loads` but without verifying the signature. This + is potentially very dangerous to use depending on how your + serializer works. The return value is ``(signature_valid, + payload)`` instead of just the payload. The first item will be a + boolean that indicates if the signature is valid. This function + never fails. + + Use it for debugging only and if you know that your serializer + module is not exploitable (for example, do not use it with a + pickle serializer). + + .. versionadded:: 0.15 + """ + return self._loads_unsafe_impl(s, salt) + + def _loads_unsafe_impl( + self, + s: str | bytes, + salt: str | bytes | None, + load_kwargs: dict[str, t.Any] | None = None, + load_payload_kwargs: dict[str, t.Any] | None = None, + ) -> tuple[bool, t.Any]: + """Low level helper function to implement :meth:`loads_unsafe` + in serializer subclasses. + """ + if load_kwargs is None: + load_kwargs = {} + + try: + return True, self.loads(s, salt=salt, **load_kwargs) + except BadSignature as e: + if e.payload is None: + return False, None + + if load_payload_kwargs is None: + load_payload_kwargs = {} + + try: + return ( + False, + self.load_payload(e.payload, **load_payload_kwargs), + ) + except BadPayload: + return False, None + + def load_unsafe( + self, f: t.IO[t.Any], salt: str | bytes | None = None + ) -> tuple[bool, t.Any]: + """Like :meth:`loads_unsafe` but loads from a file. + + .. versionadded:: 0.15 + """ + return self.loads_unsafe(f.read(), salt=salt) diff --git a/venv/lib/python3.12/site-packages/itsdangerous/signer.py b/venv/lib/python3.12/site-packages/itsdangerous/signer.py new file mode 100644 index 0000000..e324dc0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/itsdangerous/signer.py @@ -0,0 +1,266 @@ +from __future__ import annotations + +import collections.abc as cabc +import hashlib +import hmac +import typing as t + +from .encoding import _base64_alphabet +from .encoding import base64_decode +from .encoding import base64_encode +from .encoding import want_bytes +from .exc import BadSignature + + +class SigningAlgorithm: + """Subclasses must implement :meth:`get_signature` to provide + signature generation functionality. + """ + + def get_signature(self, key: bytes, value: bytes) -> bytes: + """Returns the signature for the given key and value.""" + raise NotImplementedError() + + def verify_signature(self, key: bytes, value: bytes, sig: bytes) -> bool: + """Verifies the given signature matches the expected + signature. + """ + return hmac.compare_digest(sig, self.get_signature(key, value)) + + +class NoneAlgorithm(SigningAlgorithm): + """Provides an algorithm that does not perform any signing and + returns an empty signature. + """ + + def get_signature(self, key: bytes, value: bytes) -> bytes: + return b"" + + +def _lazy_sha1(string: bytes = b"") -> t.Any: + """Don't access ``hashlib.sha1`` until runtime. FIPS builds may not include + SHA-1, in which case the import and use as a default would fail before the + developer can configure something else. + """ + return hashlib.sha1(string) + + +class HMACAlgorithm(SigningAlgorithm): + """Provides signature generation using HMACs.""" + + #: The digest method to use with the MAC algorithm. This defaults to + #: SHA1, but can be changed to any other function in the hashlib + #: module. + default_digest_method: t.Any = staticmethod(_lazy_sha1) + + def __init__(self, digest_method: t.Any = None): + if digest_method is None: + digest_method = self.default_digest_method + + self.digest_method: t.Any = digest_method + + def get_signature(self, key: bytes, value: bytes) -> bytes: + mac = hmac.new(key, msg=value, digestmod=self.digest_method) + return mac.digest() + + +def _make_keys_list( + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], +) -> list[bytes]: + if isinstance(secret_key, (str, bytes)): + return [want_bytes(secret_key)] + + return [want_bytes(s) for s in secret_key] # pyright: ignore + + +class Signer: + """A signer securely signs bytes, then unsigns them to verify that + the value hasn't been changed. + + The secret key should be a random string of ``bytes`` and should not + be saved to code or version control. Different salts should be used + to distinguish signing in different contexts. See :doc:`/concepts` + for information about the security of the secret key and salt. + + :param secret_key: The secret key to sign and verify with. Can be a + list of keys, oldest to newest, to support key rotation. + :param salt: Extra key to combine with ``secret_key`` to distinguish + signatures in different contexts. + :param sep: Separator between the signature and value. + :param key_derivation: How to derive the signing key from the secret + key and salt. Possible values are ``concat``, ``django-concat``, + or ``hmac``. Defaults to :attr:`default_key_derivation`, which + defaults to ``django-concat``. + :param digest_method: Hash function to use when generating the HMAC + signature. Defaults to :attr:`default_digest_method`, which + defaults to :func:`hashlib.sha1`. Note that the security of the + hash alone doesn't apply when used intermediately in HMAC. + :param algorithm: A :class:`SigningAlgorithm` instance to use + instead of building a default :class:`HMACAlgorithm` with the + ``digest_method``. + + .. versionchanged:: 2.0 + Added support for key rotation by passing a list to + ``secret_key``. + + .. versionchanged:: 0.18 + ``algorithm`` was added as an argument to the class constructor. + + .. versionchanged:: 0.14 + ``key_derivation`` and ``digest_method`` were added as arguments + to the class constructor. + """ + + #: The default digest method to use for the signer. The default is + #: :func:`hashlib.sha1`, but can be changed to any :mod:`hashlib` or + #: compatible object. Note that the security of the hash alone + #: doesn't apply when used intermediately in HMAC. + #: + #: .. versionadded:: 0.14 + default_digest_method: t.Any = staticmethod(_lazy_sha1) + + #: The default scheme to use to derive the signing key from the + #: secret key and salt. The default is ``django-concat``. Possible + #: values are ``concat``, ``django-concat``, and ``hmac``. + #: + #: .. versionadded:: 0.14 + default_key_derivation: str = "django-concat" + + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous.Signer", + sep: str | bytes = b".", + key_derivation: str | None = None, + digest_method: t.Any | None = None, + algorithm: SigningAlgorithm | None = None, + ): + #: The list of secret keys to try for verifying signatures, from + #: oldest to newest. The newest (last) key is used for signing. + #: + #: This allows a key rotation system to keep a list of allowed + #: keys and remove expired ones. + self.secret_keys: list[bytes] = _make_keys_list(secret_key) + self.sep: bytes = want_bytes(sep) + + if self.sep in _base64_alphabet: + raise ValueError( + "The given separator cannot be used because it may be" + " contained in the signature itself. ASCII letters," + " digits, and '-_=' must not be used." + ) + + if salt is not None: + salt = want_bytes(salt) + else: + salt = b"itsdangerous.Signer" + + self.salt = salt + + if key_derivation is None: + key_derivation = self.default_key_derivation + + self.key_derivation: str = key_derivation + + if digest_method is None: + digest_method = self.default_digest_method + + self.digest_method: t.Any = digest_method + + if algorithm is None: + algorithm = HMACAlgorithm(self.digest_method) + + self.algorithm: SigningAlgorithm = algorithm + + @property + def secret_key(self) -> bytes: + """The newest (last) entry in the :attr:`secret_keys` list. This + is for compatibility from before key rotation support was added. + """ + return self.secret_keys[-1] + + def derive_key(self, secret_key: str | bytes | None = None) -> bytes: + """This method is called to derive the key. The default key + derivation choices can be overridden here. Key derivation is not + intended to be used as a security method to make a complex key + out of a short password. Instead you should use large random + secret keys. + + :param secret_key: A specific secret key to derive from. + Defaults to the last item in :attr:`secret_keys`. + + .. versionchanged:: 2.0 + Added the ``secret_key`` parameter. + """ + if secret_key is None: + secret_key = self.secret_keys[-1] + else: + secret_key = want_bytes(secret_key) + + if self.key_derivation == "concat": + return t.cast(bytes, self.digest_method(self.salt + secret_key).digest()) + elif self.key_derivation == "django-concat": + return t.cast( + bytes, self.digest_method(self.salt + b"signer" + secret_key).digest() + ) + elif self.key_derivation == "hmac": + mac = hmac.new(secret_key, digestmod=self.digest_method) + mac.update(self.salt) + return mac.digest() + elif self.key_derivation == "none": + return secret_key + else: + raise TypeError("Unknown key derivation method") + + def get_signature(self, value: str | bytes) -> bytes: + """Returns the signature for the given value.""" + value = want_bytes(value) + key = self.derive_key() + sig = self.algorithm.get_signature(key, value) + return base64_encode(sig) + + def sign(self, value: str | bytes) -> bytes: + """Signs the given string.""" + value = want_bytes(value) + return value + self.sep + self.get_signature(value) + + def verify_signature(self, value: str | bytes, sig: str | bytes) -> bool: + """Verifies the signature for the given value.""" + try: + sig = base64_decode(sig) + except Exception: + return False + + value = want_bytes(value) + + for secret_key in reversed(self.secret_keys): + key = self.derive_key(secret_key) + + if self.algorithm.verify_signature(key, value, sig): + return True + + return False + + def unsign(self, signed_value: str | bytes) -> bytes: + """Unsigns the given string.""" + signed_value = want_bytes(signed_value) + + if self.sep not in signed_value: + raise BadSignature(f"No {self.sep!r} found in value") + + value, sig = signed_value.rsplit(self.sep, 1) + + if self.verify_signature(value, sig): + return value + + raise BadSignature(f"Signature {sig!r} does not match", payload=value) + + def validate(self, signed_value: str | bytes) -> bool: + """Only validates the given signed value. Returns ``True`` if + the signature exists and is valid. + """ + try: + self.unsign(signed_value) + return True + except BadSignature: + return False diff --git a/venv/lib/python3.12/site-packages/itsdangerous/timed.py b/venv/lib/python3.12/site-packages/itsdangerous/timed.py new file mode 100644 index 0000000..7384375 --- /dev/null +++ b/venv/lib/python3.12/site-packages/itsdangerous/timed.py @@ -0,0 +1,228 @@ +from __future__ import annotations + +import collections.abc as cabc +import time +import typing as t +from datetime import datetime +from datetime import timezone + +from .encoding import base64_decode +from .encoding import base64_encode +from .encoding import bytes_to_int +from .encoding import int_to_bytes +from .encoding import want_bytes +from .exc import BadSignature +from .exc import BadTimeSignature +from .exc import SignatureExpired +from .serializer import _TSerialized +from .serializer import Serializer +from .signer import Signer + + +class TimestampSigner(Signer): + """Works like the regular :class:`.Signer` but also records the time + of the signing and can be used to expire signatures. The + :meth:`unsign` method can raise :exc:`.SignatureExpired` if the + unsigning failed because the signature is expired. + """ + + def get_timestamp(self) -> int: + """Returns the current timestamp. The function must return an + integer. + """ + return int(time.time()) + + def timestamp_to_datetime(self, ts: int) -> datetime: + """Convert the timestamp from :meth:`get_timestamp` into an + aware :class`datetime.datetime` in UTC. + + .. versionchanged:: 2.0 + The timestamp is returned as a timezone-aware ``datetime`` + in UTC rather than a naive ``datetime`` assumed to be UTC. + """ + return datetime.fromtimestamp(ts, tz=timezone.utc) + + def sign(self, value: str | bytes) -> bytes: + """Signs the given string and also attaches time information.""" + value = want_bytes(value) + timestamp = base64_encode(int_to_bytes(self.get_timestamp())) + sep = want_bytes(self.sep) + value = value + sep + timestamp + return value + sep + self.get_signature(value) + + # Ignore overlapping signatures check, return_timestamp is the only + # parameter that affects the return type. + + @t.overload + def unsign( # type: ignore[overload-overlap] + self, + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: t.Literal[False] = False, + ) -> bytes: ... + + @t.overload + def unsign( + self, + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: t.Literal[True] = True, + ) -> tuple[bytes, datetime]: ... + + def unsign( + self, + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: bool = False, + ) -> tuple[bytes, datetime] | bytes: + """Works like the regular :meth:`.Signer.unsign` but can also + validate the time. See the base docstring of the class for + the general behavior. If ``return_timestamp`` is ``True`` the + timestamp of the signature will be returned as an aware + :class:`datetime.datetime` object in UTC. + + .. versionchanged:: 2.0 + The timestamp is returned as a timezone-aware ``datetime`` + in UTC rather than a naive ``datetime`` assumed to be UTC. + """ + try: + result = super().unsign(signed_value) + sig_error = None + except BadSignature as e: + sig_error = e + result = e.payload or b"" + + sep = want_bytes(self.sep) + + # If there is no timestamp in the result there is something + # seriously wrong. In case there was a signature error, we raise + # that one directly, otherwise we have a weird situation in + # which we shouldn't have come except someone uses a time-based + # serializer on non-timestamp data, so catch that. + if sep not in result: + if sig_error: + raise sig_error + + raise BadTimeSignature("timestamp missing", payload=result) + + value, ts_bytes = result.rsplit(sep, 1) + ts_int: int | None = None + ts_dt: datetime | None = None + + try: + ts_int = bytes_to_int(base64_decode(ts_bytes)) + except Exception: + pass + + # Signature is *not* okay. Raise a proper error now that we have + # split the value and the timestamp. + if sig_error is not None: + if ts_int is not None: + try: + ts_dt = self.timestamp_to_datetime(ts_int) + except (ValueError, OSError, OverflowError) as exc: + # Windows raises OSError + # 32-bit raises OverflowError + raise BadTimeSignature( + "Malformed timestamp", payload=value + ) from exc + + raise BadTimeSignature(str(sig_error), payload=value, date_signed=ts_dt) + + # Signature was okay but the timestamp is actually not there or + # malformed. Should not happen, but we handle it anyway. + if ts_int is None: + raise BadTimeSignature("Malformed timestamp", payload=value) + + # Check timestamp is not older than max_age + if max_age is not None: + age = self.get_timestamp() - ts_int + + if age > max_age: + raise SignatureExpired( + f"Signature age {age} > {max_age} seconds", + payload=value, + date_signed=self.timestamp_to_datetime(ts_int), + ) + + if age < 0: + raise SignatureExpired( + f"Signature age {age} < 0 seconds", + payload=value, + date_signed=self.timestamp_to_datetime(ts_int), + ) + + if return_timestamp: + return value, self.timestamp_to_datetime(ts_int) + + return value + + def validate(self, signed_value: str | bytes, max_age: int | None = None) -> bool: + """Only validates the given signed value. Returns ``True`` if + the signature exists and is valid.""" + try: + self.unsign(signed_value, max_age=max_age) + return True + except BadSignature: + return False + + +class TimedSerializer(Serializer[_TSerialized]): + """Uses :class:`TimestampSigner` instead of the default + :class:`.Signer`. + """ + + default_signer: type[TimestampSigner] = TimestampSigner + + def iter_unsigners( + self, salt: str | bytes | None = None + ) -> cabc.Iterator[TimestampSigner]: + return t.cast("cabc.Iterator[TimestampSigner]", super().iter_unsigners(salt)) + + # TODO: Signature is incompatible because parameters were added + # before salt. + + def loads( # type: ignore[override] + self, + s: str | bytes, + max_age: int | None = None, + return_timestamp: bool = False, + salt: str | bytes | None = None, + ) -> t.Any: + """Reverse of :meth:`dumps`, raises :exc:`.BadSignature` if the + signature validation fails. If a ``max_age`` is provided it will + ensure the signature is not older than that time in seconds. In + case the signature is outdated, :exc:`.SignatureExpired` is + raised. All arguments are forwarded to the signer's + :meth:`~TimestampSigner.unsign` method. + """ + s = want_bytes(s) + last_exception = None + + for signer in self.iter_unsigners(salt): + try: + base64d, timestamp = signer.unsign( + s, max_age=max_age, return_timestamp=True + ) + payload = self.load_payload(base64d) + + if return_timestamp: + return payload, timestamp + + return payload + except SignatureExpired: + # The signature was unsigned successfully but was + # expired. Do not try the next signer. + raise + except BadSignature as err: + last_exception = err + + raise t.cast(BadSignature, last_exception) + + def loads_unsafe( # type: ignore[override] + self, + s: str | bytes, + max_age: int | None = None, + salt: str | bytes | None = None, + ) -> tuple[bool, t.Any]: + return self._loads_unsafe_impl(s, salt, load_kwargs={"max_age": max_age}) diff --git a/venv/lib/python3.12/site-packages/itsdangerous/url_safe.py b/venv/lib/python3.12/site-packages/itsdangerous/url_safe.py new file mode 100644 index 0000000..56a0793 --- /dev/null +++ b/venv/lib/python3.12/site-packages/itsdangerous/url_safe.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import typing as t +import zlib + +from ._json import _CompactJSON +from .encoding import base64_decode +from .encoding import base64_encode +from .exc import BadPayload +from .serializer import _PDataSerializer +from .serializer import Serializer +from .timed import TimedSerializer + + +class URLSafeSerializerMixin(Serializer[str]): + """Mixed in with a regular serializer it will attempt to zlib + compress the string to make it shorter if necessary. It will also + base64 encode the string so that it can safely be placed in a URL. + """ + + default_serializer: _PDataSerializer[str] = _CompactJSON + + def load_payload( + self, + payload: bytes, + *args: t.Any, + serializer: t.Any | None = None, + **kwargs: t.Any, + ) -> t.Any: + decompress = False + + if payload.startswith(b"."): + payload = payload[1:] + decompress = True + + try: + json = base64_decode(payload) + except Exception as e: + raise BadPayload( + "Could not base64 decode the payload because of an exception", + original_error=e, + ) from e + + if decompress: + try: + json = zlib.decompress(json) + except Exception as e: + raise BadPayload( + "Could not zlib decompress the payload before decoding the payload", + original_error=e, + ) from e + + return super().load_payload(json, *args, **kwargs) + + def dump_payload(self, obj: t.Any) -> bytes: + json = super().dump_payload(obj) + is_compressed = False + compressed = zlib.compress(json) + + if len(compressed) < (len(json) - 1): + json = compressed + is_compressed = True + + base64d = base64_encode(json) + + if is_compressed: + base64d = b"." + base64d + + return base64d + + +class URLSafeSerializer(URLSafeSerializerMixin, Serializer[str]): + """Works like :class:`.Serializer` but dumps and loads into a URL + safe string consisting of the upper and lowercase character of the + alphabet as well as ``'_'``, ``'-'`` and ``'.'``. + """ + + +class URLSafeTimedSerializer(URLSafeSerializerMixin, TimedSerializer[str]): + """Works like :class:`.TimedSerializer` but dumps and loads into a + URL safe string consisting of the upper and lowercase character of + the alphabet as well as ``'_'``, ``'-'`` and ``'.'``. + """ diff --git a/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/METADATA b/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/METADATA new file mode 100644 index 0000000..ffef2ff --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/METADATA @@ -0,0 +1,84 @@ +Metadata-Version: 2.4 +Name: Jinja2 +Version: 3.1.6 +Summary: A very fast and expressive template engine. +Maintainer-email: Pallets +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Text Processing :: Markup :: HTML +Classifier: Typing :: Typed +License-File: LICENSE.txt +Requires-Dist: MarkupSafe>=2.0 +Requires-Dist: Babel>=2.7 ; extra == "i18n" +Project-URL: Changes, https://jinja.palletsprojects.com/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://jinja.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Source, https://github.com/pallets/jinja/ +Provides-Extra: i18n + +# Jinja + +Jinja is a fast, expressive, extensible templating engine. Special +placeholders in the template allow writing code similar to Python +syntax. Then the template is passed data to render the final document. + +It includes: + +- Template inheritance and inclusion. +- Define and import macros within templates. +- HTML templates can use autoescaping to prevent XSS from untrusted + user input. +- A sandboxed environment can safely render untrusted templates. +- AsyncIO support for generating templates and calling async + functions. +- I18N support with Babel. +- Templates are compiled to optimized Python code just-in-time and + cached, or can be compiled ahead-of-time. +- Exceptions point to the correct line in templates to make debugging + easier. +- Extensible filters, tests, functions, and even syntax. + +Jinja's philosophy is that while application logic belongs in Python if +possible, it shouldn't make the template designer's job difficult by +restricting functionality too much. + + +## In A Nutshell + +```jinja +{% extends "base.html" %} +{% block title %}Members{% endblock %} +{% block content %} +

+{% endblock %} +``` + +## Donate + +The Pallets organization develops and supports Jinja and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, [please +donate today][]. + +[please donate today]: https://palletsprojects.com/donate + +## Contributing + +See our [detailed contributing documentation][contrib] for many ways to +contribute, including reporting issues, requesting features, asking or answering +questions, and making PRs. + +[contrib]: https://palletsprojects.com/contributing/ + diff --git a/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/RECORD b/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/RECORD new file mode 100644 index 0000000..ffa3866 --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/RECORD @@ -0,0 +1,57 @@ +jinja2-3.1.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +jinja2-3.1.6.dist-info/METADATA,sha256=aMVUj7Z8QTKhOJjZsx7FDGvqKr3ZFdkh8hQ1XDpkmcg,2871 +jinja2-3.1.6.dist-info/RECORD,, +jinja2-3.1.6.dist-info/WHEEL,sha256=_2ozNFCLWc93bK4WKHCO-eDUENDlo-dgc9cU3qokYO4,82 +jinja2-3.1.6.dist-info/entry_points.txt,sha256=OL85gYU1eD8cuPlikifFngXpeBjaxl6rIJ8KkC_3r-I,58 +jinja2-3.1.6.dist-info/licenses/LICENSE.txt,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475 +jinja2/__init__.py,sha256=xxepO9i7DHsqkQrgBEduLtfoz2QCuT6_gbL4XSN1hbU,1928 +jinja2/__pycache__/__init__.cpython-312.pyc,, +jinja2/__pycache__/_identifier.cpython-312.pyc,, +jinja2/__pycache__/async_utils.cpython-312.pyc,, +jinja2/__pycache__/bccache.cpython-312.pyc,, +jinja2/__pycache__/compiler.cpython-312.pyc,, +jinja2/__pycache__/constants.cpython-312.pyc,, +jinja2/__pycache__/debug.cpython-312.pyc,, +jinja2/__pycache__/defaults.cpython-312.pyc,, +jinja2/__pycache__/environment.cpython-312.pyc,, +jinja2/__pycache__/exceptions.cpython-312.pyc,, +jinja2/__pycache__/ext.cpython-312.pyc,, +jinja2/__pycache__/filters.cpython-312.pyc,, +jinja2/__pycache__/idtracking.cpython-312.pyc,, +jinja2/__pycache__/lexer.cpython-312.pyc,, +jinja2/__pycache__/loaders.cpython-312.pyc,, +jinja2/__pycache__/meta.cpython-312.pyc,, +jinja2/__pycache__/nativetypes.cpython-312.pyc,, +jinja2/__pycache__/nodes.cpython-312.pyc,, +jinja2/__pycache__/optimizer.cpython-312.pyc,, +jinja2/__pycache__/parser.cpython-312.pyc,, +jinja2/__pycache__/runtime.cpython-312.pyc,, +jinja2/__pycache__/sandbox.cpython-312.pyc,, +jinja2/__pycache__/tests.cpython-312.pyc,, +jinja2/__pycache__/utils.cpython-312.pyc,, +jinja2/__pycache__/visitor.cpython-312.pyc,, +jinja2/_identifier.py,sha256=_zYctNKzRqlk_murTNlzrju1FFJL7Va_Ijqqd7ii2lU,1958 +jinja2/async_utils.py,sha256=vK-PdsuorOMnWSnEkT3iUJRIkTnYgO2T6MnGxDgHI5o,2834 +jinja2/bccache.py,sha256=gh0qs9rulnXo0PhX5jTJy2UHzI8wFnQ63o_vw7nhzRg,14061 +jinja2/compiler.py,sha256=9RpCQl5X88BHllJiPsHPh295Hh0uApvwFJNQuutULeM,74131 +jinja2/constants.py,sha256=GMoFydBF_kdpaRKPoM5cl5MviquVRLVyZtfp5-16jg0,1433 +jinja2/debug.py,sha256=CnHqCDHd-BVGvti_8ZsTolnXNhA3ECsY-6n_2pwU8Hw,6297 +jinja2/defaults.py,sha256=boBcSw78h-lp20YbaXSJsqkAI2uN_mD_TtCydpeq5wU,1267 +jinja2/environment.py,sha256=9nhrP7Ch-NbGX00wvyr4yy-uhNHq2OCc60ggGrni_fk,61513 +jinja2/exceptions.py,sha256=ioHeHrWwCWNaXX1inHmHVblvc4haO7AXsjCp3GfWvx0,5071 +jinja2/ext.py,sha256=5PF5eHfh8mXAIxXHHRB2xXbXohi8pE3nHSOxa66uS7E,31875 +jinja2/filters.py,sha256=PQ_Egd9n9jSgtnGQYyF4K5j2nYwhUIulhPnyimkdr-k,55212 +jinja2/idtracking.py,sha256=-ll5lIp73pML3ErUYiIJj7tdmWxcH_IlDv3yA_hiZYo,10555 +jinja2/lexer.py,sha256=LYiYio6br-Tep9nPcupWXsPEtjluw3p1mU-lNBVRUfk,29786 +jinja2/loaders.py,sha256=wIrnxjvcbqh5VwW28NSkfotiDq8qNCxIOSFbGUiSLB4,24055 +jinja2/meta.py,sha256=OTDPkaFvU2Hgvx-6akz7154F8BIWaRmvJcBFvwopHww,4397 +jinja2/nativetypes.py,sha256=7GIGALVJgdyL80oZJdQUaUfwSt5q2lSSZbXt0dNf_M4,4210 +jinja2/nodes.py,sha256=m1Duzcr6qhZI8JQ6VyJgUNinjAf5bQzijSmDnMsvUx8,34579 +jinja2/optimizer.py,sha256=rJnCRlQ7pZsEEmMhsQDgC_pKyDHxP5TPS6zVPGsgcu8,1651 +jinja2/parser.py,sha256=lLOFy3sEmHc5IaEHRiH1sQVnId2moUQzhyeJZTtdY30,40383 +jinja2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +jinja2/runtime.py,sha256=gDk-GvdriJXqgsGbHgrcKTP0Yp6zPXzhzrIpCFH3jAU,34249 +jinja2/sandbox.py,sha256=Mw2aitlY2I8la7FYhcX2YG9BtUYcLnD0Gh3d29cDWrY,15009 +jinja2/tests.py,sha256=VLsBhVFnWg-PxSBz1MhRnNWgP1ovXk3neO1FLQMeC9Q,5926 +jinja2/utils.py,sha256=rRp3o9e7ZKS4fyrWRbELyLcpuGVTFcnooaOa1qx_FIk,24129 +jinja2/visitor.py,sha256=EcnL1PIwf_4RVCOMxsRNuR8AXHbS1qfAdMOE2ngKJz4,3557 diff --git a/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/WHEEL b/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/WHEEL new file mode 100644 index 0000000..23d2d7e --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.11.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/entry_points.txt b/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/entry_points.txt new file mode 100644 index 0000000..abc3eae --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[babel.extractors] +jinja2=jinja2.ext:babel_extract[i18n] + diff --git a/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/licenses/LICENSE.txt b/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..c37cae4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/licenses/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright 2007 Pallets + +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/venv/lib/python3.12/site-packages/jinja2/__init__.py b/venv/lib/python3.12/site-packages/jinja2/__init__.py new file mode 100644 index 0000000..1a423a3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2/__init__.py @@ -0,0 +1,38 @@ +"""Jinja is a template engine written in pure Python. It provides a +non-XML syntax that supports inline expressions and an optional +sandboxed environment. +""" + +from .bccache import BytecodeCache as BytecodeCache +from .bccache import FileSystemBytecodeCache as FileSystemBytecodeCache +from .bccache import MemcachedBytecodeCache as MemcachedBytecodeCache +from .environment import Environment as Environment +from .environment import Template as Template +from .exceptions import TemplateAssertionError as TemplateAssertionError +from .exceptions import TemplateError as TemplateError +from .exceptions import TemplateNotFound as TemplateNotFound +from .exceptions import TemplateRuntimeError as TemplateRuntimeError +from .exceptions import TemplatesNotFound as TemplatesNotFound +from .exceptions import TemplateSyntaxError as TemplateSyntaxError +from .exceptions import UndefinedError as UndefinedError +from .loaders import BaseLoader as BaseLoader +from .loaders import ChoiceLoader as ChoiceLoader +from .loaders import DictLoader as DictLoader +from .loaders import FileSystemLoader as FileSystemLoader +from .loaders import FunctionLoader as FunctionLoader +from .loaders import ModuleLoader as ModuleLoader +from .loaders import PackageLoader as PackageLoader +from .loaders import PrefixLoader as PrefixLoader +from .runtime import ChainableUndefined as ChainableUndefined +from .runtime import DebugUndefined as DebugUndefined +from .runtime import make_logging_undefined as make_logging_undefined +from .runtime import StrictUndefined as StrictUndefined +from .runtime import Undefined as Undefined +from .utils import clear_caches as clear_caches +from .utils import is_undefined as is_undefined +from .utils import pass_context as pass_context +from .utils import pass_environment as pass_environment +from .utils import pass_eval_context as pass_eval_context +from .utils import select_autoescape as select_autoescape + +__version__ = "3.1.6" diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41729a9373ad38d1524019b2b492a7fcd7a11ce6 GIT binary patch literal 1637 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/async_utils.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/async_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e7c6eb7785c3cfc0de921121c9162f97a54774d GIT binary patch literal 4958 zcmb_fZ%kX)6~FiWvyH(93^)Xm8sau_|KNl|NVBw!(@~bDWocogbyPXV?~#}qo8I?; zK+TjUXgF23rrTObRV`$yRH9^3KIY4$b(Qv|`(S1y*eji=mPzY3w?tE$?!(S`&(DU0 zF59#VzW3aF&pr3PbARX0{iLFzj6nJ1_kTH6?Ih%{*s+Q+1D&-Kl#m%B6PYGR*lcN- zgFctwhlH>|af}CA42wVuvY3#DY+;*0OF-Mhc1jd(u-Xz`YMEfjEekt2v zYAvSowlsfDx?0f7x^a@`WZN~ng^~=(h#4t^7;{Y7uNIbD?WAyCVouF#NlgpIy#%=| zEkw#oGjMIrZe577ld3K;MH^ws4VNeat8s}(fwd|nT^ z!gVw31O%OGx)6)1Q1_=&3Edu7BT;oc8M{Imcod={XnePy37$#~DZyAO(LWxHor-GO zcyLrnjs_F){^0Pqb}E&8v}I3gP>pNKBg4_yE76mR8XSx#2cxY)LtbP=izn2U;cL%5~)};p+38G@5lL8UuEUvZX74k<~^ zPy)mIH5JTiknMlfOasc<0o5j&=~(c0Ecp*E_z&j1p=@P{A?5?TUKWWArR0%>5{ZB} ziX@{$NR>w-uZ~0$W)J-oxTg5Yq#|DdFT-#j-8M2TM>R!`L8n*{t@pui?P;jq5oQQS z&I@T$gg07z<18 z2U+TqPSx#Ky{o{W9D z(LXO_8z0Wv>sOtgU!Gp_KC}S;&WG{>t=RkitCvPDj^qiD*J^HIlP{wchiGa15WQBj z1`~&9-bEUprdj)zmBywIb}T;dSgv{hQgi1*b7%JHzFhOMT;q$d$xVkV^IG=)y^D^H z6(A?F{g@}Fk@S^?^t%% zWn9K3nlR-IC9xL#Omwg(d?wT?HYkP#VG6y2ijDywbc=gH^yZIJQeK-CY7BY02noT6 z!Z2iPaA$ba=is;Y2dIt^y1}KDOin8}dqPGDtJBbujIvo^kSVMy@b?hSQRFQ~Zfk^) z(hh9aY^ibZ13h2mz&fwwYTK6FZCQKUs7ZKjMf;AGPzg5yRRCs!z@s*VVRYeQ6k(*;#Y3_$!Zj2Q6s z72pd2BA$$Ek;p{TckSL{u{;A?s0g7mWVya6BmA^rA9n#6GYjL0==vKdR01bt~DVqPyDGEH#;c9eW}F4K@4>yqR&*(^=AB3cjG zK}uPd9}?k7s^NA#HA#O=$LJV2&A&v(D4K!s^#M^AlJEv#-vdc{ zMUR#mHZM4yO6t|vU3cj)W}qBmGipnx%7$x0jiIc2Y0TuoL{r5JFw7y(@s9Qtk9StM zZRBiuPnWpoe{hGn(C(H{b_ds)65rAlB!VnCDpTlvM(bkn4_OJsH5=7G3i? z{1h{%65cQ$ItWrAXSbb-su;9QZQw`f0$K(pY}lgvTyHrA$A?qwS>QefKlNd#Ao57W z?#`T^ZCJG3zwGudxtkW;O*!|DtbNB#_s(m={EJszId@0a-f_19q1~Q?-&!A3-yMls z$y+qWog^{tQ7Z}vW)#4e=G)wxI0c7~X?Yrj1+_sh_XK>LOoTYDsvd-Ywe{u%1Lhu`CzES7wx_GIBpNcdO`q>1b!wh&mtJK` zh=2(zNfjWf>YS$NwqA&deNkrG0p=1qA5Ut!IFLw1H8WY{K^MA!D$xZw9@E%PJaQ{m zkKzK+fV}P~hFc>d>b5Q=sjzs=Fd{?iy6~)}*E_83w*7$lf_Q?R0Fmh2)Q_P8S3v2f z#Punu{)_}ZBMpBe4WAMJDlbewdG^Uqh2~YE;!J4z@au;&v2)#L=vPvJ*5+TN#0$Z7 z&Ogu3_g-(iQTvhbyWWq>vb9IDq&hFzY1M4#QrE?cnn z!0Zim4VznaPTD^E>ZS3EE{>(@kR^VK}vz3lAB^U$t} U(sbYJeVM0TKYmMuzJc|B0qK`eKL7v# literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/bccache.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/bccache.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2bacdfa2b3b29b73d0c2459b53f64fe19dabf1cc GIT binary patch literal 19329 zcmeHveQ;FAwdcJvcfMydBaOZg7%oU4je!v#w!s4I0D=Gp;TZgEYmAusq`*io|)2Gi@>mLFEuYl(tZvAfXe?27#|DArA zkJETr{!gMHObbb&S4fIUTUzWDS#ImKvE1HkXSviXvE0$?V7ar`$#Pe(i{v5!aMGD{rR#?qdK*N+ zCVX8;y5A6z9wj`qrC8g1yJ_cdy-i7PZ*$UT6BNhL4)bdPM?9N9zbxrUx7 zwld$6;wro*1LNpTsb0fLHO5`2Rn|}MYBqx!%php?-@AsDg-}*&l|9DF>QEL|9@M8Q<{wb z^O`b{Ps^zRc`Tck2jk}y`JAGTc@x>;k+gDNM&*%oJcov}ZDIt8#o~B%Mm|Cb?`@9?mBQqh9Z+BJj~cMFoWN z;l)vu9mpvec_6EvO=Zr=qe_1%Fbyr@IWN^qWm36RJe?X>k}?KH106`IT25A#SM!RN zi+Vq!73_(Kx}*)pwb8$(0n|` z%km3&nHCfQ`$V8UqS%u*MH-Nj_BW(nhX<6(${dQ6bSloF8WTVf-gY9?V2t_6mOepx~?EsO~_#GV^M#D*y< z=>e5?y+-wA`TpU~!R)ZonaHO5$2t?hT5hcKoRT@$nNIb0j*MXiGdrW(cXVRGm8~Q3 z#M$^6Me7_&WrpHAI{OoBJkgOc-F`;Njn`KWF^bYPG!W|TJHj$jp+|R|i>LF7?#iWx zm25tjpi!1SWkXj3_@ok=B9a=UcN)H0Pe}t3jatii)DT{DyH<4^C|ygH9Y`(;cf*Zp z7;g-A#H+hwu}pkeiN$npEH<1?=F{}?_@# z%H-500!bs~z-c@x(FIFZ<3&4&({iZTHsMC+FQ;3r^v%cuc%!w|SZtn_3Di zZ|&gu&L@7+_4BUx6F+~J{k|KhU2&iuhfknCqY1IK9NO_2A^EdrBxRK8Mgcv`e~qM+ zGQqiRNqbTPB{FJExO5} z3;^4@$YY|id^Fdt$q7{fn@W0#Fo;2wvVs0F#s(@_A=Mg*YerYZC(_w?l9 zY0q&kj$RUk%6LEE8X!{{?86i~0mqIq2)YkD-(+tp4l2IcbhkMz)rYd4vJFc_$2M#a zUZ#Z!aYBgUjXx7M{36!Lo(VgCQGNbP7@{63o}0#R5nPLJPf6W~GQR6K;Z1j_3bwP! z4az@Oa06^?TKQOPJje+uY8LDQByByCi^AgSO_S0cck^PnaZW{w z)Dh=pY6o7&{l>uAr1zmjqkRt^_IPws`hkayP8SEt_&+fPwkCssMr$%uI(&SjCr35d zlOq<&o|{HJGrwS)uob_a1=7^oi7#6>F=rlz!VnWMP3SzI5H0ASharFYSNb(!|8slt z2st_u*z@SVKzSx36vo6>AxCS+o}9(Ja*bx$fV~Y&Tok@+uCD;rsBqr?iZDvc-g%s@ zu*F#z6Np3LNJC&zd7e(4#gBg4<5+yZE`d`eb$eRL=k2sj!uK^=ybn8D4tRxI-nIkV?YFjxNUQIG-=Qw{ zfG~u-^10|M?t-Mvq|4Vu>=@cZ6;IAoiUhN!D0~U6SEp|%K&JQ(2#==Vs^i8UGWaO? zq!^LL_jKonN6L>ioJm<$@-XrEHbX*@-uF~inhIV7W1Q@35wX@ zHuY%$SBb-#sCfqOWvHn)_g;(`MvRYie6@j~Y9GHzAc$`PO>X~!yJgPZ^7gLTr+)P8 zC+^J;9$3Wb*?I6XUZ#bdHBIxhy?`TZ$T|;D$#{xW1~06-Crk*ZpdF1r_5#yI%IPq! zG1O?$h@GK9r9(`2LvmtjfPod=4#A`hD|Obyb6FE7t159~ed+^Htrf{d;rBHS-%m~2 zaeyzlSI@au&m6qtmKTF{S0*k^eE+rE?#CE{ZrHdm6A7v$GOHx5sx3%#S1gvyCSoy_ zM0Pbo37y31WAxE|AeD)$V@FT2b=7TnAq9s`O8pwWbyHHI1+-BaB~kp-UPJPRaL?v+ zxCr`4EhR~T2qXi^nn%LsiZgE|4S2a~Q8LmHpah{zhxNb#LjpTU zc98h*K>Me&a*C`Rq}p@y1lcJ}A~a-X%Pa!ksu_|t$Q`B=CUkpAKOhl53$uxuf}N#n zSji1`^_3l_eXNS{h1jRX75gCGAIRngVcuY%AU{G^AP`wdjwHYvDoR}LVjXt%y~cI% zsHuzd9nxp$D*KuJM(&SmsYK;m$VMV-ugwW}56zCrG+9`ShDWk$ z4&%uU7C*8v$tT7bRy)dY94Uc8aFWV^YzpHYWR}<>CsQhTj~p&C)Eg1oO- zx!60^C$MG6HemNNg{=x;Ivq*H2S}PrbSRt3ur|fswiu^j6!wz-YC{hnf3$pR1Ca+OUq(;=&nI7XI-`%C zjxm+;gTKR^%YJ#7wETGTTs)J2`iqUEnI=UhL35HMlE&AH?TLLj5(iZp+vgaWsSMTD z$-6Z)SW62953fDpHYGy^tx|DlOc0A(bYn~( zdi($#lw)jLXR@#(^kc)J^*{;?gc)g5++fKSdop#Peg&K@aw-SADGZtfA_-$c21Ywb zI(*thIyXc;+>^~6HJO`|WZD4^KxR&R4zn&jc-g@Q>7$fEmebuEXt7LYxr@SzR|syH zuW7yQZslUfS4ZrxzXDA=3Fv|WDmz+XNr&Z#x+}xFaT-z&7ShmDobA3;BA-V65>rwUS3O)ZA}>ver9*eH zF2=*ML?A4UNBupVc68^2N8YEK)HsL^6wm-_a1!37fzLN{fd>QkZ6xV_gUb123O0JnK=vZ4seeIr zB?4cm{6h|pu^kdazm-&4_HhO8CdjWm@ zpDbgwMhR))Fk-ONZX*>~TSBFUESi>8b{3MI&;-V97hYOAKK}9M z2N#upc^A#JuOoqptX>F)7i#3W8hN&U_WXR!HmIZFriJk4x$x%ehv&mPC*4c6O*6jP zQ`bA@Yj;jMnX*b+{LE8#+#42y4U++W7L=Z+M2qxqnT&W3U1^J}G1HK3eC_i%zvvt^ z7QnI#@pBOdXq7hFg9n%wChUjIy7=W6XsVHmsKgR6^OR|JQ4bMh5)~q$3N>3jOrNav z;bS244_CWM*;dyg(H$>lApe!DX@Wi}lvHThWH(YNIfH-N-y(vO>)RMawmk!(XToo;?U}oW~(A);ASc-rDwV*E?O;<3Brd zkAK;LiiJj-WB2t|Qnq)$*GkIv?t;6<(RjCEL&1R@7Smu|!9{OwAzVNG_|)Swr=~g! z9xCw)wem`W% z6dFO@pkoytOgi|>q0@-AkaqrJ&(W6+%^Y(oR$`z4-*A2!LIj5h7bSykXiw$POB|af zHeeAZ=4xVrX*|Q2PYM>z!88@0YZb55#)z!TC3nZ`+Y;?x77#TflF&pac;2m(F+A>`N5 z-kBR7;js%`bN#tt&_KoTY@L4Zqf40(+c4#vbJWiE#E z6RAh{SXj_q#>ANtS|+*@o43@pB<2znThngTp^2nl2yb}(868jS5+md*dK@CKJEg2>*GE{H`-^^i3; z)HEIo7bQN*A*cDZm?v##VZc=(z@IAd*A)Gl@WvR>HJ1%`ZWAs-(xjpwRE?3EHAykJ zU@t)jVwnVQrX_Y&3%i(*ieAfVwpDW$qEH5j?9&y`pAV^IM06TgJQF1% z%uDtKE2?0 z&GDvqInCcjEuVWuPaXevdb<2imHkAVJ&Drc5vw9%U8Wh7s;EVq0*r>Xp7CP`;~8d{ zQy^murx2;6#Rqt}QrYSF3c@Wj3OB+i=P4>OMW&|_HH%0e#0zAUD54MjFXS^6G{@|B z71FQni{&!}JO=hYe()h@V6^&i5Z#;3BKk;sCRzey0dq5)j7xQE3pTNK$FkiXt|UB3s9wbTWq*T!l3|F^h zHKvs5E|@4FkEyDPwz`D#a!{AZB#-!ovs6>ka`Bw*Iu$$m!b>l|sJl*{V#v@P@V8{L zy7SPXmye!04TI7k;`nq&npKDMH-;gyW~aK3zM#8MfbWPw1fUZqyAMTN{1hCaZk-Sl zs4o{1W^VGv69{)r6J~W$(SyaGa7@N?8j0bC#>h|2G|fEv)z;v@p^bJ0?Cc`KB_&VI zieIQ}S*UHBt8JUF-8AWh&Bb4HW!I%$Zzlip*~Rdx$rDS##<$d&-FJd*zwtL+K5@5x z!%ud+`}8|c|9H>r*4uU4CS6MnP17$;y)^yJsc+71y52tDuw&9o5f_b1fOO>2kvDsP zQvdFnch=nTM+~0>+*2?j;zH;J}?Zxv*yE z+?t*9!6$wx{mTD=|KniyKeE{eSKW56`eJ3f5Ne{Zk;cVf=*sA&(YH5V+j4cw$H7gD zp@u)&g;4vSD3r4CPbL+M}ZV??ngRnJJ;VS?jxi2cChHKh;E=Y9=fQgjIwal#;=s;(^7p z%5gg&jst=zCY&-fbEld|Yju>8^ORVkAh(P>VA&2cCc?IPjJ7iEk@3xsr1tX44p6!F zIJ&whECp+6mDtjDU+cNrbG`d#$8Q{e|M0JleQ@mkeRErm%mz;0%U7!U{9e`Y%hQN#fxnGK5JK{;zh zSRk2Q#342&n1N!g!gd(HM{QIp;=eH~fUHNisr?bB`h9@ZCGryLwS-o9Zw_bk8Sb~y zYmaLCyG>Uq=YGNG>2yJiX*R>p24?ay;-#MhG-d!+Sx^p*mzWjjR8(<%Li1w;m@XK?4dA9bBzx8uFiV9Tz z#fnd;-EgP%iT{|r9X$L8!R`s)a|(fQ!G*W`pL@-E_cf9Ye(2vTyi>Qo(f-T&{bBou zbt0vWj{WOgWqXVmF{6q9iLri9`g5qFNE1WWN&<@UB*o&YR@-Mo)hNaaB>R%KWEZP= zCaks;oBdPirlAo0C2k8$N&_N-FZ>tm9^sN?P;3>Y;*uqlKS;6I1E$SL7$UDq#dArj zrdVK9hJwUtzMOt9Z{`7z*b8I`WDmqX58cec?lDS4;q{UoP$nRHD{Inj`Ey}KT-Y_E z{o_&;r}p89LzmdWZl|-_Iie~9sq?y1%MY+T3CjyEz!*bf87(>!<&t>$*WxtkPR528 zgleq$l2DCMicEqr1&dJw5f$6tjA|yPOSdNZE9VJTEM?V0k-tiQJD>ZVUZ>%UkEw_q~{8n78_ z8?YGz4fi#Q(ft?eT7@5bI_>{K?6fnpZp6>{l&ayQUe|*&AbA?%TvkPR6b-TDRQWDd zag)cMjtU+C7C3;PhfHs(?lKHKC75s&{nNbkM*zdIL%V`_*w;7{Bm4EknOGTKng{`R zP@fP$UaW;+`&_X7x^q6*IVmmq>lgg(bN=?lT80_dlxsRT6`Yk2<+>DZnBFzDYa!e^ z7jB*Ho)1SLge``fSA5QZcT)Ug$mwY)gdCo_#o9V>zCdI#)VL7ZI2YQ8z{F4oIA0)A z@Vh;ABzz%A_RUwP&rO}1$$jE)`#ebBsLh{#A6!GyOWkk$t1h2h^w(0rYu%q93zAf0 z_pBiv+48SWE>){`UwaA@`!H%duv_?WTit;z_FFbL-f#Kjg8|{z7RP}nUAMNosAPBD zL9hL90-=N3?0>Va7HOH&*fh`Jf$HhNkSiB%2TD54QuxK2e8c)g2v5r9V8{)dF z#)F$X2VaTKDxRdhRAixe^lsr>I|YH5AlYh4#3=FxR6Ij;Q>WsoK8m7y%p8v>Tz*pG zN4&)-&E0(YfO(ilq#l)cBlhiIvrHEsk9axlQ-SUp$l&fqVo-Muq|#6Vbypg3Lf}p% zVpdV~Vl2Qow^?%sTH(eK`EScTw<`Hc#vgkaGhmj;eMIb}(p(h&=oUO{e;;a^u9>QN zTct?C>uo=a-iXexd;0w+ezo_5y&r9w+w=T<=o<*;Zhq_!g5+tKJW{Y>`~M~cEn|9g zYLtoj-S76i)AQbu`KEoJgr2$QK(#McoI?HTKY)5swY~r{2ge|ceHhqwK(O5sJO})a zTRssfX~eY;n0q-lsj2E8vfMx=!I-36amNwKNzA)f;ytSH2@O&eN`8rd+UrQjClrKF zs7=@}o`$E#=~%b8s^wbC)t1?VSJxHT@2-6;zv~GsJMg^_+UjVzZeJFV6}~Nsj$`8W zP1vu<-#fL;@`5wKT};Tx#e}S|w#`vTUMFN!gly%w=yNo1XATXj>?HD_fSzSC%T_vb zJUExgp_4Q&YvBrOmEEGaSPZ2LF_Uza3s(mbhv`jvnd62WHNKMH>{1pSHZ^*qb+fSC zU(*go!0h2XuFnbqFMD!Cx%ZqNQ@hr#j2eIh~wu)LSzo%PB{gZB=S}yhvJztbP)^@3MKQz z;0f_1v+7q8ajYd`qZlY_Z45t%wUYfT7FvdLK2DA)>P9)Ai^^opA_vl7c2t3IgQy^_ zh-m0BikcoHO5h_&u-mK@enf+;psUfrRAP{PYw>dk%A(LMHY5Z~_VMWeovOn9Mn)Rr z0Q;hrN2l8A0y*4;!+S5k`j^1P=A4?RU{vaxB5{nRjYf@??vhV%is6@WkJ1f5BAa4F zCaQ8zP*)c`X1NZysQhWr!$^0wli}1JTEDVmd7mAi>Z= z+l-z*1AHHwqVdCYT9M0)M<4S0w*-j@z}OPykcy5>3yMm9PxF@*Qik|d+;m8(j_J!i;Xq%k@X;|*OOn$f@zu{QZa6>&~T$E!JQZ_a6YmEqeyQ8$n_UCMlcMD3?pL1)pnXKZHi$`AKAQSQ*CSEDNYqvw6%C$S_;?vC2x!tQzB0`zX>8x(qhG>F0HFw!?3H z{~H|P?0UN1SRFuF2p0*Eb~^d@vXlQS+@Y1=1E!YjF}=Q$dL6%2YYgfQ{EQgtRFzNc zRyeBlWe&jfT2g#2u+W{z3W?ST*ET*@?Q2YZrVF3ePRR# zGkYladmE+%He}Mi+06eIFQj`zI%glslWIYis7b9W9vD8W=q2N*brS=o2Rei}WXf8+ zh^0l-i(%Na45*O$@h3^D>n9rXP!)}PT)TJi6(H?{F9X3+zlKEjSUN9X7Q_9{jC7k( zq1^q>%yVrIS&B02+#9qWF9QH6_D)X}COW!)IsNR^v$IdUyXT!f?=}9S<>xJkU78Q? zo$N;3Q6S89-RsgP{;glEc!ba@Za637Ui9N|6c+P)cA>tL8O-l%G@I9V>~{(uIve+| zvwyf&L|SHkBi)d#<~9t58Q)M)`2hQ+6kvR&6=Ex6>6gWJrU{93KCD8O_5Dr<%H72l z=39019Nj~&2E`0=r1k%5@XF0&IZHCPD5r{UTAgy-CG^sbX)q?jsi}#6{*)57pG&Q~ zjJ)dcsX|9&hLMMnPnlih2Sh&q91vjKt*N_m_R`sz!`F^oJvLXf39*Z0$%xE_BJ-im zla7MqBJT=w$J`FKFNW%;eN#SWC$Z1I$nE!^gr2@zUj9jF^A}6O=0{Q(&G6>V{dlErDFUWLJWQz5JZL(5PDcV&wMKSvuc$gwfUPd|XR~R3o39POOVq{+ zjqzRjO38DC5>hrvV|*MMqtRr(b#g zQ0(B5LkFKb+Vge2mN$<V7LU|5j-HtP1PoQ*V$adT&Hp~pK2$ZjUTRI_%jWgf+qd+-Z=Kl?)9Gpx5 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/compiler.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/compiler.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6f5139bea0f3e41d800cd6bbe77ac82c4a141d9 GIT binary patch literal 104044 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

Lt! z&saZp+;&XAI(sr)u;rd@>k7+~7Zv?7%MwGOnMW9*=!U*1Y2GuEne0A(Ox=;R(uhR1 zCF4KdzyFL{nrLKcDJi;ZD+-;A*vkLhR>U!##UJnuC#)NU+8BCUx!_~QAV`1hddgJD zTqpQgqLNe6P#yaP5|p)T9wVH9yZ;_9f_aIfMjDR)n_l7DBeqJp@LZ!ti<*@+?+TJd zqw7)vS8_0^wO;|2E+9}vWgjaU*cUx0{WKYpP5Rzr3D!Q}>+6Tcxael9827fPE`)sc zcv~N*j54^#HZOVB8x$JQUbL#Q$(k?~X)#NjigvIMV18Hz!J@>L)I|MrYRwJ!8biVE zdVA;O&al0L^i^6XTVdqie$TP$f%N3N*?*%y^tG9-;gXHB!ac_hKDvn}mM>+H?8Teq z8v1o|jHcVrvW%0O8N}qUY}&yVZk^^(wb+(h+A%IsOuDGSps2aAjoA&riUE9YH}v#~z|u62d$ly+Rzpce zY636ORitUm9k3XYqHM=yLYTXhW5bJT25Oyijd6#cbz3)qXfie%&OmGd18vsVbzIeOc zM-nZj*z0s^N4l7jj=cPLxrLE|0gULEu;8!V?&h<<6`1~|mk5$30q6raEyrYsNviFm zs<}tpLWbYeowSy7-yso%`XQ0_oypLxQx;k($;x8mblyEzJq)cg8h-v0X9*c$XZ^_GT7>0w)?+`BWpAu%GW<^3 zHuIcun>lX0oUq;~^XPApDVs<5Ao&vi9eP!ZfFuC_H}RfQNV@b0R1%~SFO+-*K}@Uj zi}8;-8<)#`%Xvzr1b|9UP9Mt7Tk&3pJk@hkqn`ra>XfNRgBO@+PGJj}m=NvBFMO?( zP=067CE`i=*QW>#RoKKf0{N@4F8|`GYcU9D+JGVSR9eLu?rU2>`QI zdJUIF-p9uD)`LL{%_Am3gi7;&MA^jui*D_7WB5N#q5E|E3*6!RY6lFB|U49>Urd}Gf-PC+cE=59`nL|w*Ql@V9vbj?ie#}&6LB2`Xac#Kgan;=HJe&X7RSnjGw?y8yQSmXXk1ggV3r*|bT_<J>C=2yW2V(VGBluswHR{;5 zP+kEU+X=#{%rvqn0t&D4o~@LxSyl)-#V-AI>nT+3m$h!ORjCqMlW-Hrl_1GogzpmZ z?aV}nwJDY$Q#@6A&6-|DS=2AwwSj}$oklC`?TrX9ESD@qW9d=q^8{bU`iZSNWE29o zMVq3wN@dM?D}z%o+QpZq3Ozsk1KbkCX1a(5$iqa6JMvyU66S%f%X~7CpicZxbfYOG6P-j5h^$X2Q<{LROW?I+3E3`X8K(|824G0>ipxU6 z&7m7ZGcV6>x;;8yyp2h5?ar{h1lrLsr-0`4Yqwv!>)yRk>mk<)G~lZi%ByZ(xN!ky z-X|8VhRQvF=A+rcMsp|kJ&`b|2|2Rf{5|x7wl@Xin}&eWUd%6(ZcnM6l*Y`Ljebx> zfo}#^wC_|xTI_bi$l9ouRw_q2`@6#U#QeG)vIj@u@ zd6{2dGiKIP$d_e1$P=_ObD)$u4liB$wH!+YJCrhJgCJE?!#l)@#f%WfY5$ug;>z_>;7xF2JK;Q}Z3nTBdAiD$KI|a~xw~nf@6|=~MRK z)Dpn`ZMs&RioEq|ouX$_nNaIxdPXht)f4EAUrK+Yw^3^9ZMdji+TVKZzGs;>wX5Y@ zrslf+_yub2P*WaVZgyrQ!06`mx_6n9XR6 zAd>-NkvUPM$kb&I4dm!e4*$#9rLA5(@4f zZdD@r$t=LJ&F9l`g!gx}xS~SbQQYSSb4B_CSJor5A*1{|T{nAf^t|6TQ~B;-%+d5S zGvXvqaZPIyUE@)`SUb^LuCx}q{%+!Zu8x>p6$aa%>>qT!XG zmBOz)Hf?tYt>YACa-zq^@t}pCJa!l;!$&-kW!!M=saum0?O&A^QqzI@2 zAS9hpfCFyey&UmiK&TTq#kz_@S%`|e7+fcgzPQjOl9x2(C7z30*=tex+D~f^n__fh z1QlFnlife{dNQS}MEscACmnqU-)E(U(u$u^@BbvP7|k=Y@#7u0cSK9J%$45VcJ#CC z`MhI~tcZPRFqfu>nM)h+9ow#L``lnktDaCDTWj1{`$H}F994@}0FYGPy--wkv*ZT2 zc~gdG%Bt}CQN&1m|EB5Md#(n2k>SC+a>Qhm0U=1Mye8zOhS|x{XLY4{YLKzdU|zeA*Xv zH2$T%M6#m`JDHH4Bo8~0{{yKsTcWONrOf7xO0~?so4q%BLuY2H!X-^nmsctN%^f#( z%vd#VHiB7OOvSh0By_8@Ik7s`(*-l8nN2hGv)SQ_jrVeKDu6L-GwtQW+)qp($R19nb!hBFjj;}@fja=C2+*I&E#+Kly{!wbqdQ^!b< zqz{Y`{&NfzEg>wEaO2qe0vU%*fiLRH5DOYjzN8BtuXNr*ahl|BI!q7Xb>6~pnYqW* zY?v~hfg;3*B*3y{O( z>iW;!yoNXvqJK#?RuLs1ZBrFa~OGz9;cVlQbez+|0& zB$;^%-0LOINAY>x%m*ikDkTx-l7zD0nZl}&eC_qRFZx7sF4TRVJi`jxx4SoZ*DCR}T=BAT2SouCj+Fz2AcsW> zxdQ~P!Ez_EQ}v}psU($wMC(#}yQyp{eaC!?FwMNjmVh|AJ?Mmvh^9Fq8+_rh@l4Rg z?P1+VeWT3J_V!@LUgmlp&UCs#89%l_ug)DzZ&)=lNWPam+k+L%qSh^|HJ#$k!oF^( zY8`U7{`4PlJx*nPE4YJcAi1%od#gNH{=bJw%_Z-oFvtoRPw`_n#wT#)6(*X0L~*pm z31{AnU?7C0dOXE8ya12z!^{f-DILl#aF{NYjzjOP;Sn6gp}_U}{K1Mu;&BN;G1dY9 zi!xy%u$?AyDKoojct#(|)Ue`fIb>!xg)uu00f?gE|yP`xG^0HEoCuBpQHLZ>`v66fTZ%3eICQaE*Gd zMX+#WkZBLYwObdw0l?S4IKtTT$LRnZ=!G`#;5m4*`Ay^j7pVa9Gj2kp1y!V)a^)Kr zT>L|&7k&vZ`q;5qBDJDO3m|zPH^g&V6I^;TloYr0L0L{d1dMnNxLJgfNDuU#p#Xp+ zvEji=6Sp!&V)ZC6iN(MXJy@2w2hU_h0Zr6lLKr_n`tWp-l{1mDP~Z5$_^t6+{q{)x z_E`PiNd4aV`u(39($fwaBaZ5cgV#<>Jrg>;;LM8_t-kAAjj6SeRS+#%eK(83Z2r|v z7tZ9&w8xtEN1FD}H#LVFTkf2^GjgZ*vzNmaC-1pV;fwF=zMJJq{NVJ=A)>2Yn^=NP zce9!nT=|58LOu6fwLi)CgjY4s=eLAiEsJ?noS)@Gb6MW2^fB)d`Say#!)5Dci^C;5 z?>TnSc78S&CTG#=Ec5Mtc%Ad zxG7THM4QKTQz_b>1e(pL`>``m?)_SFlrrs=?9lW(1boT-HOuBG7dkdR^Azhb!n8VMHNlDG}s%?HVOAatJ&0wlQl6SjN$?MkyL8QhCnNqf{r#F_m`}rjd!$l zFKxFiWar1SaR5X0e0FU(t8QAD**H@dcCHKC*6|Wk3FS-SOaBH#oTihWwv1e2td;s_ zI_ga%#k>aRHYn3a-Dw^-3&wGa%1C3-BMk5wOKv^x( zDurqqatIq)7zT8Wr-3p^L#;Z-EU(u?Psi9-%wx27tn;)(%dVB%If!vWT(!|Kxav#Q z7~5{imwMWtT03k@nL8BP$vG;Vds2=h)nu1olXA5El5$kGs*-Y4 zMu3nuX6nqB^Z#n|3RKk^IR*6>Z`Y|$a!5#*OM->TE;#x&$T8|Kj(L*s6<{*n!)B^H zo|vRylL{$^LRY9YP^Uig6qFZ_~b<9ND$i$k)K zjuvxjsc1m1*j~%d$k?NZxS4vpE-!V=zKy|hP;QGdsj(ZeA2 zsWEa$9SK+e0A!P)?|>Ym{;DvDC&_-&rH|E;N_QyfBRr0ogp`kR6Y6ke8K}Gjs1@r$ zI-)%wG@y-0)@JO&!7y&t0+sR`^_LkRRH@H$NX_wi!<6ajMOlwADP2&7EA(TQ{yG8P zFMHXT94%M)yy5D9m2>vw2iG)@3=A_ll(Lsef*8nqy5J2BXPS`43ftXe+K1j&9Jr0W zP8`E+;@dN+(!nAixDEknu$Lm8@EAP*EF@@dq=uvHAUI>GVJ}jf9QUc_2Wj66pUPD{ zW#+UiPgt>!7ZgMG3e%qkwd=Um>LRBLZH0|Pg%|>Q) z0gZp6$vw5)MK)tB9XOA@e>CXhhd#f7B5-HKPMyB7yxu)!72@n7g2?vw@w zPv6g)4F_cjm&Gbd`ZY@FX{tf0+*U*(g2q=azIT_-GoofqR;g8EdVu--XF(D(?Pyq> zK>YMh#)NgjmhpDRB$TDAVBt5tF=}hLpIaJgf2VaK?Ll$*g!6uR<-5MH4VzzC6PBM8 zLf?=)PR=RG}<{=l9y6^z&`7c*hBgYB(FlPxQsw)T!+J3hVUdk5w$f0*|Dw13$esoFoE zdjMv{kno|Fp_ZG+Zyb-6?ueA`m^(aQioLIc3k{7EM{uymf|TU+`S9i=k-8&ECEF4y z+%jjIFJz~6Dxc(r(j)e&>Cy1kqmlZfdY`1t7qatvJx!EL9d;+{R!>;TP*p6NsL#f{;jrkRsaOdGvDI+y$5Yhmvmb{_DZ=BVw6q-S|GgU$hk!sS#8jEeD6 z)^(5CDwxe?eZ*Fetwo#nenO8q(@LZs%PzRybghZ=PtylJIDG5ye0CGxm?eXZw&|Q{ z>-3A^lJ!x?2C0Tuk1v|iGFC4XmlHERWB#a{o5y}Q7-`rUDc&`eijLtz)#>W#yy@=H zD^V8{!7x|&(`ffqND5~lM;vw2{-|R$C46l1*jufO6)4%Fn znRX)$M-$_q4XS2!#8y4sGPf~otB%_CYHB`PgMH?xZ40#!R4}h*KERm~yjpl#9!ASQlTTZOA{)e?j+{NcHyA$LEBm~7AH9X`87(w;|h~!Js zB9urOZLHpwsE+oaS<9&ENgv3UrtFVP%e;bc`GUF11h!yhOsRFuEToVDG&7H|Ys<{T zV1X=HMUR*wQ4{5O(!>#q~KDLsNiCXTj=~szLXYX zBxq$My1F~OoG84G?#}Pi3P9X6*Ka91JoEB*gz0r3Y`(SmW`CsYu%xV0QE6luwlbr? z$HvBB@o^%Q8Z^6Jjl4k`-K7805Hx!bDsRLV8uO2#XFE!JR%~%*>mv~e@l?o+y8=*9 zYQq!L#@4Lsw9G?n$d*)^CDM`#L31ZNBa#&{SwbkSxTt=z5{uF<-={|SZ6pr0daNA4 z1gQD!kxyGcX^m|>6xnzvT6TCo_wYp8eP{mE==E2xy*hnyCNS@W`-c1W%!yHMC|VV9 zR?XY1r(eEnhnhrQC5BHhMC+M+pKx^$m|Xvi#@IdS5eI6iMTvrXXDKN znbFvqBat;n!ro(uJ#I{h4mUnU3N6Qse{Z&=@v|2atW^j(Fa+^61g_K0iSDir`4E5< zOIMZr^{g=y32pUb-!;8wCixfhK;S(k9R%b)KQe&LURXm2%(Q-Z2zE(ys)#&anD$@L z%Jl6YTWD_>1T@ibtfORUCF}eT@HSCeX_J#n_6{Py|6}SkVl&Ze>?oiYuDvjIJe*NA zVIptX+a|Y#?e2x#{Hcvo0UX$LYZy8sEw=g2~~Y7)R3l7v;lBQD?(BOQnf z<+u{e9)5lTdO<)M9Gx%;JGB9bNv>1hgNNXT#O++zi4Awcn@v~0>(kaVS56R4PC(ciu4dsVyQAZ_|0?X1RUAo+S<74A1VkwFTD-4Dk7=K}B zc`_QDZ(qrShY{5_cJgcIg^P(JD0^u^acFjerYl*pJQ5ojN?VJ#;GUbcbzjE^&0&ib>!+R2$F12RW8)Y=fS~8N%J?RfUaU zjkL1~)>mGSio=2hMYOYk||8zjZ^m%$4B3UK2zEV3hF!BK*J z-^JYcvvAM_mOR0{SgIXpJ zBH;p@Lbsd9UiPp(YTKbafSua}B3B078ntax;ym|ktCsHUSdcgk+=qR*ExTbw5J?x- zA^JAyS_vDUJobD?GQG?=IuOC2ACHY-KT9O~h7%BWusxIuomHiPW~t*B-iOla4ae;vjs^2i;Z# zzRaML7gotEef85r!@O@~qXeaCH8eg$Kx7KYS|)CWglCCMi|45R^-1+#P7?G}sy=P8 zfI49glUU^|;VD8s5sK|+Cr}_XgV;RlSfR>d1&}r3lP`g}0o8MDpzb@ONw{g=6>`=> zs8nqWS;J5|TT_hyVy!Mwt&$h6Jd%wr zty5C}=>q)&Rf$g-O_e+m8&)OmP%MGNRx0F1&6evZJ}bD60S!5!D_4&zgGvuDyIAr*t8^aH=p=7MiM3CRHDgBjNnZtZC(V(% zNOs+Pb_3YNnWEW^u?3TdsZdmM-=9bb0K^<3m~9CF+oQMC|Vx#Xc5^A2<<5mVyeE$9&EFB?`dN z@d$;I(LgRkw*2ExnLJGPkEjRZpav>LlG1Wdr$NVTwA{a6{oUGowuYsHMllb+jOO_= zjUqP%)83_~O}hMrpu2VKTKo>YSM+bykDyj&VuRDvY*PRNm(`>{d}RpqnGdCA1qUTa!sXIenp=K z8c1uFDf4pQYD~hJ*|=W%8vO=U%PK%BPwZOSZ_qqDqG*`F#z-WCbW#{hx&)1o9p%;$ z#Xqs9b<<_->R|n;)!jbIbT9PEp)sS@Z$JNtexLZyk3rn78R?49L>lQu%k)|1m_=|> z6Ji9ow7M~)M#0{vXr8GJ7O0TJ0k;xT`#{N8)~HbC5V<_i>s4q?U=htu%$r=BjT*b* zN}}5|ZoXvjibjtcMPC4g;huI3Bn z?c+S{m&$7I%r~==VMX!ctl$Rj6nq~#B3P~uB^97S{2V}@ERn4& zj3j;!FU0TA?YHPgI~j2c`8kPOAgC0%+B5CpbJ5QU3N_NLg}_PZNghy!#V&+oqh6xV z;udNKk!+gdj^UBBP#)-HCSF{4%Tgqxns^#}3%A^&o>gxS5#Py6TxAj&uoT2aE8IUK zdB$DArI52S;;f80Ya`Cuc_-XuKXB#2`_Nriz!#paNfA z|K|l;LtQiLW=_p?&1{-I@ZrwiGKLGbPB?I*eU!jAw}p(~*nK~z;5(L3`@8A$In@)* z3kjRj&GXqFY)Ctid&OO6g{+%hGSfbDc(!>iXU-~{(&RHB_g!dvOZxt$Gs4Vsv+Z-$ zb9u76&3xKyrmgKWP!n#Lt(o=DVpErN^0A|kkcF)BpC8Mrx|>xcr5Uc+bkD^sk(hVT z%~97DI2nKc;*9X!vH7x%v|+sFTH2I(s{KbfOeq2O#-TtIEt6kzec;+aEWbXIUq54+ z&u^MCQ<}6XPDVbbX3TK@ThesTypJjdjM0iK*XIWp-?SZS#Cx z%Y5mX0-t(u3kOHb?r+1D0_M zyy2Jx3&PfME0cojq^74nEjl$M3Cxs&D1w4aQwHtYSoJ;1rK2D%SOt9*XhoS+n>s6{ zqIRp+Tr>%(gGRvyGh$t%pa2n;?9x+Ymdi+<-{JqpsJpnRDJbfEa++%Gv8EX4atLbHs}NHyKdZk`^3_cT3nGkr5m%$a$|1R*(Nbyp`RS)gM%7p0r0R*a zuak39f93W=?XRKs>0{}7Cu5>CgoBf=i$)N|rTSkm)zqe>5AZ0@h0DYX>ZCblr`n7= z1pAmBt-E&2uBYe>HD*O|m2LWEjAtS}y)o(-%K#yeF_wvY=9mLSurfNKTK~#2UpdF@ z3cNym0RA0DWa;JOLP|Pw7n{cld%$LZ&bRn--O`p4vgE!YLWX&8%6ZQsn3B|8b(Fc~ zHfH&4TAH#N9{^*+FV%W!+$7`$wkhw8dS`eF`SMsrAFY83J9S#p)u*FOEab^`8h7fq z{FoE9El6lH{nYm*Wrs(t3{oh>2s3q7%1{VbDPh}XqnN>3hxAjDE(h>-r!qUoY>d3u z={2E9t_w@)Xi_qGR9OcGaa;#Nw{;WJJ@G}qN>Y|Ssq>vlDEz2c?m1Sbwxv+&=oqsx zPhEySomTQ!qW_*W_Vik^>Qm!a~u5jGB9N-%rX$=2@p>zvtBtW z{>dr06H-{Y-wb_Eiq|V+<&$z?3Nkul!C_dk&byb6tG?4q-^pW*xf}TviK%vB4c6^V zyj{064WVkxrOjW9EyrLCC2Q@m@&2T~`|{W_ODG)6>Rc_COM^MnbTMbOJpabCg}kwB zhCj3ZXW~zxhUKzp?2G48Baeg?v2^X23)D;WE8K0!=Q z#M2(k#|{Lf_MM7*oLz46K`tIgGLPvjlH_>k4w1zQ0c{U%kEG&)}b`PLe@b{P6mM)b{fd}4N}@3wDV%Hx4}|upQCF>x4XM8 zb&=zCfOxyQ`(cXR;E~RS>xAL~?S99t{t;-Wh&L(yEEGwWJ+apfL0cnt^4`fd!h$Z1 z>H{vk0Lijtrmyafc`z+0deHR^#GvU(s~`P;Tj*>D{}QD4n?O`^<* z>$%8%tD_S5yCgv(=`7bZt~@$dIel`vWu|bpayBPgwJB10R8lw2$}tAHPk297i|GSC zHrDCaZUq%c1L~-&0*U4>MiWcbJC8CYs19;9>MvWr?@|4Tz2^0+ICMJzAH$W?>tOkC za{A@z?)TFp6^DKy_EYC(s7LpqiXN|+jStoH2C>Drqn06#l z6flg3+bMQnK|HmEA1@fU(L0&L=m`Z1K+t z=5GlmMfAfyDIhW%sz>yi6x2jo=fx{@BjqeE%sfq@R=WKbMZ8P5AJOeP-N@idTthcz z9Yuyw%#MotAz{+e8wB?kxOwt8u3$>RG;aAJK}sXg`H7OOmI*oIDZp#^A9tK5KM+7f znBabq(u+$ts}tr^1H*AEJBcx#GB6~J^!wuGp|gF=kctaWSt-c4DxSeK7I7xyMG~OK z9lW3TlRpm-vb;Otmm6JWCeDH6z30T(T{K)?!^SxTs_;~pH!&WzZ3>rfnzwD57{8xW z9ty-N)Iod(B;Y&Hb|K>BBRxMrwA= zm+hWx{RutJmo-halC=y||Jyhl_;mD>(L3eyTb_;-JRP;2gr;g?>Gjus18weNdn4n`0^NX*axpwCLO&{#IwPQYS4ODs48Yf!le5BxH zFw}nYg&QwKoDC#COuLpAIyk-QgYCDrPkU!G!^L~<xKOL*x5~~=4!bkvwWDpe za=vQgwWDzJbbxHbTCcTEpPtzak#ED?hPm3gz@3&mTf)^x@8upN0|1JCcBXBnWj1%# zH~Vb3de^<&-HTZ$)?&V-HyJ9J&i$b1R?&1uxWF5=tx*&z$sgqC(t z+~&E4J5^yv%V#H{a+Fr^2<|S^IXsND*wG^~8|@cIZKaP=3|aY8=ihO{2G5@Ltxj(1 zHk&q|w>@ItuE@gIMY7k;LQ!FxYMj*_IvuXs6E4{sb?nnIC|kWIY+HjP<}yQh->F!r zU3a(k>A9NN_NOD;pAK(+nwg>f$X+0c!(rDHb!=ohIE4{gVJHt4WbCONr!<8EH%D)b za?>k4V=Vr~63eU4Qw)wme(1p69OhC->*`86S&JVkaP>HYtB&MU$8s9)<}}Kp>59|$ zT+fi-FD7k28HI};{-EoxD_pTH>e@~a+MGh;Q5QV+XuNeP{vC!(&W7aRpK z2Yjkk#2k$gN8`+CG68hgP~q5F5ZFW^N2U%ht4JwKUqUZL9Uk@sy}K%o6?pvHCnit8 z{_?3CPtDkAoKe;J7jw;FM=1?pZUgx1t&3aGx{KS%A`cdSAQ0vPfB4GxUx`*8oi98( zA)ne4Dw#erePVWfxM1^K*F+}S02UNqzkKa7mRq2{*FJeIT5)K;;LwC}z+&0-Sh#Mp z_LwvvW93Rkh@Kz9J3KZq1S9iGszj>+aX$!t9RZDK95o?^2(Y9}FkWcJqE?4k$|dPa zQrf4@l%(rJ=^4xx%<&}SIiueBE+Pqp6fF!j8C4`VZW$zzt)icy!pOO>BL#GJ?Zuat zDL?6R5FwLvf!M@xib)C;-BbjkH%tSTu_CS7`dW&CR)+$~N5 z#N!q6>!#O-WT>cL#&no7!vDy7XeE_t9{jUq(jje~C!~|Wvg?&atM{Pmem|i_bW%Xs z%9hq${tC6pd=E~k)Y<_4N&TwmIjHFh&Lq+8lltyS@j5jTXsqkhMaUAesPk0ZIX+oED`WCqymA$RH!R^Vma56vZWts)8k4yAfS;odP2|zF(5b%N^ zv#DNAm%b3Ro$#)(4&B)K4` z<+W_XZ1bl_J~=YG>y9m4a^$m(Q3sY~b_^4vA4rj5A$!-H@Q1zM@0~mIS!3AsEOLf{ z8r-7VNPhrk^;cV&{$@qQSrKzK+;uk0oc{R5+b@Qj*qX~yXq;*P`1#w<&m5o2o4YjE zd*|h_3z_}B*(4{!3bTH8~xl-q*;di}Bq6V(ECInbnOG3@D(xymhQ`o!z&hh!uQ{j@f zu&e!%R9M(J(aB4v@7efaJqfA`ipuf~R&Y{P=))*TzWk-Y8d};w0$?__I_tZPN!SXL zlAIbGCz{y$FCbMNO-#0>WY_shqveJ#byjs5BeQ(gO${tBMbDYH!dMwT=i? zj$byngcS~H?3Ag1DI8iVFYEjk@uZ>-pfzQbv@%J_qy@eCXchxLYAG;wi zl#GNR+%yg+#lxBsihWI7sgGe)uH5z>5L$54N=t~8ya1O({ej+LGV_EVY(JKd)k&gK zh_E#BWiMGv$O~QrSGwDWo(yiI6@%n9jk2tjFHR7gCM77Xc3x)L+W^PdTN9TcFYHBO zm_)9S8hC;)obkv9)kt zRqqUjT{Xl*GqzU|5h1gv?I@Uhb|C7;>$|S)ny!V%75U96?sH{oe0pCv2d-G^DS`=? zPDO2P^dQSJ_}i0F+bMd>ysl9Bv)E~cwH|hu8k2s+Y$bPXC83uiHuspOmtuzB;^;Cbm#+ z7xh<@8bNa76vOS=R~mwxRhm0B4M_)d?$PsMv3r8le>;2G5svdq}> z-Ifz&AvkE6@RtZ3?IQEuFL8F!b70aN}zJF2~g!~E9grUbIWW4EMTz{2X z7da$;cbSrA;(e_QrT(ICfY|6lA1C;STqYfj*W4$SS3d>3t>-yv?{Z(E6bT+7O*qI< zj$@=QUh^kQgpCD zUSpGJ2-TY*-|H-t4%29t&P{k@pwC_F=+b@-bJdwwH)Vsq^)TNGTFW;2Hc`SlxdC0R?Ri zmXpmcrvVc=@dvq$@M2}pn-a+U;QiBdylvFJ?!L1Kz6KR<_#gSB&J7EttFEQp&jN?d zJteM*l&<++?GJKi%c5C(7D}5CnNa|$5Z)R;vVLs8ZU5fsPdh&82-ok8X6#!i^h_L) ziOZt3vqiJ^xpi<#G8c&2nq`{ui%^-`+A#5jRIPByrl@1HcFZ$74q5D=1r2S%28sgt z-z;YAC~$(h^sc>BN)N98a&ocv7t388$z46uGM~FXoU>teJ^XHEZFvL_ewp|NB9!H1 z8zCW9z9v$>X1;t~xNLoR{ef@^P7QAPkt6Raa|Wp`&_ebyVhb)~Nx@k+v5c}vMp-PQ z?ruh%QbJ=^C60Z`$izu_8ToG=eIOJ0ZBa*iLboF`xKD(81L+JH?Rub4)xU``$E;5A zn|y&Y)_x;Jt8R&~k|ry4>rgmYXLPvQBuTGM?sQJP9;FM44IZ)7l@IAF6uDXhaR;3Q zuUmda?VVO+?CIz_Jz^ATD&llx1M1!hTELcIOG;{KdUYX1PQA9&l!eS8)LLN$v9~@> z;pj40v@%;AG`CuN)Ib5L`IApDzjc^j99DO5ncW4@7LS+Pnn2zP3Xk@WlMNiJE`Lj^F2C`K(T;ucO} zV9R0BN92Nh8xN8>M=s7B+Rdc1%r07tXh4;qrZpuKieZR&So#%xxVQLEnj*GSL5P)SGc~ zgxJ!kzLElO8up0DA)FRQo&dQ=!XLajh=79$-S2`xq@?>|xhUzJ4HblCX#O)LD|i7N zhu1OYKwYptOs9#sF(M9nlD7%fkC_JKjQ1@l-)sMZUyzJb0R1N5$Lsqbq>IiywVoIG zE%TwDLupaC+J3>dx}f(CJ_fT9kdq{oZ#h-U zYiPXc?Z)>6=ls4j^6?3WSpwdYK8hHz^i#rB*43Ie9;q{2rJEzi4SiOb61>?*D zL3pBiMDSa5(TQ;{PJ%|e{AEK+wJ$3zCHo4Zl1gA-f%O1dnPV*PLbOa9V@^5zP^hc6^_3IPXv40HS1{T{_Hy;nO30e{243KwjA z$-;5diVU!q45#6W_!%>jGz8S7wVY-1%i?}Q;v)M)m>+qb4Ow% zM@jl}xfxwMg6e8NT9P~QxBN0L-?nOE=ykdnI$%`Gu17>vv%Wq6Mpe7E4XWU}d>S8y zWVf~eg9YP7fqa|Zf|#<{a=r@}@?Iz6xa@%8M!p8?OqWgIo1nks%`;fSeCtr!uN2d^ zY4!O_Pj8!GwC!9>_JaCm&z)pauxUQgMm z4OjQgw}s1hF1U74$xPezuboi=T=W4@8OwZjapQiWWaR zwJTu<3t*|HIhcu0w{Be8*gtoY7!=RI+khq8qj4yNwG9ok`$>2H{Kc{ohnx}8)hM#>d^33z$rw@k@zYwl^anb#f!Rp0aZnWW;kZ+w}9=KGc_0NnP?@h1h`)Y8kq5BX2r%-K?VJR(Gi@ zGUB8zo&rK3-D`nWi*z^`w26cnMTDmAA`fqh-x7$0z-(g1hYq!E?2tonEGbvQ(V=!Y zs&|u!WJQTiQ;7od5c=Ui0Er?X4z2bmZfqN+?kkondRjgIStm{5KJZwvYwPX#kY$1s>F zMJ6uU<7Wktv!VFo~Sbkwql;aMpo#omHCx^ z6wV}PlcN41SCeHXA#G8<$KoK9(E1mH>2$dX^%US4Z#iRdkVPI5F4Sytpf#1>)i&NR z??{FeMdcYWn-F_2h!VW{H8KLYDyhfBGl7?7hFvD zrD{#GB@c1mZ(Uht5>NRzQxl`b4{^VyYD4my0_uz@;D~y(R-K<1??Q2}14E>FSHmXQ z=fmBi_1eJu##apM>2RUd@Y5HLf?=U^WZPI!yp5-a9+}CIp*C)ZG(3x-2N-A0# zpi(5Jis85nh z7Oc63UKRG9{BKdC-NNx!_Au<*k1pAAYOkOKv#O9_cHrA0jM<>VC?KcN1UXNNky6d% z|8KETs#+NFi5L?nTYlLu>R+{&*a!`-%prH8e+AFI%|x7>dT!+#DRy9V4s81A-@ahM zA$H5)p}3tSm0$yeECs-RiG5=H%mp%0j@u+AA<^}WNl3(u#v+8`Az7*rF$WcwRuFu- zU?G=Qaywo`$v>hGrE#v`bv!a!h8OzAQ0XX>^>{B{zIl3&65{=DJy zlM_R*3Nk7yS>qCW<)O8YGl2hZeE%Pb@4zge`0JZw_zgz(Hn_Kekye8}lQtN0IX8Lo zskP00@_G~wTO#DM*59*jc)Ss2M|?gALXnRf)%7qHpjqoB<0Yuq8upNzn}_KasF`td zGSU{`h4B%;YaiZ$m=sxjX<`z3#8G4w@@E;^!bSEP+H?;fDeH^Q$u{J}ZLV(S7ZrHp z6065rIZPcUQT>{T8%>dBZuGu|dm{c0dMWNib;rq)c zSJJ@!GrD9^xBe@-FqZLQdPY_(pvHzV9FqS7Js?{c{9so%guyIYY@<}7^qI>N&WmNL z1I)lhfD-|{|(_%6++C5cX~mkP_S!CBwCUzy4z9rDZtN2T$Ro(l62 zT;>W!C$e{2q4fTXTxJ)eD^k@p-wdO?(W>pY$D+BAV=2o8xnB~y-%vr3kVYhuOCk>ci9aet(k^yiDW zP3^kp$mb>*Vh-js*FIkqc0h*Y8FDPx_jccstB7ms$6T!u7mnNKUkJNe7hKy(Wv};} zy-QWAV^uwos-9?7A2ixiW>>h&0q^@`*T=$5J<(OY;mW@Grf~VT+lT)0$WM+exDIH1 zVnh@@`J)Ric$nmti@Ufbg3y=eH$=;se_<}gGBbJYjjL}2_bj+NC@W-DW?o%zF{wa` zPsWOyBE?PN=1udjM2mMUxOS?!d{>e_$t2#oxr65gSDTt^@*0eV1X~pCJStK2UeAK7 zPJhE#ABd<0^4OQuHo<)_>Ha^%Wb;V9l3oNCT{WlhLbRYRoL?WV-xGH4y`!l3G3K?Z zG|0@0WJ1^vOyio?SoOL{^|~o^KqUm`T%}5Jfs=uwK|l9lOtwDYCi(+rBqZutL*A?N zVR9NKJjiqP#we6ICvRMsy)d^U+Q=+Pz{1IrXONB#_FmolOUVXqv4nR3PpB*bMr}i^ zwm(waAFX{V?5a|}8D>I(Fo{tPQN$iaEK(hUP{rJ<(c;aw?F)_pU7D%?YJU*(qLo0D z4__cZuq)6PJQS*nwe&|?`sa5>TegRr2X22kT>Si^>mZ2(s-w)n@aTN?Cd67(-p4n zo`YU14I1@=3TG=#sqqHMa#Owr5j+Lym-@s>nYO^e6I0QGt+3HP7Da(hLhsdaLCoxu-eA5 zD(_~IaSx8_0y4~a#9UnwSJ&LpSoh9I_s&@NV5EC+(RF}iO;@dy`T?@%M<)LO0`Kuh zCLm3T&@uf;(^h9GArF;m`hiFkh<+t)P=$60bVeW}n28=R{S2EWp&SCfoMUpyVziyvg`5I#!676mJ$%83+B>7n4i;bk< zd!J_{*|rCMy7m&~_8)QagN}p|2d0|>Qtd;ug;SD@YGl;q06lv^7o7%V3I*)dA^ARv zS*beU7(2nqJNdsN-DJ%m?%*`n?h|Iu$?EeafQ#?H;-etNaIoEXweQ_cQw~CzkRzNq zAAl<8YJDk!3*5j-MdOo1aXYW%Qlg5l4K|?QO0|5K(N8Vu&T^OwQ?Sd% z;J|{bMa4VkulcX~gP;;Ly24CF5OU?Fg5WB69lghe+p|%szUr!l8wLF-ua26N-*#GD zMUOyNe}Ya;DW7YQY-J*Wwp5eUFvZhETq^m97;T9RsOX~iK63vk0PkNY^zOW%^cAukRq#r8Y2Y|!dT$u z&_JDqD`BVW$Ic%*=Pupe5-#7d;AS*Dt>JKIa;x`dZ)gpy_?0ty0ke|k?x@l0$Svnh zXS8U2xUh4sGhDE7!O=tCCKPZX*t!VUf^=vkHkt8HNcr_=hz`|k`9;T=f5~GF$S3s` z)9<30tLQXkz%8IVQ}8qm7JzL@*0Hlixm5Us^f=s-!yh613ZH?aIasnKJH23=yaqAw zA^sGK6AaoIP!TAx?R-3wLkHPzh}$VA+u*MRvrI~R1eE3Kby$SAUr!r@Dt0Vq~g7W$3w=6i;AVnd?3xn93)Kkvf>0E9HAlN=!YjD0Bs^;YGQ&k@UlL7 zjyXyr4$^jPz1|wUxZof&mz?Y&E|doI6^o9BmGDd$iox^Te^+=mC@YBZnhG%x2_3wrDcMZT_)y@eI!S_Qlg*9XIN>E7Hxv zv6)N_m~mSfR~V-O5E=H-=9H#~Izg1h;6*3_`AbAIgHn#tl=uK|VoK;N(Iqo$G6_`^ z*W?!!RYCg=*Q_h#y>1ahC{E#{8ruR+lnAxsS(A<_5V;2SdMuJ>1=SeA(fy0a7 zr8)5~cyiFg)3HC}zG!o$Y|&A-a;^|cU~`28zf;o+`Uqj{DZl9lJV_;)enz-aah%OC zSd47DjloY=uTtzY4`56VPM)1K>mhQqrKX;s{;$S|(f_~^Ch_8#u4}Y08r9lvtf5-t zqt{ zOt-m1L(I+;I~4E@4aqZ&t!L-Fb9DM>z&1SuJIK7qa=gye@skb<2}l@V(fRl_i_xK+3QTaLZ1w-> z5OgFI3j@{6AR$^`O{W`^V^D;^#+;NW4EnWjuZ)ATOg>So$@Kn-u~*KM2}PRKP@WiV zuRef-H^G}H;UfX*Z76aKj+=dYA?QA?crkf$7}g+_uQww5`N?zVxqV1B7Q^F{KHW(s z&N8XPWj`)tcHN>bt(wgQwMBl@(z+tUI1K>p&n{aYxNtHu)BR0<*ip*5IPM%$%Sq~E zLRhSik7Ed6d&&L{3HCFDuB+d2iJu>&{CL~^d)<1;`Zk36E~Oz|ohXJ(BCeJE*Rap@ zIr6_PO_I1a`w6F(&5-U=bLyejZb%o@(JPJFM1-HdFpI>br!~V=FOxp zVGKnisDVi`Z5dJTeF6z0pwfHu9)Ud((r}s>9|0mDFX1rUs?0-nHr$@6P2A^4C(p~{ zzUtxXW5<|k*Cbg0Id+WW0>T_8CdgmQ*!U~eq|P;V{5(!w`}6{i9b=U|c1$ah5X+XN z7HQ2?+fxm8~N%(VnsLw#T}*N4mD(9*s8ci&pIqm+WUvY<9`3=)0A4*@_p~=VW4ovyn!} zvk~UQ9k)Sq5q?FW8#hGGGZ>N3OL=LXZdPbvs<2#wOWbh|A}2$`@+&e8B5NTdgpUE@ zo_U7T3pvTdWG2XxQ(tH8LRk-C8KR7xr%i7TXVudQ@#d?W!#KA}x2&p-Rdz-yJHw97 zDaXvlrJVdJ8?*eNJIq*OK7`AwV&$!o^43^+SERgaYTv(im&Dvm?Wy?}6s?cA>%)z` zEM6CJ*9FfeGVCQepE)eK`O_opaZSWs6KspRJCiJ;Hje*$#C~W2Rd^mh-^ycVi6vYW!S!Fub@0|3C<4iQsJQWL+dT+ zri8Z;|MQxU>c3x)*APbw%T~ooTO&Bo-8DB7E$y4KU&)~p>o=$03@bdCxbzBU|G5#2 zg7%|%$X4_gM2u9^Bt2z6UAs~TSkO_|cS4v3o{g$mq16*7h%$l|12{b5n*{@G2kgCf5VXxOn=HSRRWAPTn%GNgg=3`UXvkfLXjlJR&LWoLrXq}=?p|t z8q-mfY0IcMP?B5FXrt6uJb#E|L^5RssTk-#%KmB-u2_*Hq+$oqOqnpB`QDB|_q+Qc z4-j_La_m5GK4K&dD9$?g9}vNQM`&yeoO0u!ux{7o^>LCQOc#+!a+X2sOFR6!WYO7<6nIondJ+7*tL;WAczd60|r$1YSYMe@8cH6-6yL1U?8sV7^&?-(hWP7W1z#U znR#0;(dDE3^LR#@&*~?Le$Z<1+8F2oP#MOBQd=CWw~Sw?oVbw+NC4*SONI`b&>3VOf*$Mx(iPVU>qqoKrNP*qkZ?m@)n_9+-ck8O3Dp3FVo7MxNAoFJ_x5oo*z8-7Y5iwAl$T@xMvJ(&8*~PfISC!R`Y&WMcxcnk>RK4N#AjN3SjCY z!@~?HYhgE8Id%>g?^$&1UCPdX=jG{_bsKA%G!zbw8D|XQa`&CBNSqc(HG|5TLf>)I2+T9Z(c})`w5%>#gwBzd;;Q| zUwH7@N$Mb$RW@ogVDfrgOyi@kuDtsvAoZMTb=7h#&`UV2?<|RZy34pGQ~Z79B0A^9NFb2LoMKT zomh;MnhL7=UAYh{nk-FZUB_r42d?Jr;ykZtwuPWH_`5g;NIO-*s*!Yg>r0bW>I0on z@!A+?gTx{P<0&o)f(CY@puza6&-u1weEYF*V#j#@8}w<0p+7SOJ-HJl)u=7h!;2Xz z%7>*SVb5G9H8Mf#;VZ#G97Oef^zMK-=S7D%!<6%Aif@ryUGcXx3s8z8<7&MPYD{kn zJ6ft&pX=ep3?m$}Tmpq4%B4ae%R}hI^jlGqxbLi2{uV82HNA{mShUiX+ADvT5-Qi> zZGj?_I^S6B1pW00SOymls(%Ix;uwjQ)w9ScREhL2lSJBTi(wm}@fh{uCX3YgiL^S4 zT!{LLXQ{@$v|5f>X40-4f6el`?TFYo>)Ji=Gkv%8B0&#NwGcWiPL#2hIVN441R0WIAi% zE`XRFn>Ko#iTrd@Y(1rRQ*3}@zr>~4DI!3aP7lsUXtiQ>h}*F>JsY=Sv+E;#5B3HB zaTbPRvX8IAVv~J5?cV?he7jHsGSUlo4)u4l>Y3!V;y{Rmq9ME!E{8lE6WwO=#`nl+ zPD7-iAzIJ`8NL3ertCP4gHKK-TUYF?pBhM{5*_$*Vm(n;oIE09EM4Ec5ZD3- z7K+)I`WtPtZNXKc*TYpki|$@3fULo&k1ufljMQr0P*2IHmO^KpYKzCdkXz6EA+3oN ztqFOfMV-VWDY>@k>L%FpRK0lighpmJFM8IKC_lOF>*ef4|o*E{FDWNJ|b03|0k*W2)`2QV2^SQhitMLcyM zjD-u?Vot1l*O(l$U33m}(e^47ov!4A3K9uk3Expm z2A+CyvBO7}#DpM(0t~RBN0yF_8#tkWg4$OM*NaL@EqZb_NpxE&>%+eT@Qw<(9{1DVObD@uXd5RW^^QgM6 z{1=x74?aJ7f@|mf&y@80ba|F?>Pr%>6$aS?X4RD~B1Ch@zmLo+^P4>&25a2u8yy`d z-8x@f8uuY&g{}|;Q5KIaOgB@!c<{7#%r+k_kW3gM=BSA{Y9O+Q1KpasKbEGP_aJSZ z?4>z}iCrsRnvX|MY$si^UQAf~#J0DZDz8zdAT@W6$iKXnu$0RybVQ zF=q>%pW7eSbjwsN7+R|mY1X_QMyqFBIJ*+xPE?b~$V#CJQf!hG!7Y+V?Ot${l6VEh z6_EITpUvhxj-LJm z-{WL4LqsV>H5pyYNLn2mIRU`(kEj4bNwQFEQ~oZ}O)MNmT9f0MM@VZLGVCX8^nanK zWNfrlRDSLB)zj~uoyr0^+O}>gi+K<$;1(ETj;4sCDdZu|JxzA3T#+m$8P2^6j%q>$ zx|~@*m-l=;8VMH+E;tU5LSX-Nf0%Ll6dx$w_fKCx9qM0nZ6rz)96We#FOSu9Mru04 z)m?L2;4^x`wNb@DT(>3W;HZZyy(m)9<%^E`m57Lt0z*U<91#hv6~O6p6Gr4Epdk&( z;63H%E5UQSdZ&jz7oH0ck=%s|BH$u3TSS8A3Ufw;oWydX`ZRcQ0_%|_F%nX5m=;r5 zH^eZG)5tnFzRKF44?8#&4fz) zd31cG7z3_m40g76ZqAl}Mlb&XUA{vXn$pZrfXpbE=jm1>W8h^5s{7FQANI<$w8+0h zpC{^N<*5-Pd#GlnQzQAW@k}hIN>tk@A%lC!0%U***fZTT^BVI%18*sB4+chKtJ?3b zYM-l*t>1Qc{WiAat+|_9gKc8WT^n)N2G?T)7IU{m+$~&fWx?IOlvD7|+3B;foT^9; zNwYd}0c_#2`~Kp+%$zpiD61)dh&TCK0fn83d9=6u~S9gu;}%S^|SO+o0S? zquqoHMq7gkPVN$Y8@Chv3_%p)u*uDIA?lUWs#HN@bZ|C@qTR&kWwCY>OL~)lp%-t0 zwV!DL^(h)f!nWkGV0EA{Fw93ca47^-+`j1OSlP=OvG-Gyi(dXBC+oIJDuw+n+Vrif z%0h&FQXzq-v()w3Z0zF{Z)s$n_$_X&CPQwg6`LK8y@Z;{6Lg`e$&Yr2Y1mc_O+Vf- zE#S(~Y$N#w@zAiO-=U!)p(f1JoO~J>XjTqhAB^U-PT9yO)W~RixylETsZ}?*59>?8^PaPUr!{F1(EN{fP z#_c5jCvXdr&n451$N%P2wv{=)MdPg;*QZI&9Od*A1Iavkh3d%|LbRecTHLqj>i^X* za^cIItUAEfik5jPf7cz)-78scrtE(4pO|ND8*)7K(R%Xr&JC6ytl!dV`$>xx zSMet8KOkc&)|2tTjX7b}HfoQ?kzgzVRcRWyHpUDWjdSwWK!jBxb)(_|9FVjz_KTn- z2+`UZb&=sUu);4uXH|twhIOHh`ZNtFTjy!I@pV3}t(n09PQBR_hv8<6e1VdQjK67C zH3uZWX{rDXn-DQ`NGG1{Ea}@c`d+-sNOZo0L2*0Bjk#V5|3^3aX8WS0?KIeR z)4Qj(&3Hb}%~wrBbS+>BRgS?GHINMC15wWpYXhCZnm_27=~{HG2^HY75+_il8BQSD z;Yr3m2|L*FlIf@R@e_}G@^S{ zAJIS^L~D}<>enBmnNAVKqK!)*YW^D@m*FRxk54czBKpQSN5$)lah#AtIO3vJ!dUVZ zi1ei>J(;$GnFsR9!F`*TQ>+3P@BsyrTP~*bAq?@Opk({lND3&Slz&E0Ld?rYK#AmR zdGt)a_WIS=ajxNN)aLdA8K>gnE|bg_L-(Sq8K(=*7KqNMf*A@NJ+!tyw62L;TOL|F z{AnHdYeBR=Zi5J2JZ(bu+nHdUhQCo+4cbz@c38LTqu zgVvyx8^Zsc;1(3riZw`KTZ60w2TRPh=4t;6fi10a?99_3iBwL+DGoEHGDCJFQp!uNi>T8#DhT0 z7;tH!;}D>8p*Q>tO2qu2evLaP&T$4OjR71!0= zyyoBAjYYJru)(ao*xRjSHi{XM$pJ$r^Ui-+O|R z4baax!Xl2gTQ5GslD}9l8TQI3`APeTW~-==lDP1g ztqudnQzoceF;PjBOQDJMiR$EHW<=>Ip5PCj8H0~Bwt}+xN{HVun{Oj$vSk4U(PMJb z7gEk#O8j>;a~Dt`bc?uh#I=)GPktcX$ePVs$kHsK8SHH;6uM#?jYyx#6{I{QrA=l$ zmCby(yko(=R^j3)CO|ckpNzONlb-66qz!?2yFk`FlWl)$41+~?H6-HQ%`3?*4KFAw zk?@3!X+Rj)FJ*dQo~skZ5SnJ=<5nvn8TN=TVtmZMq%R8j?{AT)`QvILCi#Tr8NbL3 z3xW#yj{v)7iR^mAg)oOs0?as6Ehf&%4-pltDGGO(LA01(#FBnRQ2A990rCT7(UIuoHuBL=!Q5ec`=a>&FzGQ$p zNvn>ih}HO8{t=)m7#KzVbK`xA^2oi*&rBIw{Cb4i--24-jDYEA$ zrc6~X*d_b;Q8B#Xser33F70#FT2ST}sr5zs<)_7`y}nMkyv6Pvg3sJ4QJ%TyAdoFF zfxL8aYd{TVe3#B|^H;v9(i8A@hs_uNiEIja9fowWFJk(6orZK%8r^y`VHiSi(mtsP z-w4HDw3S>N-}H;=Z;|&D5nQHYZv+2sYF>B%v~Yh2$S)MRG2#+&nO>J(8gd9)CY)cX zsOW6HG{Rlvp`kfvkgdpkpUPxa$N;L}HaJl&x6;b{hjgKJQLZNHcq_#i^I{9dKE$3Kr>We8hd#nWj0LYU;R_U81?RrdDTc9zvlHX~8j}*GVj<0tB@G4n=meg5 zlmI&&#+6f1?tho%QMS)8R@QdCjh|LW%GQNT*U#02i@O$F-QbJF3RXu7R>uljA(9*` z=!g__%-KJ3e%~1_=$qcItokT`4DKAdab)&Lq^u)cx^~VQE?!U8s`UbzBL&T|f;Eu> z2wUG8yEzsu=$YEi&fBLp1*KT^+Pl?jKdxJI-7&WTJQ~moyQg;tY=PZ@{@^S8jnG%- zYr;jF=Dj~Y{ln7>u05b`vI`Ys*kiwNd19`bh^r>Nx|_wTBd+S;8tBw}GNDtSl{3AX zJ+6$nDg)lAYc+K0vogtAe_q0Eae2a7%$hn0@H7R=g6n6#20rF84kU6EE8%3X%3!ya zsgsHJMfx7*zc6vkukS3|Sz%2&xS~Tvww3ughAI^s;v#IEU^}4@E!`FzGzvF5OcG8s zjfJ_5Q*}HbJ`xqN$K^GcwgSWhv#@ER1pz1ooTd~h_9T~Nhf~x-K)lP6HpQsh-Dc{d9GI_i}gLA{n-y$Vv#P6-pCKiG4wwH^3z4@G8rcF6f?X}z3v&#^_7Xb(m0_;{VI9Vay$wK1 z+E6e-J~3yNn^kNhRPs|4fc<}VUM#yll3gG4h6dntI=kn-CEK|Hd{DRNoeR?!!nt)C zolbssgQJV?wxztHYpbrVisd!k&1+JUmx}6xqc_gXo(aAT%M)GOtV`Q={pF^gY`WbZ ze)f5A?+OoDe`zD12M$YqF@L+|ZeEL8_{*VzTl;SA3vHgyht=6d_fw?uLCO>_1`pr( z^6Zy`gTPzmI9u%gtQ__Gj4H`$`p`$@#e3V*wpH2wWtFt8DI;k*5*5a#;}t|c$8=0Z zZPSj#^fOMoCkG<=w@?f$)v!@!1I!kam4o@eBA@sK@DSFIr)VgNm1rKy?r3)JeT&tp zdIJC*u5Q>q12451CPAFdEW<;e6YsxQ*=PHoq%F4O?xW_i?t2D623ua%Z@>x(N{W&VSRrM}gr`lv zCn#AXwydy41OXkT1S?sm61CC53;~NFV{pXyNrFuhyckGH`+a{tX6$7_iQ90(x*T^1+NtDd$kovtZURUIUXE9SStcL6T>yk+-^+;meOGX-XqfUlbcF<(nhf;rqjV#RK z_^18h+~~R`7iJ_91eVlKtzB`poWh<|K;L^Do6xKR=PcI40!{+$~b$VA5%WjNbp2qSy#V^(Zo$#aL0;z1q>)&r4e4Eq1bBch`0-gIxel85y0J-}0T zp$>?20_9vx|G@gYR_I*2FG{|&i|Mj!Qqu;xXvo1F6=)k#y<3Trg+9H4o|wR#2;mr* zt-paSqUNleXfmKCh(|MqZJF_8g>0b{T}6gR+N%B8Dq+T+%R8}x#WQthaq+C7<7XyD zP7l30Ec+M*#&?Zb=3V19wB5km)upIEpoG`yBHkeq?8|xd*(~NU?KJGZzv7F#_-Fau zip`>I$6MmnpNy*>p{e{Ud=nD0kK7P~EvIf4Np)D+9EOWK7hPQnDr1`>b;jzG`s0qL z0*)a3eYM{1f>G@a{jm)@?rzvIb20ekNYVN`Tc@&?dN;><_uTE>6UYu;iWGN)-kWlL zoK?K!s+!stb5%uLRl%%?Yg6cA)V1m3?2_yIeo;?q zjL2h>JS&L2xN-!rcl5$*6LN~PUM2Ayjd>FrbG4(ouJv5)35;Cbyx^$SpE0B@R6NnB zy33E~Bhzu0fB->HP)VkrksL-33MfS*<;Mw&3}qX{r5tVgO2}jo1dM6L7&EsQZY4U!;Cf}gTN?%H3i-6yta)|UKD zBBDr?^9|~>cGhW{P)xW0YF2Ezngll7+;i#TrG zfV|=ISHw6$Z4k&I8Afj( z(I7*Pcc?-6$qgdQ^+JCwS}PhJ(F)_D!k0;6+QX$vza>pv4_TOplG92R0EmOiCl5NfgFcD2YE( zO`E!y;f8+ztf!hKDkm_tY%U3vrr1$v7zE3^fp>}A^j*`BDRP>H1w>#V6gDtX=UJx0 zacdl8fUN)wytWTDJ}9YDi}(;i^S>Hdx>w+t#t8=Qju!{s}U1by?j2gYG7a z3@gZ&a9JrZ|1)|qh-R3Twv?s&0L)~c8NR@PUrdV_bKIoq@lzCu>5-Ek%V~_{G)8lp zr)&y_W%{!t!Ma#&cciu(UfpUpg=?M)7h?HVx%Hb z7bv}YAQF<|bazNwG>%xV{F$oQN=^;)h#mlerg?Q}XVd3Ub5=ZKf?2!vahpgcMu?`w z9MAA^xn*v?j}%&&+20k@B%X1a^GIZ-%uI9}6RzNV@KR+D19N*sT%bn#C%j{{>+v^= zsHuUK89$FTB6C}gJ-X;OCIQc)%Mnb{U8Y6~5{_43V^qeYu$Y!4ikypn+Qe$MrrP{ysSn^|)Q zVdU<99=`G`n2h(7a={EJLS%O=wY1MjWc=>u?nWG=W_LVf6!}kF^;-+m{-UsM>n7nFG1&neX}gG9q0JpWgXn5n4Fv7L2Ao=AGp@&27Xa&JSPfQyyKnf_(WYj9$%+Jd zl)M`#hI$-)?Vyu<$X((UJ}E*3?a$H5Kx;`noxiivE|_cL3><|{zRrY;U?mI%uzR_e zRt-XlY$_;cLREY&u=T<#0CfKv(Tqr${iif9uHqZYyoeRAjTEnq7Ow|W*i%aO6Jwsn zh^H~?X`XVbD@a>lRk#th8MZDswy_1HA>x4Pj1YuvI+tK|?8wz4r0oMcl8A^$EXbn*lR}e!m$JM=m!HwIuT$)0 ziZQRUCncW11?~ATwbKSAJgUUD2r{!ge*U#SlaFr@_(b} zaQ1`#!6lw$a0n`A(GxrQIh6A$>Yx){a>_@iU0`+*cf-px*_nHpxC=thlVc-iM<-8C zc)7x_#``xU$dQffFT}?v;0+op?=+VgXd6TcB&J_;)`b%g-6U_3VUC%``ariWZ%i zl!+x3Psd^k87G+>y2UdTtv-2_-5ucSY4S;SrzjZ7r`0=BT9C(aC!eJYO+=YCHF3vo zaH7cD5|@Da5uV8;>H@EU5Hg?vmEWYybO095P|iN&-=;f<%CDBE=+>j&8X4X4+bl`M z&XvDKN%;dS7@hJA666~+FuzAxT)PdtF!?=t7DQvJ{pkvRO38;wee6!Xb#6nX{b7o<5&uNzZ}kgwzt~ z0{)j;mTl^TL}r?_&l<2LEEEP0@-Xz3Wpy1mGMALC{N&I?$r- zzWcU(sTWR96D(Y_j)nJ}*~=21FE^!0RreifQf*)x687O~Qg8G<@sS z%~SJ*KQ8-WS)_eCdlr87&@!bZa%@sX;5>b_BIKcuR^X%Ct-GxEZRyggr94l9g_X5D zY-zpKaBqa4137xg3JlGQ_2fkZ)DD9hPrR{-Rz50Z%A11Y#!w!xP$T$ zB#K;8<%}<3K@gZ+rXaD(vfH``pOmB`_DKpWD&OC8eNXWG4gah^Qn5b461NVpkbM%F z?&nD8^DW64(XN);tErhSchHXrAO+I@^nK?>spDQw?mMqczXIRyr>>sj=9lNBk21fX z87}IJ1=7JbI$sa^nEGR7pd70DcYEz5+M}~ILM-y zY;BXOf?uTy&((j_^8J=b<5LL>p53lnrXcZUYmT&;Mg>8rYng)k)#=h!D?OudzKe%< zwlA~$`);eW%NhdoP`tMJ;+t2<%S zXz%sCp~72LH><)GJrPfD!p4%f?&Ohs&dhg;r;BF_1CE)hh_hx{qU6Mhv`i^8*psjz zm;*#2m`6_{NR&uYe&F!?N3S2H4$cp4qYlnbU>s^faM6WeZZ$osS@M`vgxfgG?eO&L3vU1?Ht!qF@pu!hjn6waahDa4pqp0RER z?7eQqG~<+Cg5R{Z5fsmjMp`%VCk*T;Ut-eMAyovKnp(nwDD@Ho>Lmn;@3 zc1^;9=sbD=0l@)+#35^sRIVl?O34T)89`#wnvHhoW4Q17zEFLvr7zOb7p~}wc={7I zmSOvD9%0p@Cy7-~X%N74qhhv#rre-)Zj^_&?KJn`GOegv$`2ln)ptkgyAu}N%p(Va zJIH__F>c+RCOvC~E2FXNV+jkpq48r88bB6FykIYo$^i#2U4JQIK{SL?5X_?#1c??0 zaG26Nh*Iw$px!}{@BmrByzYV70|f1>=8=lvHc}NV(ij|#HEfJDY^2IJ-p1z<+`-2Y zB#JVn`oQ`3{nz~jnDuk@ESTR;@L8WINt5b>-8VMRZYFeCKaW%dw~>nA0T5+n!1})H zy6XY%D>1**^Rcs)%7S?}3TFWth=$hP>b}_xTjNq0p>duLS9Wb{FYliED)ZU9CX(9} zwRa@!?Py5$vIRlnm{pQihpe|UZ)VP|`>6Z--I1nVDsS~X;1WS1r$DMF_}X)IPr^p= z#x@@I0H@*pp3}7~AxyO77&20vGEztx5whV%n5cG26|v4J|?)KF+o6Mf*`TBSSnG+1W_6j1T-cH z66@2YwJJ0qO3;9SpaDU`ElIU%Dx#E%fKm}8a?+)o;Q3f%Z=@03iJRLf8v)0VISF@$ z)ULjbD7}q<-bRqfPuJ^(DAfxA)eAww?a)&ZrBnozir_xHdQ}9rMcvhxcYo|Ie^jin zI_QZOHe4R0eA}b$8uJ?iQFkq#<(37yqPf+VcYW+GgF(4m%4UdcV7cyL5~gKIr8F4d z!z@OW1_Qxu`==BTP!_N(@cT$HNh%>+@;#s$Lo3=R6+*& zATKEKyp>fiN7|qQG@{gp2&fMcDAhwOQOz=AeNf>SQGx^n^V?x$MQTBwRMV0*F!t5D<<-kjTVf<8v`j`&~~vQA;ID8?k;NWcYxA0%%J^7P_M> z2w4`QJVA^ez!HRF+U|N#Uv}Hw$0JHEte%-z4B7O&OVblKG(8c}^rUqXEB7j6`YN8j zil?tiq}xDb(pOsUdLUy=cbz>fRKBr_k;NkRoA@sxAha0Lt0;)fPL`9a=$11nxu4hfc zMt2*qJR@Y4LzpPbl%PC9ZS1`3=}g$z?WQd}f?N+Idcv4K@-1w2ns>;5M*GHe=-m-> zq=#-YNBBPN%-C_h-E=bV=@S!Ye6pP~q>W9;#3Y68F!rI7EP*&^@(tQO5krtT;V9@V zGmhkgkQw)=NW4tOKjLhiOwFI8PsXl_N!q}Z-pFaH3UMvv9J&w>QYO~BOsc6e$yCT4 zbfH7AxO5(NnS5N^kj}j1-E=9Ur0hdSo*fw4wrgP9-ra*c<9WQ1kZ@O|bbRqN)PF1G zFQ)t%hoF7<0vtS+Qac!4gb!P|B0pk6HDDEmf}w=<)2vA|GlO1 z6HDdaTM9n0w0~l$`oz-kiKXQeOB1`U`ovPrvXvro&u0I|(_er3|FGBoy}jw4J^S*O zZ|r_+_tZ!r@BOmtWx;Ksyjz7g3xj(ir5y|QwU^03ZdUG6X3kP(Hu#b1HD@Wi@P3{% zJMFSFQIc(KoOv!`K@eECOhKYL-&!yOx*P%DS%q@3cdYzj<(-UhX_}A+ZcBD&mRiAw?w3^%Mvmq)>`VCmWnHu zipuWi7FtoE+;`fifuj(=m$xb*A*3q1-f5X`nK?M!k;tGYa5AiK-ktO$(~_OT+!B73 zOg}((GLc1(UGT%1$fj5hw7nAa3GC9-Ggz%`Zw)Qy(UY|~);x4r;dPLEhz4mNX3a~~ zx~!{~@M$Rf^W9-23r3pgp|a;ac(a4uQ!@|0b$F`tt)t5l(mz`zSsU(KaQV#rtkvqd zZ=vgh6KPg#X=mCVSSWnpaHEG$Kd>Np&`@A)2<&}eLGYl>ZgtG`KCmEokaN7uy3IPX M_m>tHVKDXo0u=6-9{>OV literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/constants.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/constants.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba3d6ba326c5ba06a3a6b765a3927aa4fd1ee6da GIT binary patch literal 1540 zcmX|>y{;TZ5QTTaAZsKCMj}O>I>0MTh7b`*AYqJbAtVy3x2JZuF8wpNe@5CTv3LYt zgVYfz0SPZ)$(bX*+P$!vtvb_FU48mgb-#Y_VA}9|{m0*@-#*xE{;`+;e%m|n_CDa% zX5Rd=Id31m=f5D(J0Ez$NX=*GXC7U&1#c3ncT(srAr3aFr4p` zAEB4({5TGAd)-T(M{@h<^Q_&4=v#B4q*UgDXD~6F2(Zn-H3?n%kUCvnSeAJukG*>H ztB+C1Q9XviXv3CZRKM3g>IIQ$)Ha%%{i6HPrdeS>Yp}Jhu64jMYinvlgq8^Nxnv1$ z9%}#Qc)HwaH&0;CKav%gqEl8@4cXH$tDG23ls@}rugBe3dHA*`!R z0NYL~&^A~1d?Y5LImrVPDxjBJ+R93r<%%p(*8K@XDKOFHk|+Knr8Bs7sOMp7Uo81X z4zA2vT&Y1Z*;jFkudJcg{fo@JON_ioW1VSXhy7Ahw06sh36>Vn2!q0cl$VwPbm0q1 zU2QkXz!IUDhI#Ql6!1LV%?fs<@1}Dy#sN9BP_~P5abJP*yQl2nNhnM?BRD z9LE`QtP{9Ux&d_ItkI{Pj$&K=szxVwsgpS}cbZb?fv_k6`ztV2I-ELJ5%Wjoj&fF} z6iPK_Bl0;Y*Z6$&_Wu8tajWiXOHTJF%1|actRMZzAU*Y+)qraHQllB> z&QLamVXPDjl-w0y85`7Ev_)mKg;lsHyhRtA{;GEuyVzoX)MGgyJL?nzn*Q)VuALu? z{Atg+=brmI=ezf};c$S0^xI$j*X18~P}G0pf}2PU6rP@^De5LA zQ4+0CGfqy=Fi>U`c9EOmXv|}Q_RM&I<|L2eUG&ZP2<=t)MgNSS&_198GXa{Cne;xl zn|hACAj>PE#qdlR+D9cnwCk$ZyObQ+52O^3o98$w_?{1*GQhb2Cw?1tbPtt@T`v0Dq`ncBwh0+AotHc6s&u|Ca(wTcOWbj*hoI#zIqZ z+U8Gbl02Qu$Uujuk(iWc#pD8JJccQ!fE%5b7c+`z$nWP=L%cePkcM#MlxoOG6_rF1 zGyuc5n9}u>I*-Xy8dP32Y_G0mQBtp`a7rR%xG+^Nn1O&KwlZ!!`C?crqXO;>51fj!DXZRg%Pin_x& zygaLhOt#S;&a?AWo-^4`e+u;nzpWuHe4A(Vy1XR{g`cH&^@V3>?02Q>dHVS}?NSow z&P3j0dOn>rJ?XexSI?o31}CH@>D@VpCJQ$jWngUJfjU; zZ}Y4u{E7BeYDmIR}E_JJeGq;{v6aalA3Mbj1pCAC0$%@Hk0aZx5Zi6J!|jx!hykT%no zQ@XI2)eT`5Y!wyUTeWvl6)s(J%)WF8WM^ zt67!ke9dl6B+@B0EhgNNClc`vo7Ho=&4boNN}bbe+OWB#s2g_pO;ML8-9< zcv9UE)ufEDce4Y~p{ipqEFtWs>?UkfqGpEOG$$^|JIeeVte*<&V0)F6Dyy39GiDPi zcFH^ub1;oiHAkE`@?nksxx~Dp&5DX{v-7fHcfb#-VH4>YO_jj2C#stKprA^6#Y{$4 zC7Va`l8kg&iu)b==!EUZV_g!JtZXyJtnGpEBp0Cf>Xt(IINCnP?pcK6nvOjrUL2CX zIX!k+Ta?F=nlhUkOTu0=a$`%fx-_PwX2&u)IDd08JLQ-5?! zl4rB?qnVr?b5?D~U&~tjVfW#Oz9XNuT5Y9U)UdrEE;suKrHrgKY&wcHsn!3uN_BCVuVySz~>K=R0{o37$d;ZcJ?^tiV z^OR+p0!5}0pqiSm&lG10%yy`0WwOFk!Ej*|l!cnEPZTFg!EP(q{de#iEw@B(cHZbL zyjc#lZAaU0PTZI%MF*|uU@1CmMThT?oZO1OS>U&W(fh%ka(j2F{kYYB{4V;L`4e-i z{dD2Xqka7w7w ze4iVB3X@NvKK#Rh0pXZOc102&0ynh)L2$xpf)h;o)1!5)VA74yLVBlsRnMJjY=wx& zrSXQ}lO#r>-KY+po=M@@2$9!>2>VxT-ec6Fw*)a$@+`wKTm}pxd6z*GL}}k0Y$c?; zSK{+Nu=C_ItgGP+r#=3sLv`Av^1R7Q{slmXh?N3)pSs`V(-F6}o=ZV@Y>1aaAQP78 zPUNdw|tHMZ+ca8DT!rFVpN5;J zHw}zkFsN}mbXsE$mQaYEB@v~>Sw+?zIe-;AVUQ~cNQT@^b|Z>fsCp&{00TfHl1Gr3 zf;fi_N+KBoGU*!!EZp0!sjQ+42eTNndsNqAH&z9)mdKOQ2=$S`oYeFa2!^o(fmXYe zyR+(aQ5&ku-qqHL+)hhO3(out0Q+{%aCJ!?(J4FXVj7q!2TlU0VQoHE@qv|304%f)Y`k~-gIgbz22NN5C$?I~3*Iu{ zbUjcE+>Z`!_J6^TZMQ@VUI0Ap-Q~8fa?jxJeO$x~KqEx$>n`)5HSc=Mjex};xPS13 z#h>{6m3!X)7j;5$@~M6a7gbK_jW z+=`48xvwIza;Ui!>as$B>VW9ALcKs-zfinD%H39|8<*cMzKt`0R5+U|PSu$^q#ZSF zP*)1|SfQSJ+dYk*_B1yK3S4E7^~4|Xp~Cdq%j;*p;QRjYDA@6YqJbG{#fJXylXcOG z2&KrN6&ZZM4^~*H`$os>|EvD+43Ie0AK(lwY?*DBSn=ZH5wMh$*tDMYUdehQ>&k32i1;1SUUgyh-DoA>28qXxk0 z4+8+y*hceVa+258Ao*}`&ihTj$uEGlI=%W4T=qS43_MM6uR`6b3>SU&kBk*gmNQoJ z_u}V~UE_(uQ$e&o2A2X$YR_Oawb~DmgQ@m9gr67QrvP-+pdx?{mZmOJtN&Dk4E(Z! zuc;8(ohX68+od2vJ04=V7Qd>&6(R|shbvlEksxZ42qrFxDFugT0cuFK6WI5J9Vgyk zk4U2Ql+7p!)C=Q4FT*cRKmK1xh&cHG>IN2gC-C&}egM~L4X%40xC}1p2+t_Ou>tO# z86{=dET9NG@I8DTB~O?Lco{r7vORyzn>Ietc~+x7BlE z<uyU>3_uTD?TN8i#C#Ak)R^PF$zL$&KcBBQ)d5AhOv2tNM)?11VTCu^c*w7QI z$@5|{Sl|jWc1qWZ*Vc`Pk^W83zr?J;SGGfK6^`?_mk$lx?!48xdFpP<)}a@z(80pV zLTp|CvP0ih6qnz zd}lIo>fGe1?|x_MtnI7rWrRJ4&0ipIWugM_H3o9xBz`DBAK)L3X}0fec=P}RZ1$v@ zv$@l$q(K(vNU-e$Q!fBny1|B@4a@13AWHcs9?f9w3Fe(OSog5u}-r#P#a^jVsYmm^&jmJq#t zw});GZGL3+PEwu@J&^%;{iT4=9@;<{VL?NZfPL7n`PzOdARxd2E!Yl8LoN<#;dVrd zxHtqjDn(s91UM$eTpR{GEDZ~Y2s;HY*jY~lZk&Xa#1Sclqf#2jqzoRGvN$f~a6-ys zK^g(y(wUEk$k;eQGLDnip$A>ze{?BVmzE}QTAIWe=^W02cW%I*Cl|;RiIND3kzt&F z9+EEN5j=_oc*o#3jz?4hr*YwVSek}Ni(VdQJsAW3_Sx7&o4!G&6FgQ=C{7-6Q{xC~ zjfw&rTCCNZ;@UdEU{hRgauBNaS{k8%a$7_vRI!Mx8I-hTwPSYMT=9{if&oyZp|pwF zQCh_IJ)X6FvrnLZZ2O+@e#Z9AJYo5Its%& zmujj_N|b1Je1I*5ZV}7Q4Tj`viz>3&+9psDJ899b-h$GVG(sLJJv+N&8hQzL+Z|Kh z1e2v2x}AQ5eDDb7Gwl@2Q&O+Kua_*Uw%Y`=)PlHJ*{DP1R_e8COMYEUhc!o@-?>4^s$%Yqg$Et9&-jC)3Rt68vXshUYc;75me6Xp%Sk1XE&Y*m!2 zx5et6T4RZ&Ps7QO8_`Y3++;qBu*|LcO7#xk+qEXNYh0Q-<;2E<$K*e*tko*F>h2*p z6K4OV-_Yi~1M#0ZGah@Ur2G$sg;zSfj*7mT8QOVvyLf?54HO z;&qR?FYT#&ybimP&Stx|(?wImPD7fpbTCb=N()vZxk7S~rRRgsQzKCr;go4=y> zenhWe0THPo4*96k3(pr&_@|3JK9GnXKb>M@(yRR&geuR<(&LDw-)DtfA-a$y=x!L z?0)kS%{z0KLGky5WqS1K;B)N@Shk zl#`ft5;^{*3$Nlsd0!u;k4MnN1t&8Jv7E^%XLQVgVPW>b>W^Om?^o%_+aOClySR60 b_tJiLU-^N2PxcExXWsf7MFbzc25bEXMWD!R literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/environment.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/environment.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de842dfdd92a3644ebdcd8f749e723f7b859ceee GIT binary patch literal 76666 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/exceptions.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1cb43ad684ef3ecefafff2f40ed38b9e6e6bf877 GIT binary patch literal 7704 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 E2M|u26aWAK literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/ext.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/ext.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ba1e56ac3f6d4e81c5dcbf06ebdb783b1aabd3b GIT binary patch literal 41897 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/filters.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/filters.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f870b34396d3eeb56bafa36f36906fed32dae5c0 GIT binary patch literal 72314 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/nativetypes.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/nativetypes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e3a1593da20ca31da5e9a53d6b3fa7fa56b4c1df GIT binary patch literal 6995 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/optimizer.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/optimizer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09519b11e09a3409dd070deac65bfa1551e8d2bd GIT binary patch literal 2673 zcmaJ@U2Ggh67HUzo&EFLPQ1oW!cR*vnB~H55(wfj3M9cmf^3lDm+T2fwsjm8}s%yW^=d%dt+i(6oD-4AGBNs8jTG9H)Fs&d5Rgi-n&BGPVNjSQfsOuFS zBjA%>vTjrijHp)Y?S8do;s(eVUb>#CWI#Se^;%)eahG+{PR7Y@e^xb|+*!Sn)6fgZ z={bv>JTt zJxXn>DQK*MI1KLfSs*VCEKq29o;nmZYqw)fBOBr2t z!8T<8YX zfXp*~8XSrYkB+IMghh5sCSd}IH4)1#u4{Y3_F@)rmkD&jEdGlvRb`X+&y;+8KYN|eJ^Zrq=9 zIrHmaN2p7|Fh@7HxJ=d1;52A*-`1qsF3AI#C+mNOVFd;9E=6s8rOUsm1>H=DGP}s0gZLdxO3Dm6^$_Sa-9Al2Bd6AS1x@DbqQ*ioO6X6 z6caoJdtAB`W;O~&NEqOT+b@=9S)G<`=1sTCHq=1SDue#Hvgb~h8?9iL`Hz(j94vz> z`e?(lPg^rolxwbEvksOw)Rh{oFuk$b3A$y`i+wPhK{pMwWA}%Dy4L^Dnd3j^dft2D z+!OCVxbn#IBOkgK*#&mlxpx0!*K&`4r$4?9^2nA$OmP&}mtf~@h$=-D46l>sq#O-i ziG-7YS46#Q@h8#j=+xNmeR7h&vyZpeQ%x_SDt3&sJr&>>zCJOjfW87Skg zGEgG0g4(!^OyDAFK~_u_v5G`d3w1%gs#*y786kWJOkvtIUEd8%b8+WJcuVo#y&xf^ zvuDuSo`;t7f2D`{04!CAL*3#tA~a6V%lQtwWLEdVZ+$=1!3TJ2ZJg8F=pD$KHlD$4 z?Nj^@!$s}HXZT(GCJvJDjiNUGbfGYLOm?QQx5`=FZ2AFj zLU(b(v?WZaY$)A5a7R?8+2i#FgRV90(PEO{BllGe4@*wM5oS!&_ADVxQ^*)WA(JyI zfB1c{TpZd=ojYuM01m|k7;dSm>>oTgzuGr)Y3jSavCGeW-#2#k`PII$CH-a^_1tsb z`Dpy&i3=xw$c=WiRDSTsp?g=}SbpRB(1F#V18arhm5Jqv&!;YpTzdV}(W}9=!qaPe z9=JGmVQh_zTqpZi$^K3X4If-b`tZ&U(s%6a^rQa5QtsBx6e{exKCpLnVDE1T=LgsN z3pe@(mU5jGEN%-K#H&YgM-HQ}4-Xt2Ong(oFcx)%%;Hc`lCCk|^8T`=O}wf~ak7|| z<>Om*YMNozG@;u=^8vocG+%F8UUb5T;0hHPF8{yq9vH&J5x=Fd3-euQZR&ZZ3H3Sx z=R)?6XCs7F>B~U|OdN&b)>*W!;pAw?*pV!(6?UzhSU%B7NPhI;zcoK;M*Ne1{wn!p zQt>x3z3=TlxBGudkogtC5LK~MTFi$j({z{(W*b3E{)LDvj7$zwD#;keMUa z=CK#YkG*t!@&$#F>W`6U5=-?`eU73F;4attAQ&3 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/parser.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/parser.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96cfca82ec94a19702886f3e8c29e6269f6f0148 GIT binary patch literal 61187 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 U=l&yNsn{_)@Sg?zu$Aop1J;qrLjV8( literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/sandbox.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/sandbox.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2cdeee3cf60af7318d68abf3b49debfe3eec6439 GIT binary patch literal 18091 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/tests.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/tests.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28011d134ae1bfe2dbe00daa94d2825e905cdedc GIT binary patch literal 9034 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 K0w~9qhyMw((+K|n literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/__pycache__/visitor.cpython-312.pyc b/venv/lib/python3.12/site-packages/jinja2/__pycache__/visitor.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..31fb35cf2fe91288a8f5f99dbc8a5dc19ee6e7ac GIT binary patch literal 5349 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 NUp)P90vGPoe*s4@CeQ!? literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/jinja2/_identifier.py b/venv/lib/python3.12/site-packages/jinja2/_identifier.py new file mode 100644 index 0000000..928c150 --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2/_identifier.py @@ -0,0 +1,6 @@ +import re + +# generated by scripts/generate_identifier_pattern.py +pattern = re.compile( + r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࣓-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣ৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఄా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳷-᳹᷀-᷹᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꣿꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𐴤-𐽆𐴧-𐽐𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑄴𑅅𑅆𑅳𑆀-𑆂𑆳-𑇀𑇉-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌻𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑑞𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑠬-𑠺𑨁-𑨊𑨳-𑨹𑨻-𑨾𑩇𑩑-𑩛𑪊-𑪙𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𑴱-𑴶𑴺𑴼𑴽𑴿-𑵅𑵇𑶊-𑶎𑶐𑶑𑶓-𑶗𑻳-𑻶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950 +) diff --git a/venv/lib/python3.12/site-packages/jinja2/async_utils.py b/venv/lib/python3.12/site-packages/jinja2/async_utils.py new file mode 100644 index 0000000..f0c1402 --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2/async_utils.py @@ -0,0 +1,99 @@ +import inspect +import typing as t +from functools import WRAPPER_ASSIGNMENTS +from functools import wraps + +from .utils import _PassArg +from .utils import pass_eval_context + +if t.TYPE_CHECKING: + import typing_extensions as te + +V = t.TypeVar("V") + + +def async_variant(normal_func): # type: ignore + def decorator(async_func): # type: ignore + pass_arg = _PassArg.from_obj(normal_func) + need_eval_context = pass_arg is None + + if pass_arg is _PassArg.environment: + + def is_async(args: t.Any) -> bool: + return t.cast(bool, args[0].is_async) + + else: + + def is_async(args: t.Any) -> bool: + return t.cast(bool, args[0].environment.is_async) + + # Take the doc and annotations from the sync function, but the + # name from the async function. Pallets-Sphinx-Themes + # build_function_directive expects __wrapped__ to point to the + # sync function. + async_func_attrs = ("__module__", "__name__", "__qualname__") + normal_func_attrs = tuple(set(WRAPPER_ASSIGNMENTS).difference(async_func_attrs)) + + @wraps(normal_func, assigned=normal_func_attrs) + @wraps(async_func, assigned=async_func_attrs, updated=()) + def wrapper(*args, **kwargs): # type: ignore + b = is_async(args) + + if need_eval_context: + args = args[1:] + + if b: + return async_func(*args, **kwargs) + + return normal_func(*args, **kwargs) + + if need_eval_context: + wrapper = pass_eval_context(wrapper) + + wrapper.jinja_async_variant = True # type: ignore[attr-defined] + return wrapper + + return decorator + + +_common_primitives = {int, float, bool, str, list, dict, tuple, type(None)} + + +async def auto_await(value: t.Union[t.Awaitable["V"], "V"]) -> "V": + # Avoid a costly call to isawaitable + if type(value) in _common_primitives: + return t.cast("V", value) + + if inspect.isawaitable(value): + return await t.cast("t.Awaitable[V]", value) + + return value + + +class _IteratorToAsyncIterator(t.Generic[V]): + def __init__(self, iterator: "t.Iterator[V]"): + self._iterator = iterator + + def __aiter__(self) -> "te.Self": + return self + + async def __anext__(self) -> V: + try: + return next(self._iterator) + except StopIteration as e: + raise StopAsyncIteration(e.value) from e + + +def auto_aiter( + iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", +) -> "t.AsyncIterator[V]": + if hasattr(iterable, "__aiter__"): + return iterable.__aiter__() + else: + return _IteratorToAsyncIterator(iter(iterable)) + + +async def auto_to_list( + value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", +) -> t.List["V"]: + return [x async for x in auto_aiter(value)] diff --git a/venv/lib/python3.12/site-packages/jinja2/bccache.py b/venv/lib/python3.12/site-packages/jinja2/bccache.py new file mode 100644 index 0000000..ada8b09 --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2/bccache.py @@ -0,0 +1,408 @@ +"""The optional bytecode cache system. This is useful if you have very +complex template situations and the compilation of all those templates +slows down your application too much. + +Situations where this is useful are often forking web applications that +are initialized on the first request. +""" + +import errno +import fnmatch +import marshal +import os +import pickle +import stat +import sys +import tempfile +import typing as t +from hashlib import sha1 +from io import BytesIO +from types import CodeType + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .environment import Environment + + class _MemcachedClient(te.Protocol): + def get(self, key: str) -> bytes: ... + + def set( + self, key: str, value: bytes, timeout: t.Optional[int] = None + ) -> None: ... + + +bc_version = 5 +# Magic bytes to identify Jinja bytecode cache files. Contains the +# Python major and minor version to avoid loading incompatible bytecode +# if a project upgrades its Python version. +bc_magic = ( + b"j2" + + pickle.dumps(bc_version, 2) + + pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1], 2) +) + + +class Bucket: + """Buckets are used to store the bytecode for one template. It's created + and initialized by the bytecode cache and passed to the loading functions. + + The buckets get an internal checksum from the cache assigned and use this + to automatically reject outdated cache material. Individual bytecode + cache subclasses don't have to care about cache invalidation. + """ + + def __init__(self, environment: "Environment", key: str, checksum: str) -> None: + self.environment = environment + self.key = key + self.checksum = checksum + self.reset() + + def reset(self) -> None: + """Resets the bucket (unloads the bytecode).""" + self.code: t.Optional[CodeType] = None + + def load_bytecode(self, f: t.BinaryIO) -> None: + """Loads bytecode from a file or file like object.""" + # make sure the magic header is correct + magic = f.read(len(bc_magic)) + if magic != bc_magic: + self.reset() + return + # the source code of the file changed, we need to reload + checksum = pickle.load(f) + if self.checksum != checksum: + self.reset() + return + # if marshal_load fails then we need to reload + try: + self.code = marshal.load(f) + except (EOFError, ValueError, TypeError): + self.reset() + return + + def write_bytecode(self, f: t.IO[bytes]) -> None: + """Dump the bytecode into the file or file like object passed.""" + if self.code is None: + raise TypeError("can't write empty bucket") + f.write(bc_magic) + pickle.dump(self.checksum, f, 2) + marshal.dump(self.code, f) + + def bytecode_from_string(self, string: bytes) -> None: + """Load bytecode from bytes.""" + self.load_bytecode(BytesIO(string)) + + def bytecode_to_string(self) -> bytes: + """Return the bytecode as bytes.""" + out = BytesIO() + self.write_bytecode(out) + return out.getvalue() + + +class BytecodeCache: + """To implement your own bytecode cache you have to subclass this class + and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of + these methods are passed a :class:`~jinja2.bccache.Bucket`. + + A very basic bytecode cache that saves the bytecode on the file system:: + + from os import path + + class MyCache(BytecodeCache): + + def __init__(self, directory): + self.directory = directory + + def load_bytecode(self, bucket): + filename = path.join(self.directory, bucket.key) + if path.exists(filename): + with open(filename, 'rb') as f: + bucket.load_bytecode(f) + + def dump_bytecode(self, bucket): + filename = path.join(self.directory, bucket.key) + with open(filename, 'wb') as f: + bucket.write_bytecode(f) + + A more advanced version of a filesystem based bytecode cache is part of + Jinja. + """ + + def load_bytecode(self, bucket: Bucket) -> None: + """Subclasses have to override this method to load bytecode into a + bucket. If they are not able to find code in the cache for the + bucket, it must not do anything. + """ + raise NotImplementedError() + + def dump_bytecode(self, bucket: Bucket) -> None: + """Subclasses have to override this method to write the bytecode + from a bucket back to the cache. If it unable to do so it must not + fail silently but raise an exception. + """ + raise NotImplementedError() + + def clear(self) -> None: + """Clears the cache. This method is not used by Jinja but should be + implemented to allow applications to clear the bytecode cache used + by a particular environment. + """ + + def get_cache_key( + self, name: str, filename: t.Optional[t.Union[str]] = None + ) -> str: + """Returns the unique hash key for this template name.""" + hash = sha1(name.encode("utf-8")) + + if filename is not None: + hash.update(f"|{filename}".encode()) + + return hash.hexdigest() + + def get_source_checksum(self, source: str) -> str: + """Returns a checksum for the source.""" + return sha1(source.encode("utf-8")).hexdigest() + + def get_bucket( + self, + environment: "Environment", + name: str, + filename: t.Optional[str], + source: str, + ) -> Bucket: + """Return a cache bucket for the given template. All arguments are + mandatory but filename may be `None`. + """ + key = self.get_cache_key(name, filename) + checksum = self.get_source_checksum(source) + bucket = Bucket(environment, key, checksum) + self.load_bytecode(bucket) + return bucket + + def set_bucket(self, bucket: Bucket) -> None: + """Put the bucket into the cache.""" + self.dump_bytecode(bucket) + + +class FileSystemBytecodeCache(BytecodeCache): + """A bytecode cache that stores bytecode on the filesystem. It accepts + two arguments: The directory where the cache items are stored and a + pattern string that is used to build the filename. + + If no directory is specified a default cache directory is selected. On + Windows the user's temp directory is used, on UNIX systems a directory + is created for the user in the system temp directory. + + The pattern can be used to have multiple separate caches operate on the + same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s`` + is replaced with the cache key. + + >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache') + + This bytecode cache supports clearing of the cache using the clear method. + """ + + def __init__( + self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache" + ) -> None: + if directory is None: + directory = self._get_default_cache_dir() + self.directory = directory + self.pattern = pattern + + def _get_default_cache_dir(self) -> str: + def _unsafe_dir() -> "te.NoReturn": + raise RuntimeError( + "Cannot determine safe temp directory. You " + "need to explicitly provide one." + ) + + tmpdir = tempfile.gettempdir() + + # On windows the temporary directory is used specific unless + # explicitly forced otherwise. We can just use that. + if os.name == "nt": + return tmpdir + if not hasattr(os, "getuid"): + _unsafe_dir() + + dirname = f"_jinja2-cache-{os.getuid()}" + actual_dir = os.path.join(tmpdir, dirname) + + try: + os.mkdir(actual_dir, stat.S_IRWXU) + except OSError as e: + if e.errno != errno.EEXIST: + raise + try: + os.chmod(actual_dir, stat.S_IRWXU) + actual_dir_stat = os.lstat(actual_dir) + if ( + actual_dir_stat.st_uid != os.getuid() + or not stat.S_ISDIR(actual_dir_stat.st_mode) + or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU + ): + _unsafe_dir() + except OSError as e: + if e.errno != errno.EEXIST: + raise + + actual_dir_stat = os.lstat(actual_dir) + if ( + actual_dir_stat.st_uid != os.getuid() + or not stat.S_ISDIR(actual_dir_stat.st_mode) + or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU + ): + _unsafe_dir() + + return actual_dir + + def _get_cache_filename(self, bucket: Bucket) -> str: + return os.path.join(self.directory, self.pattern % (bucket.key,)) + + def load_bytecode(self, bucket: Bucket) -> None: + filename = self._get_cache_filename(bucket) + + # Don't test for existence before opening the file, since the + # file could disappear after the test before the open. + try: + f = open(filename, "rb") + except (FileNotFoundError, IsADirectoryError, PermissionError): + # PermissionError can occur on Windows when an operation is + # in progress, such as calling clear(). + return + + with f: + bucket.load_bytecode(f) + + def dump_bytecode(self, bucket: Bucket) -> None: + # Write to a temporary file, then rename to the real name after + # writing. This avoids another process reading the file before + # it is fully written. + name = self._get_cache_filename(bucket) + f = tempfile.NamedTemporaryFile( + mode="wb", + dir=os.path.dirname(name), + prefix=os.path.basename(name), + suffix=".tmp", + delete=False, + ) + + def remove_silent() -> None: + try: + os.remove(f.name) + except OSError: + # Another process may have called clear(). On Windows, + # another program may be holding the file open. + pass + + try: + with f: + bucket.write_bytecode(f) + except BaseException: + remove_silent() + raise + + try: + os.replace(f.name, name) + except OSError: + # Another process may have called clear(). On Windows, + # another program may be holding the file open. + remove_silent() + except BaseException: + remove_silent() + raise + + def clear(self) -> None: + # imported lazily here because google app-engine doesn't support + # write access on the file system and the function does not exist + # normally. + from os import remove + + files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",)) + for filename in files: + try: + remove(os.path.join(self.directory, filename)) + except OSError: + pass + + +class MemcachedBytecodeCache(BytecodeCache): + """This class implements a bytecode cache that uses a memcache cache for + storing the information. It does not enforce a specific memcache library + (tummy's memcache or cmemcache) but will accept any class that provides + the minimal interface required. + + Libraries compatible with this class: + + - `cachelib `_ + - `python-memcached `_ + + (Unfortunately the django cache interface is not compatible because it + does not support storing binary data, only text. You can however pass + the underlying cache client to the bytecode cache which is available + as `django.core.cache.cache._client`.) + + The minimal interface for the client passed to the constructor is this: + + .. class:: MinimalClientInterface + + .. method:: set(key, value[, timeout]) + + Stores the bytecode in the cache. `value` is a string and + `timeout` the timeout of the key. If timeout is not provided + a default timeout or no timeout should be assumed, if it's + provided it's an integer with the number of seconds the cache + item should exist. + + .. method:: get(key) + + Returns the value for the cache key. If the item does not + exist in the cache the return value must be `None`. + + The other arguments to the constructor are the prefix for all keys that + is added before the actual cache key and the timeout for the bytecode in + the cache system. We recommend a high (or no) timeout. + + This bytecode cache does not support clearing of used items in the cache. + The clear method is a no-operation function. + + .. versionadded:: 2.7 + Added support for ignoring memcache errors through the + `ignore_memcache_errors` parameter. + """ + + def __init__( + self, + client: "_MemcachedClient", + prefix: str = "jinja2/bytecode/", + timeout: t.Optional[int] = None, + ignore_memcache_errors: bool = True, + ): + self.client = client + self.prefix = prefix + self.timeout = timeout + self.ignore_memcache_errors = ignore_memcache_errors + + def load_bytecode(self, bucket: Bucket) -> None: + try: + code = self.client.get(self.prefix + bucket.key) + except Exception: + if not self.ignore_memcache_errors: + raise + else: + bucket.bytecode_from_string(code) + + def dump_bytecode(self, bucket: Bucket) -> None: + key = self.prefix + bucket.key + value = bucket.bytecode_to_string() + + try: + if self.timeout is not None: + self.client.set(key, value, self.timeout) + else: + self.client.set(key, value) + except Exception: + if not self.ignore_memcache_errors: + raise diff --git a/venv/lib/python3.12/site-packages/jinja2/compiler.py b/venv/lib/python3.12/site-packages/jinja2/compiler.py new file mode 100644 index 0000000..a4ff6a1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2/compiler.py @@ -0,0 +1,1998 @@ +"""Compiles nodes from the parser into Python code.""" + +import typing as t +from contextlib import contextmanager +from functools import update_wrapper +from io import StringIO +from itertools import chain +from keyword import iskeyword as is_python_keyword + +from markupsafe import escape +from markupsafe import Markup + +from . import nodes +from .exceptions import TemplateAssertionError +from .idtracking import Symbols +from .idtracking import VAR_LOAD_ALIAS +from .idtracking import VAR_LOAD_PARAMETER +from .idtracking import VAR_LOAD_RESOLVE +from .idtracking import VAR_LOAD_UNDEFINED +from .nodes import EvalContext +from .optimizer import Optimizer +from .utils import _PassArg +from .utils import concat +from .visitor import NodeVisitor + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .environment import Environment + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + +operators = { + "eq": "==", + "ne": "!=", + "gt": ">", + "gteq": ">=", + "lt": "<", + "lteq": "<=", + "in": "in", + "notin": "not in", +} + + +def optimizeconst(f: F) -> F: + def new_func( + self: "CodeGenerator", node: nodes.Expr, frame: "Frame", **kwargs: t.Any + ) -> t.Any: + # Only optimize if the frame is not volatile + if self.optimizer is not None and not frame.eval_ctx.volatile: + new_node = self.optimizer.visit(node, frame.eval_ctx) + + if new_node != node: + return self.visit(new_node, frame) + + return f(self, node, frame, **kwargs) + + return update_wrapper(new_func, f) # type: ignore[return-value] + + +def _make_binop(op: str) -> t.Callable[["CodeGenerator", nodes.BinExpr, "Frame"], None]: + @optimizeconst + def visitor(self: "CodeGenerator", node: nodes.BinExpr, frame: Frame) -> None: + if ( + self.environment.sandboxed and op in self.environment.intercepted_binops # type: ignore + ): + self.write(f"environment.call_binop(context, {op!r}, ") + self.visit(node.left, frame) + self.write(", ") + self.visit(node.right, frame) + else: + self.write("(") + self.visit(node.left, frame) + self.write(f" {op} ") + self.visit(node.right, frame) + + self.write(")") + + return visitor + + +def _make_unop( + op: str, +) -> t.Callable[["CodeGenerator", nodes.UnaryExpr, "Frame"], None]: + @optimizeconst + def visitor(self: "CodeGenerator", node: nodes.UnaryExpr, frame: Frame) -> None: + if ( + self.environment.sandboxed and op in self.environment.intercepted_unops # type: ignore + ): + self.write(f"environment.call_unop(context, {op!r}, ") + self.visit(node.node, frame) + else: + self.write("(" + op) + self.visit(node.node, frame) + + self.write(")") + + return visitor + + +def generate( + node: nodes.Template, + environment: "Environment", + name: t.Optional[str], + filename: t.Optional[str], + stream: t.Optional[t.TextIO] = None, + defer_init: bool = False, + optimized: bool = True, +) -> t.Optional[str]: + """Generate the python source for a node tree.""" + if not isinstance(node, nodes.Template): + raise TypeError("Can't compile non template nodes") + + generator = environment.code_generator_class( + environment, name, filename, stream, defer_init, optimized + ) + generator.visit(node) + + if stream is None: + return generator.stream.getvalue() # type: ignore + + return None + + +def has_safe_repr(value: t.Any) -> bool: + """Does the node have a safe representation?""" + if value is None or value is NotImplemented or value is Ellipsis: + return True + + if type(value) in {bool, int, float, complex, range, str, Markup}: + return True + + if type(value) in {tuple, list, set, frozenset}: + return all(has_safe_repr(v) for v in value) + + if type(value) is dict: # noqa E721 + return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items()) + + return False + + +def find_undeclared( + nodes: t.Iterable[nodes.Node], names: t.Iterable[str] +) -> t.Set[str]: + """Check if the names passed are accessed undeclared. The return value + is a set of all the undeclared names from the sequence of names found. + """ + visitor = UndeclaredNameVisitor(names) + try: + for node in nodes: + visitor.visit(node) + except VisitorExit: + pass + return visitor.undeclared + + +class MacroRef: + def __init__(self, node: t.Union[nodes.Macro, nodes.CallBlock]) -> None: + self.node = node + self.accesses_caller = False + self.accesses_kwargs = False + self.accesses_varargs = False + + +class Frame: + """Holds compile time information for us.""" + + def __init__( + self, + eval_ctx: EvalContext, + parent: t.Optional["Frame"] = None, + level: t.Optional[int] = None, + ) -> None: + self.eval_ctx = eval_ctx + + # the parent of this frame + self.parent = parent + + if parent is None: + self.symbols = Symbols(level=level) + + # in some dynamic inheritance situations the compiler needs to add + # write tests around output statements. + self.require_output_check = False + + # inside some tags we are using a buffer rather than yield statements. + # this for example affects {% filter %} or {% macro %}. If a frame + # is buffered this variable points to the name of the list used as + # buffer. + self.buffer: t.Optional[str] = None + + # the name of the block we're in, otherwise None. + self.block: t.Optional[str] = None + + else: + self.symbols = Symbols(parent.symbols, level=level) + self.require_output_check = parent.require_output_check + self.buffer = parent.buffer + self.block = parent.block + + # a toplevel frame is the root + soft frames such as if conditions. + self.toplevel = False + + # the root frame is basically just the outermost frame, so no if + # conditions. This information is used to optimize inheritance + # situations. + self.rootlevel = False + + # variables set inside of loops and blocks should not affect outer frames, + # but they still needs to be kept track of as part of the active context. + self.loop_frame = False + self.block_frame = False + + # track whether the frame is being used in an if-statement or conditional + # expression as it determines which errors should be raised during runtime + # or compile time. + self.soft_frame = False + + def copy(self) -> "te.Self": + """Create a copy of the current one.""" + rv = object.__new__(self.__class__) + rv.__dict__.update(self.__dict__) + rv.symbols = self.symbols.copy() + return rv + + def inner(self, isolated: bool = False) -> "Frame": + """Return an inner frame.""" + if isolated: + return Frame(self.eval_ctx, level=self.symbols.level + 1) + return Frame(self.eval_ctx, self) + + def soft(self) -> "te.Self": + """Return a soft frame. A soft frame may not be modified as + standalone thing as it shares the resources with the frame it + was created of, but it's not a rootlevel frame any longer. + + This is only used to implement if-statements and conditional + expressions. + """ + rv = self.copy() + rv.rootlevel = False + rv.soft_frame = True + return rv + + __copy__ = copy + + +class VisitorExit(RuntimeError): + """Exception used by the `UndeclaredNameVisitor` to signal a stop.""" + + +class DependencyFinderVisitor(NodeVisitor): + """A visitor that collects filter and test calls.""" + + def __init__(self) -> None: + self.filters: t.Set[str] = set() + self.tests: t.Set[str] = set() + + def visit_Filter(self, node: nodes.Filter) -> None: + self.generic_visit(node) + self.filters.add(node.name) + + def visit_Test(self, node: nodes.Test) -> None: + self.generic_visit(node) + self.tests.add(node.name) + + def visit_Block(self, node: nodes.Block) -> None: + """Stop visiting at blocks.""" + + +class UndeclaredNameVisitor(NodeVisitor): + """A visitor that checks if a name is accessed without being + declared. This is different from the frame visitor as it will + not stop at closure frames. + """ + + def __init__(self, names: t.Iterable[str]) -> None: + self.names = set(names) + self.undeclared: t.Set[str] = set() + + def visit_Name(self, node: nodes.Name) -> None: + if node.ctx == "load" and node.name in self.names: + self.undeclared.add(node.name) + if self.undeclared == self.names: + raise VisitorExit() + else: + self.names.discard(node.name) + + def visit_Block(self, node: nodes.Block) -> None: + """Stop visiting a blocks.""" + + +class CompilerExit(Exception): + """Raised if the compiler encountered a situation where it just + doesn't make sense to further process the code. Any block that + raises such an exception is not further processed. + """ + + +class CodeGenerator(NodeVisitor): + def __init__( + self, + environment: "Environment", + name: t.Optional[str], + filename: t.Optional[str], + stream: t.Optional[t.TextIO] = None, + defer_init: bool = False, + optimized: bool = True, + ) -> None: + if stream is None: + stream = StringIO() + self.environment = environment + self.name = name + self.filename = filename + self.stream = stream + self.created_block_context = False + self.defer_init = defer_init + self.optimizer: t.Optional[Optimizer] = None + + if optimized: + self.optimizer = Optimizer(environment) + + # aliases for imports + self.import_aliases: t.Dict[str, str] = {} + + # a registry for all blocks. Because blocks are moved out + # into the global python scope they are registered here + self.blocks: t.Dict[str, nodes.Block] = {} + + # the number of extends statements so far + self.extends_so_far = 0 + + # some templates have a rootlevel extends. In this case we + # can safely assume that we're a child template and do some + # more optimizations. + self.has_known_extends = False + + # the current line number + self.code_lineno = 1 + + # registry of all filters and tests (global, not block local) + self.tests: t.Dict[str, str] = {} + self.filters: t.Dict[str, str] = {} + + # the debug information + self.debug_info: t.List[t.Tuple[int, int]] = [] + self._write_debug_info: t.Optional[int] = None + + # the number of new lines before the next write() + self._new_lines = 0 + + # the line number of the last written statement + self._last_line = 0 + + # true if nothing was written so far. + self._first_write = True + + # used by the `temporary_identifier` method to get new + # unique, temporary identifier + self._last_identifier = 0 + + # the current indentation + self._indentation = 0 + + # Tracks toplevel assignments + self._assign_stack: t.List[t.Set[str]] = [] + + # Tracks parameter definition blocks + self._param_def_block: t.List[t.Set[str]] = [] + + # Tracks the current context. + self._context_reference_stack = ["context"] + + @property + def optimized(self) -> bool: + return self.optimizer is not None + + # -- Various compilation helpers + + def fail(self, msg: str, lineno: int) -> "te.NoReturn": + """Fail with a :exc:`TemplateAssertionError`.""" + raise TemplateAssertionError(msg, lineno, self.name, self.filename) + + def temporary_identifier(self) -> str: + """Get a new unique identifier.""" + self._last_identifier += 1 + return f"t_{self._last_identifier}" + + def buffer(self, frame: Frame) -> None: + """Enable buffering for the frame from that point onwards.""" + frame.buffer = self.temporary_identifier() + self.writeline(f"{frame.buffer} = []") + + def return_buffer_contents( + self, frame: Frame, force_unescaped: bool = False + ) -> None: + """Return the buffer contents of the frame.""" + if not force_unescaped: + if frame.eval_ctx.volatile: + self.writeline("if context.eval_ctx.autoescape:") + self.indent() + self.writeline(f"return Markup(concat({frame.buffer}))") + self.outdent() + self.writeline("else:") + self.indent() + self.writeline(f"return concat({frame.buffer})") + self.outdent() + return + elif frame.eval_ctx.autoescape: + self.writeline(f"return Markup(concat({frame.buffer}))") + return + self.writeline(f"return concat({frame.buffer})") + + def indent(self) -> None: + """Indent by one.""" + self._indentation += 1 + + def outdent(self, step: int = 1) -> None: + """Outdent by step.""" + self._indentation -= step + + def start_write(self, frame: Frame, node: t.Optional[nodes.Node] = None) -> None: + """Yield or write into the frame buffer.""" + if frame.buffer is None: + self.writeline("yield ", node) + else: + self.writeline(f"{frame.buffer}.append(", node) + + def end_write(self, frame: Frame) -> None: + """End the writing process started by `start_write`.""" + if frame.buffer is not None: + self.write(")") + + def simple_write( + self, s: str, frame: Frame, node: t.Optional[nodes.Node] = None + ) -> None: + """Simple shortcut for start_write + write + end_write.""" + self.start_write(frame, node) + self.write(s) + self.end_write(frame) + + def blockvisit(self, nodes: t.Iterable[nodes.Node], frame: Frame) -> None: + """Visit a list of nodes as block in a frame. If the current frame + is no buffer a dummy ``if 0: yield None`` is written automatically. + """ + try: + self.writeline("pass") + for node in nodes: + self.visit(node, frame) + except CompilerExit: + pass + + def write(self, x: str) -> None: + """Write a string into the output stream.""" + if self._new_lines: + if not self._first_write: + self.stream.write("\n" * self._new_lines) + self.code_lineno += self._new_lines + if self._write_debug_info is not None: + self.debug_info.append((self._write_debug_info, self.code_lineno)) + self._write_debug_info = None + self._first_write = False + self.stream.write(" " * self._indentation) + self._new_lines = 0 + self.stream.write(x) + + def writeline( + self, x: str, node: t.Optional[nodes.Node] = None, extra: int = 0 + ) -> None: + """Combination of newline and write.""" + self.newline(node, extra) + self.write(x) + + def newline(self, node: t.Optional[nodes.Node] = None, extra: int = 0) -> None: + """Add one or more newlines before the next write.""" + self._new_lines = max(self._new_lines, 1 + extra) + if node is not None and node.lineno != self._last_line: + self._write_debug_info = node.lineno + self._last_line = node.lineno + + def signature( + self, + node: t.Union[nodes.Call, nodes.Filter, nodes.Test], + frame: Frame, + extra_kwargs: t.Optional[t.Mapping[str, t.Any]] = None, + ) -> None: + """Writes a function call to the stream for the current node. + A leading comma is added automatically. The extra keyword + arguments may not include python keywords otherwise a syntax + error could occur. The extra keyword arguments should be given + as python dict. + """ + # if any of the given keyword arguments is a python keyword + # we have to make sure that no invalid call is created. + kwarg_workaround = any( + is_python_keyword(t.cast(str, k)) + for k in chain((x.key for x in node.kwargs), extra_kwargs or ()) + ) + + for arg in node.args: + self.write(", ") + self.visit(arg, frame) + + if not kwarg_workaround: + for kwarg in node.kwargs: + self.write(", ") + self.visit(kwarg, frame) + if extra_kwargs is not None: + for key, value in extra_kwargs.items(): + self.write(f", {key}={value}") + if node.dyn_args: + self.write(", *") + self.visit(node.dyn_args, frame) + + if kwarg_workaround: + if node.dyn_kwargs is not None: + self.write(", **dict({") + else: + self.write(", **{") + for kwarg in node.kwargs: + self.write(f"{kwarg.key!r}: ") + self.visit(kwarg.value, frame) + self.write(", ") + if extra_kwargs is not None: + for key, value in extra_kwargs.items(): + self.write(f"{key!r}: {value}, ") + if node.dyn_kwargs is not None: + self.write("}, **") + self.visit(node.dyn_kwargs, frame) + self.write(")") + else: + self.write("}") + + elif node.dyn_kwargs is not None: + self.write(", **") + self.visit(node.dyn_kwargs, frame) + + def pull_dependencies(self, nodes: t.Iterable[nodes.Node]) -> None: + """Find all filter and test names used in the template and + assign them to variables in the compiled namespace. Checking + that the names are registered with the environment is done when + compiling the Filter and Test nodes. If the node is in an If or + CondExpr node, the check is done at runtime instead. + + .. versionchanged:: 3.0 + Filters and tests in If and CondExpr nodes are checked at + runtime instead of compile time. + """ + visitor = DependencyFinderVisitor() + + for node in nodes: + visitor.visit(node) + + for id_map, names, dependency in ( + (self.filters, visitor.filters, "filters"), + ( + self.tests, + visitor.tests, + "tests", + ), + ): + for name in sorted(names): + if name not in id_map: + id_map[name] = self.temporary_identifier() + + # add check during runtime that dependencies used inside of executed + # blocks are defined, as this step may be skipped during compile time + self.writeline("try:") + self.indent() + self.writeline(f"{id_map[name]} = environment.{dependency}[{name!r}]") + self.outdent() + self.writeline("except KeyError:") + self.indent() + self.writeline("@internalcode") + self.writeline(f"def {id_map[name]}(*unused):") + self.indent() + self.writeline( + f'raise TemplateRuntimeError("No {dependency[:-1]}' + f' named {name!r} found.")' + ) + self.outdent() + self.outdent() + + def enter_frame(self, frame: Frame) -> None: + undefs = [] + for target, (action, param) in frame.symbols.loads.items(): + if action == VAR_LOAD_PARAMETER: + pass + elif action == VAR_LOAD_RESOLVE: + self.writeline(f"{target} = {self.get_resolve_func()}({param!r})") + elif action == VAR_LOAD_ALIAS: + self.writeline(f"{target} = {param}") + elif action == VAR_LOAD_UNDEFINED: + undefs.append(target) + else: + raise NotImplementedError("unknown load instruction") + if undefs: + self.writeline(f"{' = '.join(undefs)} = missing") + + def leave_frame(self, frame: Frame, with_python_scope: bool = False) -> None: + if not with_python_scope: + undefs = [] + for target in frame.symbols.loads: + undefs.append(target) + if undefs: + self.writeline(f"{' = '.join(undefs)} = missing") + + def choose_async(self, async_value: str = "async ", sync_value: str = "") -> str: + return async_value if self.environment.is_async else sync_value + + def func(self, name: str) -> str: + return f"{self.choose_async()}def {name}" + + def macro_body( + self, node: t.Union[nodes.Macro, nodes.CallBlock], frame: Frame + ) -> t.Tuple[Frame, MacroRef]: + """Dump the function def of a macro or call block.""" + frame = frame.inner() + frame.symbols.analyze_node(node) + macro_ref = MacroRef(node) + + explicit_caller = None + skip_special_params = set() + args = [] + + for idx, arg in enumerate(node.args): + if arg.name == "caller": + explicit_caller = idx + if arg.name in ("kwargs", "varargs"): + skip_special_params.add(arg.name) + args.append(frame.symbols.ref(arg.name)) + + undeclared = find_undeclared(node.body, ("caller", "kwargs", "varargs")) + + if "caller" in undeclared: + # In older Jinja versions there was a bug that allowed caller + # to retain the special behavior even if it was mentioned in + # the argument list. However thankfully this was only really + # working if it was the last argument. So we are explicitly + # checking this now and error out if it is anywhere else in + # the argument list. + if explicit_caller is not None: + try: + node.defaults[explicit_caller - len(node.args)] + except IndexError: + self.fail( + "When defining macros or call blocks the " + 'special "caller" argument must be omitted ' + "or be given a default.", + node.lineno, + ) + else: + args.append(frame.symbols.declare_parameter("caller")) + macro_ref.accesses_caller = True + if "kwargs" in undeclared and "kwargs" not in skip_special_params: + args.append(frame.symbols.declare_parameter("kwargs")) + macro_ref.accesses_kwargs = True + if "varargs" in undeclared and "varargs" not in skip_special_params: + args.append(frame.symbols.declare_parameter("varargs")) + macro_ref.accesses_varargs = True + + # macros are delayed, they never require output checks + frame.require_output_check = False + frame.symbols.analyze_node(node) + self.writeline(f"{self.func('macro')}({', '.join(args)}):", node) + self.indent() + + self.buffer(frame) + self.enter_frame(frame) + + self.push_parameter_definitions(frame) + for idx, arg in enumerate(node.args): + ref = frame.symbols.ref(arg.name) + self.writeline(f"if {ref} is missing:") + self.indent() + try: + default = node.defaults[idx - len(node.args)] + except IndexError: + self.writeline( + f'{ref} = undefined("parameter {arg.name!r} was not provided",' + f" name={arg.name!r})" + ) + else: + self.writeline(f"{ref} = ") + self.visit(default, frame) + self.mark_parameter_stored(ref) + self.outdent() + self.pop_parameter_definitions() + + self.blockvisit(node.body, frame) + self.return_buffer_contents(frame, force_unescaped=True) + self.leave_frame(frame, with_python_scope=True) + self.outdent() + + return frame, macro_ref + + def macro_def(self, macro_ref: MacroRef, frame: Frame) -> None: + """Dump the macro definition for the def created by macro_body.""" + arg_tuple = ", ".join(repr(x.name) for x in macro_ref.node.args) + name = getattr(macro_ref.node, "name", None) + if len(macro_ref.node.args) == 1: + arg_tuple += "," + self.write( + f"Macro(environment, macro, {name!r}, ({arg_tuple})," + f" {macro_ref.accesses_kwargs!r}, {macro_ref.accesses_varargs!r}," + f" {macro_ref.accesses_caller!r}, context.eval_ctx.autoescape)" + ) + + def position(self, node: nodes.Node) -> str: + """Return a human readable position for the node.""" + rv = f"line {node.lineno}" + if self.name is not None: + rv = f"{rv} in {self.name!r}" + return rv + + def dump_local_context(self, frame: Frame) -> str: + items_kv = ", ".join( + f"{name!r}: {target}" + for name, target in frame.symbols.dump_stores().items() + ) + return f"{{{items_kv}}}" + + def write_commons(self) -> None: + """Writes a common preamble that is used by root and block functions. + Primarily this sets up common local helpers and enforces a generator + through a dead branch. + """ + self.writeline("resolve = context.resolve_or_missing") + self.writeline("undefined = environment.undefined") + self.writeline("concat = environment.concat") + # always use the standard Undefined class for the implicit else of + # conditional expressions + self.writeline("cond_expr_undefined = Undefined") + self.writeline("if 0: yield None") + + def push_parameter_definitions(self, frame: Frame) -> None: + """Pushes all parameter targets from the given frame into a local + stack that permits tracking of yet to be assigned parameters. In + particular this enables the optimization from `visit_Name` to skip + undefined expressions for parameters in macros as macros can reference + otherwise unbound parameters. + """ + self._param_def_block.append(frame.symbols.dump_param_targets()) + + def pop_parameter_definitions(self) -> None: + """Pops the current parameter definitions set.""" + self._param_def_block.pop() + + def mark_parameter_stored(self, target: str) -> None: + """Marks a parameter in the current parameter definitions as stored. + This will skip the enforced undefined checks. + """ + if self._param_def_block: + self._param_def_block[-1].discard(target) + + def push_context_reference(self, target: str) -> None: + self._context_reference_stack.append(target) + + def pop_context_reference(self) -> None: + self._context_reference_stack.pop() + + def get_context_ref(self) -> str: + return self._context_reference_stack[-1] + + def get_resolve_func(self) -> str: + target = self._context_reference_stack[-1] + if target == "context": + return "resolve" + return f"{target}.resolve" + + def derive_context(self, frame: Frame) -> str: + return f"{self.get_context_ref()}.derived({self.dump_local_context(frame)})" + + def parameter_is_undeclared(self, target: str) -> bool: + """Checks if a given target is an undeclared parameter.""" + if not self._param_def_block: + return False + return target in self._param_def_block[-1] + + def push_assign_tracking(self) -> None: + """Pushes a new layer for assignment tracking.""" + self._assign_stack.append(set()) + + def pop_assign_tracking(self, frame: Frame) -> None: + """Pops the topmost level for assignment tracking and updates the + context variables if necessary. + """ + vars = self._assign_stack.pop() + if ( + not frame.block_frame + and not frame.loop_frame + and not frame.toplevel + or not vars + ): + return + public_names = [x for x in vars if x[:1] != "_"] + if len(vars) == 1: + name = next(iter(vars)) + ref = frame.symbols.ref(name) + if frame.loop_frame: + self.writeline(f"_loop_vars[{name!r}] = {ref}") + return + if frame.block_frame: + self.writeline(f"_block_vars[{name!r}] = {ref}") + return + self.writeline(f"context.vars[{name!r}] = {ref}") + else: + if frame.loop_frame: + self.writeline("_loop_vars.update({") + elif frame.block_frame: + self.writeline("_block_vars.update({") + else: + self.writeline("context.vars.update({") + for idx, name in enumerate(sorted(vars)): + if idx: + self.write(", ") + ref = frame.symbols.ref(name) + self.write(f"{name!r}: {ref}") + self.write("})") + if not frame.block_frame and not frame.loop_frame and public_names: + if len(public_names) == 1: + self.writeline(f"context.exported_vars.add({public_names[0]!r})") + else: + names_str = ", ".join(map(repr, sorted(public_names))) + self.writeline(f"context.exported_vars.update(({names_str}))") + + # -- Statement Visitors + + def visit_Template( + self, node: nodes.Template, frame: t.Optional[Frame] = None + ) -> None: + assert frame is None, "no root frame allowed" + eval_ctx = EvalContext(self.environment, self.name) + + from .runtime import async_exported + from .runtime import exported + + if self.environment.is_async: + exported_names = sorted(exported + async_exported) + else: + exported_names = sorted(exported) + + self.writeline("from jinja2.runtime import " + ", ".join(exported_names)) + + # if we want a deferred initialization we cannot move the + # environment into a local name + envenv = "" if self.defer_init else ", environment=environment" + + # do we have an extends tag at all? If not, we can save some + # overhead by just not processing any inheritance code. + have_extends = node.find(nodes.Extends) is not None + + # find all blocks + for block in node.find_all(nodes.Block): + if block.name in self.blocks: + self.fail(f"block {block.name!r} defined twice", block.lineno) + self.blocks[block.name] = block + + # find all imports and import them + for import_ in node.find_all(nodes.ImportedName): + if import_.importname not in self.import_aliases: + imp = import_.importname + self.import_aliases[imp] = alias = self.temporary_identifier() + if "." in imp: + module, obj = imp.rsplit(".", 1) + self.writeline(f"from {module} import {obj} as {alias}") + else: + self.writeline(f"import {imp} as {alias}") + + # add the load name + self.writeline(f"name = {self.name!r}") + + # generate the root render function. + self.writeline( + f"{self.func('root')}(context, missing=missing{envenv}):", extra=1 + ) + self.indent() + self.write_commons() + + # process the root + frame = Frame(eval_ctx) + if "self" in find_undeclared(node.body, ("self",)): + ref = frame.symbols.declare_parameter("self") + self.writeline(f"{ref} = TemplateReference(context)") + frame.symbols.analyze_node(node) + frame.toplevel = frame.rootlevel = True + frame.require_output_check = have_extends and not self.has_known_extends + if have_extends: + self.writeline("parent_template = None") + self.enter_frame(frame) + self.pull_dependencies(node.body) + self.blockvisit(node.body, frame) + self.leave_frame(frame, with_python_scope=True) + self.outdent() + + # make sure that the parent root is called. + if have_extends: + if not self.has_known_extends: + self.indent() + self.writeline("if parent_template is not None:") + self.indent() + if not self.environment.is_async: + self.writeline("yield from parent_template.root_render_func(context)") + else: + self.writeline("agen = parent_template.root_render_func(context)") + self.writeline("try:") + self.indent() + self.writeline("async for event in agen:") + self.indent() + self.writeline("yield event") + self.outdent() + self.outdent() + self.writeline("finally: await agen.aclose()") + self.outdent(1 + (not self.has_known_extends)) + + # at this point we now have the blocks collected and can visit them too. + for name, block in self.blocks.items(): + self.writeline( + f"{self.func('block_' + name)}(context, missing=missing{envenv}):", + block, + 1, + ) + self.indent() + self.write_commons() + # It's important that we do not make this frame a child of the + # toplevel template. This would cause a variety of + # interesting issues with identifier tracking. + block_frame = Frame(eval_ctx) + block_frame.block_frame = True + undeclared = find_undeclared(block.body, ("self", "super")) + if "self" in undeclared: + ref = block_frame.symbols.declare_parameter("self") + self.writeline(f"{ref} = TemplateReference(context)") + if "super" in undeclared: + ref = block_frame.symbols.declare_parameter("super") + self.writeline(f"{ref} = context.super({name!r}, block_{name})") + block_frame.symbols.analyze_node(block) + block_frame.block = name + self.writeline("_block_vars = {}") + self.enter_frame(block_frame) + self.pull_dependencies(block.body) + self.blockvisit(block.body, block_frame) + self.leave_frame(block_frame, with_python_scope=True) + self.outdent() + + blocks_kv_str = ", ".join(f"{x!r}: block_{x}" for x in self.blocks) + self.writeline(f"blocks = {{{blocks_kv_str}}}", extra=1) + debug_kv_str = "&".join(f"{k}={v}" for k, v in self.debug_info) + self.writeline(f"debug_info = {debug_kv_str!r}") + + def visit_Block(self, node: nodes.Block, frame: Frame) -> None: + """Call a block and register it for the template.""" + level = 0 + if frame.toplevel: + # if we know that we are a child template, there is no need to + # check if we are one + if self.has_known_extends: + return + if self.extends_so_far > 0: + self.writeline("if parent_template is None:") + self.indent() + level += 1 + + if node.scoped: + context = self.derive_context(frame) + else: + context = self.get_context_ref() + + if node.required: + self.writeline(f"if len(context.blocks[{node.name!r}]) <= 1:", node) + self.indent() + self.writeline( + f'raise TemplateRuntimeError("Required block {node.name!r} not found")', + node, + ) + self.outdent() + + if not self.environment.is_async and frame.buffer is None: + self.writeline( + f"yield from context.blocks[{node.name!r}][0]({context})", node + ) + else: + self.writeline(f"gen = context.blocks[{node.name!r}][0]({context})") + self.writeline("try:") + self.indent() + self.writeline( + f"{self.choose_async()}for event in gen:", + node, + ) + self.indent() + self.simple_write("event", frame) + self.outdent() + self.outdent() + self.writeline( + f"finally: {self.choose_async('await gen.aclose()', 'gen.close()')}" + ) + + self.outdent(level) + + def visit_Extends(self, node: nodes.Extends, frame: Frame) -> None: + """Calls the extender.""" + if not frame.toplevel: + self.fail("cannot use extend from a non top-level scope", node.lineno) + + # if the number of extends statements in general is zero so + # far, we don't have to add a check if something extended + # the template before this one. + if self.extends_so_far > 0: + # if we have a known extends we just add a template runtime + # error into the generated code. We could catch that at compile + # time too, but i welcome it not to confuse users by throwing the + # same error at different times just "because we can". + if not self.has_known_extends: + self.writeline("if parent_template is not None:") + self.indent() + self.writeline('raise TemplateRuntimeError("extended multiple times")') + + # if we have a known extends already we don't need that code here + # as we know that the template execution will end here. + if self.has_known_extends: + raise CompilerExit() + else: + self.outdent() + + self.writeline("parent_template = environment.get_template(", node) + self.visit(node.template, frame) + self.write(f", {self.name!r})") + self.writeline("for name, parent_block in parent_template.blocks.items():") + self.indent() + self.writeline("context.blocks.setdefault(name, []).append(parent_block)") + self.outdent() + + # if this extends statement was in the root level we can take + # advantage of that information and simplify the generated code + # in the top level from this point onwards + if frame.rootlevel: + self.has_known_extends = True + + # and now we have one more + self.extends_so_far += 1 + + def visit_Include(self, node: nodes.Include, frame: Frame) -> None: + """Handles includes.""" + if node.ignore_missing: + self.writeline("try:") + self.indent() + + func_name = "get_or_select_template" + if isinstance(node.template, nodes.Const): + if isinstance(node.template.value, str): + func_name = "get_template" + elif isinstance(node.template.value, (tuple, list)): + func_name = "select_template" + elif isinstance(node.template, (nodes.Tuple, nodes.List)): + func_name = "select_template" + + self.writeline(f"template = environment.{func_name}(", node) + self.visit(node.template, frame) + self.write(f", {self.name!r})") + if node.ignore_missing: + self.outdent() + self.writeline("except TemplateNotFound:") + self.indent() + self.writeline("pass") + self.outdent() + self.writeline("else:") + self.indent() + + def loop_body() -> None: + self.indent() + self.simple_write("event", frame) + self.outdent() + + if node.with_context: + self.writeline( + f"gen = template.root_render_func(" + "template.new_context(context.get_all(), True," + f" {self.dump_local_context(frame)}))" + ) + self.writeline("try:") + self.indent() + self.writeline(f"{self.choose_async()}for event in gen:") + loop_body() + self.outdent() + self.writeline( + f"finally: {self.choose_async('await gen.aclose()', 'gen.close()')}" + ) + elif self.environment.is_async: + self.writeline( + "for event in (await template._get_default_module_async())" + "._body_stream:" + ) + loop_body() + else: + self.writeline("yield from template._get_default_module()._body_stream") + + if node.ignore_missing: + self.outdent() + + def _import_common( + self, node: t.Union[nodes.Import, nodes.FromImport], frame: Frame + ) -> None: + self.write(f"{self.choose_async('await ')}environment.get_template(") + self.visit(node.template, frame) + self.write(f", {self.name!r}).") + + if node.with_context: + f_name = f"make_module{self.choose_async('_async')}" + self.write( + f"{f_name}(context.get_all(), True, {self.dump_local_context(frame)})" + ) + else: + self.write(f"_get_default_module{self.choose_async('_async')}(context)") + + def visit_Import(self, node: nodes.Import, frame: Frame) -> None: + """Visit regular imports.""" + self.writeline(f"{frame.symbols.ref(node.target)} = ", node) + if frame.toplevel: + self.write(f"context.vars[{node.target!r}] = ") + + self._import_common(node, frame) + + if frame.toplevel and not node.target.startswith("_"): + self.writeline(f"context.exported_vars.discard({node.target!r})") + + def visit_FromImport(self, node: nodes.FromImport, frame: Frame) -> None: + """Visit named imports.""" + self.newline(node) + self.write("included_template = ") + self._import_common(node, frame) + var_names = [] + discarded_names = [] + for name in node.names: + if isinstance(name, tuple): + name, alias = name + else: + alias = name + self.writeline( + f"{frame.symbols.ref(alias)} =" + f" getattr(included_template, {name!r}, missing)" + ) + self.writeline(f"if {frame.symbols.ref(alias)} is missing:") + self.indent() + # The position will contain the template name, and will be formatted + # into a string that will be compiled into an f-string. Curly braces + # in the name must be replaced with escapes so that they will not be + # executed as part of the f-string. + position = self.position(node).replace("{", "{{").replace("}", "}}") + message = ( + "the template {included_template.__name__!r}" + f" (imported on {position})" + f" does not export the requested name {name!r}" + ) + self.writeline( + f"{frame.symbols.ref(alias)} = undefined(f{message!r}, name={name!r})" + ) + self.outdent() + if frame.toplevel: + var_names.append(alias) + if not alias.startswith("_"): + discarded_names.append(alias) + + if var_names: + if len(var_names) == 1: + name = var_names[0] + self.writeline(f"context.vars[{name!r}] = {frame.symbols.ref(name)}") + else: + names_kv = ", ".join( + f"{name!r}: {frame.symbols.ref(name)}" for name in var_names + ) + self.writeline(f"context.vars.update({{{names_kv}}})") + if discarded_names: + if len(discarded_names) == 1: + self.writeline(f"context.exported_vars.discard({discarded_names[0]!r})") + else: + names_str = ", ".join(map(repr, discarded_names)) + self.writeline( + f"context.exported_vars.difference_update(({names_str}))" + ) + + def visit_For(self, node: nodes.For, frame: Frame) -> None: + loop_frame = frame.inner() + loop_frame.loop_frame = True + test_frame = frame.inner() + else_frame = frame.inner() + + # try to figure out if we have an extended loop. An extended loop + # is necessary if the loop is in recursive mode if the special loop + # variable is accessed in the body if the body is a scoped block. + extended_loop = ( + node.recursive + or "loop" + in find_undeclared(node.iter_child_nodes(only=("body",)), ("loop",)) + or any(block.scoped for block in node.find_all(nodes.Block)) + ) + + loop_ref = None + if extended_loop: + loop_ref = loop_frame.symbols.declare_parameter("loop") + + loop_frame.symbols.analyze_node(node, for_branch="body") + if node.else_: + else_frame.symbols.analyze_node(node, for_branch="else") + + if node.test: + loop_filter_func = self.temporary_identifier() + test_frame.symbols.analyze_node(node, for_branch="test") + self.writeline(f"{self.func(loop_filter_func)}(fiter):", node.test) + self.indent() + self.enter_frame(test_frame) + self.writeline(self.choose_async("async for ", "for ")) + self.visit(node.target, loop_frame) + self.write(" in ") + self.write(self.choose_async("auto_aiter(fiter)", "fiter")) + self.write(":") + self.indent() + self.writeline("if ", node.test) + self.visit(node.test, test_frame) + self.write(":") + self.indent() + self.writeline("yield ") + self.visit(node.target, loop_frame) + self.outdent(3) + self.leave_frame(test_frame, with_python_scope=True) + + # if we don't have an recursive loop we have to find the shadowed + # variables at that point. Because loops can be nested but the loop + # variable is a special one we have to enforce aliasing for it. + if node.recursive: + self.writeline( + f"{self.func('loop')}(reciter, loop_render_func, depth=0):", node + ) + self.indent() + self.buffer(loop_frame) + + # Use the same buffer for the else frame + else_frame.buffer = loop_frame.buffer + + # make sure the loop variable is a special one and raise a template + # assertion error if a loop tries to write to loop + if extended_loop: + self.writeline(f"{loop_ref} = missing") + + for name in node.find_all(nodes.Name): + if name.ctx == "store" and name.name == "loop": + self.fail( + "Can't assign to special loop variable in for-loop target", + name.lineno, + ) + + if node.else_: + iteration_indicator = self.temporary_identifier() + self.writeline(f"{iteration_indicator} = 1") + + self.writeline(self.choose_async("async for ", "for "), node) + self.visit(node.target, loop_frame) + if extended_loop: + self.write(f", {loop_ref} in {self.choose_async('Async')}LoopContext(") + else: + self.write(" in ") + + if node.test: + self.write(f"{loop_filter_func}(") + if node.recursive: + self.write("reciter") + else: + if self.environment.is_async and not extended_loop: + self.write("auto_aiter(") + self.visit(node.iter, frame) + if self.environment.is_async and not extended_loop: + self.write(")") + if node.test: + self.write(")") + + if node.recursive: + self.write(", undefined, loop_render_func, depth):") + else: + self.write(", undefined):" if extended_loop else ":") + + self.indent() + self.enter_frame(loop_frame) + + self.writeline("_loop_vars = {}") + self.blockvisit(node.body, loop_frame) + if node.else_: + self.writeline(f"{iteration_indicator} = 0") + self.outdent() + self.leave_frame( + loop_frame, with_python_scope=node.recursive and not node.else_ + ) + + if node.else_: + self.writeline(f"if {iteration_indicator}:") + self.indent() + self.enter_frame(else_frame) + self.blockvisit(node.else_, else_frame) + self.leave_frame(else_frame) + self.outdent() + + # if the node was recursive we have to return the buffer contents + # and start the iteration code + if node.recursive: + self.return_buffer_contents(loop_frame) + self.outdent() + self.start_write(frame, node) + self.write(f"{self.choose_async('await ')}loop(") + if self.environment.is_async: + self.write("auto_aiter(") + self.visit(node.iter, frame) + if self.environment.is_async: + self.write(")") + self.write(", loop)") + self.end_write(frame) + + # at the end of the iteration, clear any assignments made in the + # loop from the top level + if self._assign_stack: + self._assign_stack[-1].difference_update(loop_frame.symbols.stores) + + def visit_If(self, node: nodes.If, frame: Frame) -> None: + if_frame = frame.soft() + self.writeline("if ", node) + self.visit(node.test, if_frame) + self.write(":") + self.indent() + self.blockvisit(node.body, if_frame) + self.outdent() + for elif_ in node.elif_: + self.writeline("elif ", elif_) + self.visit(elif_.test, if_frame) + self.write(":") + self.indent() + self.blockvisit(elif_.body, if_frame) + self.outdent() + if node.else_: + self.writeline("else:") + self.indent() + self.blockvisit(node.else_, if_frame) + self.outdent() + + def visit_Macro(self, node: nodes.Macro, frame: Frame) -> None: + macro_frame, macro_ref = self.macro_body(node, frame) + self.newline() + if frame.toplevel: + if not node.name.startswith("_"): + self.write(f"context.exported_vars.add({node.name!r})") + self.writeline(f"context.vars[{node.name!r}] = ") + self.write(f"{frame.symbols.ref(node.name)} = ") + self.macro_def(macro_ref, macro_frame) + + def visit_CallBlock(self, node: nodes.CallBlock, frame: Frame) -> None: + call_frame, macro_ref = self.macro_body(node, frame) + self.writeline("caller = ") + self.macro_def(macro_ref, call_frame) + self.start_write(frame, node) + self.visit_Call(node.call, frame, forward_caller=True) + self.end_write(frame) + + def visit_FilterBlock(self, node: nodes.FilterBlock, frame: Frame) -> None: + filter_frame = frame.inner() + filter_frame.symbols.analyze_node(node) + self.enter_frame(filter_frame) + self.buffer(filter_frame) + self.blockvisit(node.body, filter_frame) + self.start_write(frame, node) + self.visit_Filter(node.filter, filter_frame) + self.end_write(frame) + self.leave_frame(filter_frame) + + def visit_With(self, node: nodes.With, frame: Frame) -> None: + with_frame = frame.inner() + with_frame.symbols.analyze_node(node) + self.enter_frame(with_frame) + for target, expr in zip(node.targets, node.values): + self.newline() + self.visit(target, with_frame) + self.write(" = ") + self.visit(expr, frame) + self.blockvisit(node.body, with_frame) + self.leave_frame(with_frame) + + def visit_ExprStmt(self, node: nodes.ExprStmt, frame: Frame) -> None: + self.newline(node) + self.visit(node.node, frame) + + class _FinalizeInfo(t.NamedTuple): + const: t.Optional[t.Callable[..., str]] + src: t.Optional[str] + + @staticmethod + def _default_finalize(value: t.Any) -> t.Any: + """The default finalize function if the environment isn't + configured with one. Or, if the environment has one, this is + called on that function's output for constants. + """ + return str(value) + + _finalize: t.Optional[_FinalizeInfo] = None + + def _make_finalize(self) -> _FinalizeInfo: + """Build the finalize function to be used on constants and at + runtime. Cached so it's only created once for all output nodes. + + Returns a ``namedtuple`` with the following attributes: + + ``const`` + A function to finalize constant data at compile time. + + ``src`` + Source code to output around nodes to be evaluated at + runtime. + """ + if self._finalize is not None: + return self._finalize + + finalize: t.Optional[t.Callable[..., t.Any]] + finalize = default = self._default_finalize + src = None + + if self.environment.finalize: + src = "environment.finalize(" + env_finalize = self.environment.finalize + pass_arg = { + _PassArg.context: "context", + _PassArg.eval_context: "context.eval_ctx", + _PassArg.environment: "environment", + }.get( + _PassArg.from_obj(env_finalize) # type: ignore + ) + finalize = None + + if pass_arg is None: + + def finalize(value: t.Any) -> t.Any: # noqa: F811 + return default(env_finalize(value)) + + else: + src = f"{src}{pass_arg}, " + + if pass_arg == "environment": + + def finalize(value: t.Any) -> t.Any: # noqa: F811 + return default(env_finalize(self.environment, value)) + + self._finalize = self._FinalizeInfo(finalize, src) + return self._finalize + + def _output_const_repr(self, group: t.Iterable[t.Any]) -> str: + """Given a group of constant values converted from ``Output`` + child nodes, produce a string to write to the template module + source. + """ + return repr(concat(group)) + + def _output_child_to_const( + self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo + ) -> str: + """Try to optimize a child of an ``Output`` node by trying to + convert it to constant, finalized data at compile time. + + If :exc:`Impossible` is raised, the node is not constant and + will be evaluated at runtime. Any other exception will also be + evaluated at runtime for easier debugging. + """ + const = node.as_const(frame.eval_ctx) + + if frame.eval_ctx.autoescape: + const = escape(const) + + # Template data doesn't go through finalize. + if isinstance(node, nodes.TemplateData): + return str(const) + + return finalize.const(const) # type: ignore + + def _output_child_pre( + self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo + ) -> None: + """Output extra source code before visiting a child of an + ``Output`` node. + """ + if frame.eval_ctx.volatile: + self.write("(escape if context.eval_ctx.autoescape else str)(") + elif frame.eval_ctx.autoescape: + self.write("escape(") + else: + self.write("str(") + + if finalize.src is not None: + self.write(finalize.src) + + def _output_child_post( + self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo + ) -> None: + """Output extra source code after visiting a child of an + ``Output`` node. + """ + self.write(")") + + if finalize.src is not None: + self.write(")") + + def visit_Output(self, node: nodes.Output, frame: Frame) -> None: + # If an extends is active, don't render outside a block. + if frame.require_output_check: + # A top-level extends is known to exist at compile time. + if self.has_known_extends: + return + + self.writeline("if parent_template is None:") + self.indent() + + finalize = self._make_finalize() + body: t.List[t.Union[t.List[t.Any], nodes.Expr]] = [] + + # Evaluate constants at compile time if possible. Each item in + # body will be either a list of static data or a node to be + # evaluated at runtime. + for child in node.nodes: + try: + if not ( + # If the finalize function requires runtime context, + # constants can't be evaluated at compile time. + finalize.const + # Unless it's basic template data that won't be + # finalized anyway. + or isinstance(child, nodes.TemplateData) + ): + raise nodes.Impossible() + + const = self._output_child_to_const(child, frame, finalize) + except (nodes.Impossible, Exception): + # The node was not constant and needs to be evaluated at + # runtime. Or another error was raised, which is easier + # to debug at runtime. + body.append(child) + continue + + if body and isinstance(body[-1], list): + body[-1].append(const) + else: + body.append([const]) + + if frame.buffer is not None: + if len(body) == 1: + self.writeline(f"{frame.buffer}.append(") + else: + self.writeline(f"{frame.buffer}.extend((") + + self.indent() + + for item in body: + if isinstance(item, list): + # A group of constant data to join and output. + val = self._output_const_repr(item) + + if frame.buffer is None: + self.writeline("yield " + val) + else: + self.writeline(val + ",") + else: + if frame.buffer is None: + self.writeline("yield ", item) + else: + self.newline(item) + + # A node to be evaluated at runtime. + self._output_child_pre(item, frame, finalize) + self.visit(item, frame) + self._output_child_post(item, frame, finalize) + + if frame.buffer is not None: + self.write(",") + + if frame.buffer is not None: + self.outdent() + self.writeline(")" if len(body) == 1 else "))") + + if frame.require_output_check: + self.outdent() + + def visit_Assign(self, node: nodes.Assign, frame: Frame) -> None: + self.push_assign_tracking() + + # ``a.b`` is allowed for assignment, and is parsed as an NSRef. However, + # it is only valid if it references a Namespace object. Emit a check for + # that for each ref here, before assignment code is emitted. This can't + # be done in visit_NSRef as the ref could be in the middle of a tuple. + seen_refs: t.Set[str] = set() + + for nsref in node.find_all(nodes.NSRef): + if nsref.name in seen_refs: + # Only emit the check for each reference once, in case the same + # ref is used multiple times in a tuple, `ns.a, ns.b = c, d`. + continue + + seen_refs.add(nsref.name) + ref = frame.symbols.ref(nsref.name) + self.writeline(f"if not isinstance({ref}, Namespace):") + self.indent() + self.writeline( + "raise TemplateRuntimeError" + '("cannot assign attribute on non-namespace object")' + ) + self.outdent() + + self.newline(node) + self.visit(node.target, frame) + self.write(" = ") + self.visit(node.node, frame) + self.pop_assign_tracking(frame) + + def visit_AssignBlock(self, node: nodes.AssignBlock, frame: Frame) -> None: + self.push_assign_tracking() + block_frame = frame.inner() + # This is a special case. Since a set block always captures we + # will disable output checks. This way one can use set blocks + # toplevel even in extended templates. + block_frame.require_output_check = False + block_frame.symbols.analyze_node(node) + self.enter_frame(block_frame) + self.buffer(block_frame) + self.blockvisit(node.body, block_frame) + self.newline(node) + self.visit(node.target, frame) + self.write(" = (Markup if context.eval_ctx.autoescape else identity)(") + if node.filter is not None: + self.visit_Filter(node.filter, block_frame) + else: + self.write(f"concat({block_frame.buffer})") + self.write(")") + self.pop_assign_tracking(frame) + self.leave_frame(block_frame) + + # -- Expression Visitors + + def visit_Name(self, node: nodes.Name, frame: Frame) -> None: + if node.ctx == "store" and ( + frame.toplevel or frame.loop_frame or frame.block_frame + ): + if self._assign_stack: + self._assign_stack[-1].add(node.name) + ref = frame.symbols.ref(node.name) + + # If we are looking up a variable we might have to deal with the + # case where it's undefined. We can skip that case if the load + # instruction indicates a parameter which are always defined. + if node.ctx == "load": + load = frame.symbols.find_load(ref) + if not ( + load is not None + and load[0] == VAR_LOAD_PARAMETER + and not self.parameter_is_undeclared(ref) + ): + self.write( + f"(undefined(name={node.name!r}) if {ref} is missing else {ref})" + ) + return + + self.write(ref) + + def visit_NSRef(self, node: nodes.NSRef, frame: Frame) -> None: + # NSRef is a dotted assignment target a.b=c, but uses a[b]=c internally. + # visit_Assign emits code to validate that each ref is to a Namespace + # object only. That can't be emitted here as the ref could be in the + # middle of a tuple assignment. + ref = frame.symbols.ref(node.name) + self.writeline(f"{ref}[{node.attr!r}]") + + def visit_Const(self, node: nodes.Const, frame: Frame) -> None: + val = node.as_const(frame.eval_ctx) + if isinstance(val, float): + self.write(str(val)) + else: + self.write(repr(val)) + + def visit_TemplateData(self, node: nodes.TemplateData, frame: Frame) -> None: + try: + self.write(repr(node.as_const(frame.eval_ctx))) + except nodes.Impossible: + self.write( + f"(Markup if context.eval_ctx.autoescape else identity)({node.data!r})" + ) + + def visit_Tuple(self, node: nodes.Tuple, frame: Frame) -> None: + self.write("(") + idx = -1 + for idx, item in enumerate(node.items): + if idx: + self.write(", ") + self.visit(item, frame) + self.write(",)" if idx == 0 else ")") + + def visit_List(self, node: nodes.List, frame: Frame) -> None: + self.write("[") + for idx, item in enumerate(node.items): + if idx: + self.write(", ") + self.visit(item, frame) + self.write("]") + + def visit_Dict(self, node: nodes.Dict, frame: Frame) -> None: + self.write("{") + for idx, item in enumerate(node.items): + if idx: + self.write(", ") + self.visit(item.key, frame) + self.write(": ") + self.visit(item.value, frame) + self.write("}") + + visit_Add = _make_binop("+") + visit_Sub = _make_binop("-") + visit_Mul = _make_binop("*") + visit_Div = _make_binop("/") + visit_FloorDiv = _make_binop("//") + visit_Pow = _make_binop("**") + visit_Mod = _make_binop("%") + visit_And = _make_binop("and") + visit_Or = _make_binop("or") + visit_Pos = _make_unop("+") + visit_Neg = _make_unop("-") + visit_Not = _make_unop("not ") + + @optimizeconst + def visit_Concat(self, node: nodes.Concat, frame: Frame) -> None: + if frame.eval_ctx.volatile: + func_name = "(markup_join if context.eval_ctx.volatile else str_join)" + elif frame.eval_ctx.autoescape: + func_name = "markup_join" + else: + func_name = "str_join" + self.write(f"{func_name}((") + for arg in node.nodes: + self.visit(arg, frame) + self.write(", ") + self.write("))") + + @optimizeconst + def visit_Compare(self, node: nodes.Compare, frame: Frame) -> None: + self.write("(") + self.visit(node.expr, frame) + for op in node.ops: + self.visit(op, frame) + self.write(")") + + def visit_Operand(self, node: nodes.Operand, frame: Frame) -> None: + self.write(f" {operators[node.op]} ") + self.visit(node.expr, frame) + + @optimizeconst + def visit_Getattr(self, node: nodes.Getattr, frame: Frame) -> None: + if self.environment.is_async: + self.write("(await auto_await(") + + self.write("environment.getattr(") + self.visit(node.node, frame) + self.write(f", {node.attr!r})") + + if self.environment.is_async: + self.write("))") + + @optimizeconst + def visit_Getitem(self, node: nodes.Getitem, frame: Frame) -> None: + # slices bypass the environment getitem method. + if isinstance(node.arg, nodes.Slice): + self.visit(node.node, frame) + self.write("[") + self.visit(node.arg, frame) + self.write("]") + else: + if self.environment.is_async: + self.write("(await auto_await(") + + self.write("environment.getitem(") + self.visit(node.node, frame) + self.write(", ") + self.visit(node.arg, frame) + self.write(")") + + if self.environment.is_async: + self.write("))") + + def visit_Slice(self, node: nodes.Slice, frame: Frame) -> None: + if node.start is not None: + self.visit(node.start, frame) + self.write(":") + if node.stop is not None: + self.visit(node.stop, frame) + if node.step is not None: + self.write(":") + self.visit(node.step, frame) + + @contextmanager + def _filter_test_common( + self, node: t.Union[nodes.Filter, nodes.Test], frame: Frame, is_filter: bool + ) -> t.Iterator[None]: + if self.environment.is_async: + self.write("(await auto_await(") + + if is_filter: + self.write(f"{self.filters[node.name]}(") + func = self.environment.filters.get(node.name) + else: + self.write(f"{self.tests[node.name]}(") + func = self.environment.tests.get(node.name) + + # When inside an If or CondExpr frame, allow the filter to be + # undefined at compile time and only raise an error if it's + # actually called at runtime. See pull_dependencies. + if func is None and not frame.soft_frame: + type_name = "filter" if is_filter else "test" + self.fail(f"No {type_name} named {node.name!r}.", node.lineno) + + pass_arg = { + _PassArg.context: "context", + _PassArg.eval_context: "context.eval_ctx", + _PassArg.environment: "environment", + }.get( + _PassArg.from_obj(func) # type: ignore + ) + + if pass_arg is not None: + self.write(f"{pass_arg}, ") + + # Back to the visitor function to handle visiting the target of + # the filter or test. + yield + + self.signature(node, frame) + self.write(")") + + if self.environment.is_async: + self.write("))") + + @optimizeconst + def visit_Filter(self, node: nodes.Filter, frame: Frame) -> None: + with self._filter_test_common(node, frame, True): + # if the filter node is None we are inside a filter block + # and want to write to the current buffer + if node.node is not None: + self.visit(node.node, frame) + elif frame.eval_ctx.volatile: + self.write( + f"(Markup(concat({frame.buffer}))" + f" if context.eval_ctx.autoescape else concat({frame.buffer}))" + ) + elif frame.eval_ctx.autoescape: + self.write(f"Markup(concat({frame.buffer}))") + else: + self.write(f"concat({frame.buffer})") + + @optimizeconst + def visit_Test(self, node: nodes.Test, frame: Frame) -> None: + with self._filter_test_common(node, frame, False): + self.visit(node.node, frame) + + @optimizeconst + def visit_CondExpr(self, node: nodes.CondExpr, frame: Frame) -> None: + frame = frame.soft() + + def write_expr2() -> None: + if node.expr2 is not None: + self.visit(node.expr2, frame) + return + + self.write( + f'cond_expr_undefined("the inline if-expression on' + f" {self.position(node)} evaluated to false and no else" + f' section was defined.")' + ) + + self.write("(") + self.visit(node.expr1, frame) + self.write(" if ") + self.visit(node.test, frame) + self.write(" else ") + write_expr2() + self.write(")") + + @optimizeconst + def visit_Call( + self, node: nodes.Call, frame: Frame, forward_caller: bool = False + ) -> None: + if self.environment.is_async: + self.write("(await auto_await(") + if self.environment.sandboxed: + self.write("environment.call(context, ") + else: + self.write("context.call(") + self.visit(node.node, frame) + extra_kwargs = {"caller": "caller"} if forward_caller else None + loop_kwargs = {"_loop_vars": "_loop_vars"} if frame.loop_frame else {} + block_kwargs = {"_block_vars": "_block_vars"} if frame.block_frame else {} + if extra_kwargs: + extra_kwargs.update(loop_kwargs, **block_kwargs) + elif loop_kwargs or block_kwargs: + extra_kwargs = dict(loop_kwargs, **block_kwargs) + self.signature(node, frame, extra_kwargs) + self.write(")") + if self.environment.is_async: + self.write("))") + + def visit_Keyword(self, node: nodes.Keyword, frame: Frame) -> None: + self.write(node.key + "=") + self.visit(node.value, frame) + + # -- Unused nodes for extensions + + def visit_MarkSafe(self, node: nodes.MarkSafe, frame: Frame) -> None: + self.write("Markup(") + self.visit(node.expr, frame) + self.write(")") + + def visit_MarkSafeIfAutoescape( + self, node: nodes.MarkSafeIfAutoescape, frame: Frame + ) -> None: + self.write("(Markup if context.eval_ctx.autoescape else identity)(") + self.visit(node.expr, frame) + self.write(")") + + def visit_EnvironmentAttribute( + self, node: nodes.EnvironmentAttribute, frame: Frame + ) -> None: + self.write("environment." + node.name) + + def visit_ExtensionAttribute( + self, node: nodes.ExtensionAttribute, frame: Frame + ) -> None: + self.write(f"environment.extensions[{node.identifier!r}].{node.name}") + + def visit_ImportedName(self, node: nodes.ImportedName, frame: Frame) -> None: + self.write(self.import_aliases[node.importname]) + + def visit_InternalName(self, node: nodes.InternalName, frame: Frame) -> None: + self.write(node.name) + + def visit_ContextReference( + self, node: nodes.ContextReference, frame: Frame + ) -> None: + self.write("context") + + def visit_DerivedContextReference( + self, node: nodes.DerivedContextReference, frame: Frame + ) -> None: + self.write(self.derive_context(frame)) + + def visit_Continue(self, node: nodes.Continue, frame: Frame) -> None: + self.writeline("continue", node) + + def visit_Break(self, node: nodes.Break, frame: Frame) -> None: + self.writeline("break", node) + + def visit_Scope(self, node: nodes.Scope, frame: Frame) -> None: + scope_frame = frame.inner() + scope_frame.symbols.analyze_node(node) + self.enter_frame(scope_frame) + self.blockvisit(node.body, scope_frame) + self.leave_frame(scope_frame) + + def visit_OverlayScope(self, node: nodes.OverlayScope, frame: Frame) -> None: + ctx = self.temporary_identifier() + self.writeline(f"{ctx} = {self.derive_context(frame)}") + self.writeline(f"{ctx}.vars = ") + self.visit(node.context, frame) + self.push_context_reference(ctx) + + scope_frame = frame.inner(isolated=True) + scope_frame.symbols.analyze_node(node) + self.enter_frame(scope_frame) + self.blockvisit(node.body, scope_frame) + self.leave_frame(scope_frame) + self.pop_context_reference() + + def visit_EvalContextModifier( + self, node: nodes.EvalContextModifier, frame: Frame + ) -> None: + for keyword in node.options: + self.writeline(f"context.eval_ctx.{keyword.key} = ") + self.visit(keyword.value, frame) + try: + val = keyword.value.as_const(frame.eval_ctx) + except nodes.Impossible: + frame.eval_ctx.volatile = True + else: + setattr(frame.eval_ctx, keyword.key, val) + + def visit_ScopedEvalContextModifier( + self, node: nodes.ScopedEvalContextModifier, frame: Frame + ) -> None: + old_ctx_name = self.temporary_identifier() + saved_ctx = frame.eval_ctx.save() + self.writeline(f"{old_ctx_name} = context.eval_ctx.save()") + self.visit_EvalContextModifier(node, frame) + for child in node.body: + self.visit(child, frame) + frame.eval_ctx.revert(saved_ctx) + self.writeline(f"context.eval_ctx.revert({old_ctx_name})") diff --git a/venv/lib/python3.12/site-packages/jinja2/constants.py b/venv/lib/python3.12/site-packages/jinja2/constants.py new file mode 100644 index 0000000..41a1c23 --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2/constants.py @@ -0,0 +1,20 @@ +#: list of lorem ipsum words used by the lipsum() helper function +LOREM_IPSUM_WORDS = """\ +a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at +auctor augue bibendum blandit class commodo condimentum congue consectetuer +consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus +diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend +elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames +faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac +hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum +justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem +luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie +mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non +nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque +penatibus per pharetra phasellus placerat platea porta porttitor posuere +potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus +ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit +sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor +tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices +ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus +viverra volutpat vulputate""" diff --git a/venv/lib/python3.12/site-packages/jinja2/debug.py b/venv/lib/python3.12/site-packages/jinja2/debug.py new file mode 100644 index 0000000..eeeeee7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2/debug.py @@ -0,0 +1,191 @@ +import sys +import typing as t +from types import CodeType +from types import TracebackType + +from .exceptions import TemplateSyntaxError +from .utils import internal_code +from .utils import missing + +if t.TYPE_CHECKING: + from .runtime import Context + + +def rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException: + """Rewrite the current exception to replace any tracebacks from + within compiled template code with tracebacks that look like they + came from the template source. + + This must be called within an ``except`` block. + + :param source: For ``TemplateSyntaxError``, the original source if + known. + :return: The original exception with the rewritten traceback. + """ + _, exc_value, tb = sys.exc_info() + exc_value = t.cast(BaseException, exc_value) + tb = t.cast(TracebackType, tb) + + if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated: + exc_value.translated = True + exc_value.source = source + # Remove the old traceback, otherwise the frames from the + # compiler still show up. + exc_value.with_traceback(None) + # Outside of runtime, so the frame isn't executing template + # code, but it still needs to point at the template. + tb = fake_traceback( + exc_value, None, exc_value.filename or "", exc_value.lineno + ) + else: + # Skip the frame for the render function. + tb = tb.tb_next + + stack = [] + + # Build the stack of traceback object, replacing any in template + # code with the source file and line information. + while tb is not None: + # Skip frames decorated with @internalcode. These are internal + # calls that aren't useful in template debugging output. + if tb.tb_frame.f_code in internal_code: + tb = tb.tb_next + continue + + template = tb.tb_frame.f_globals.get("__jinja_template__") + + if template is not None: + lineno = template.get_corresponding_lineno(tb.tb_lineno) + fake_tb = fake_traceback(exc_value, tb, template.filename, lineno) + stack.append(fake_tb) + else: + stack.append(tb) + + tb = tb.tb_next + + tb_next = None + + # Assign tb_next in reverse to avoid circular references. + for tb in reversed(stack): + tb.tb_next = tb_next + tb_next = tb + + return exc_value.with_traceback(tb_next) + + +def fake_traceback( # type: ignore + exc_value: BaseException, tb: t.Optional[TracebackType], filename: str, lineno: int +) -> TracebackType: + """Produce a new traceback object that looks like it came from the + template source instead of the compiled code. The filename, line + number, and location name will point to the template, and the local + variables will be the current template context. + + :param exc_value: The original exception to be re-raised to create + the new traceback. + :param tb: The original traceback to get the local variables and + code info from. + :param filename: The template filename. + :param lineno: The line number in the template source. + """ + if tb is not None: + # Replace the real locals with the context that would be + # available at that point in the template. + locals = get_template_locals(tb.tb_frame.f_locals) + locals.pop("__jinja_exception__", None) + else: + locals = {} + + globals = { + "__name__": filename, + "__file__": filename, + "__jinja_exception__": exc_value, + } + # Raise an exception at the correct line number. + code: CodeType = compile( + "\n" * (lineno - 1) + "raise __jinja_exception__", filename, "exec" + ) + + # Build a new code object that points to the template file and + # replaces the location with a block name. + location = "template" + + if tb is not None: + function = tb.tb_frame.f_code.co_name + + if function == "root": + location = "top-level template code" + elif function.startswith("block_"): + location = f"block {function[6:]!r}" + + if sys.version_info >= (3, 8): + code = code.replace(co_name=location) + else: + code = CodeType( + code.co_argcount, + code.co_kwonlyargcount, + code.co_nlocals, + code.co_stacksize, + code.co_flags, + code.co_code, + code.co_consts, + code.co_names, + code.co_varnames, + code.co_filename, + location, + code.co_firstlineno, + code.co_lnotab, + code.co_freevars, + code.co_cellvars, + ) + + # Execute the new code, which is guaranteed to raise, and return + # the new traceback without this frame. + try: + exec(code, globals, locals) + except BaseException: + return sys.exc_info()[2].tb_next # type: ignore + + +def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any]: + """Based on the runtime locals, get the context that would be + available at that point in the template. + """ + # Start with the current template context. + ctx: t.Optional[Context] = real_locals.get("context") + + if ctx is not None: + data: t.Dict[str, t.Any] = ctx.get_all().copy() + else: + data = {} + + # Might be in a derived context that only sets local variables + # rather than pushing a context. Local variables follow the scheme + # l_depth_name. Find the highest-depth local that has a value for + # each name. + local_overrides: t.Dict[str, t.Tuple[int, t.Any]] = {} + + for name, value in real_locals.items(): + if not name.startswith("l_") or value is missing: + # Not a template variable, or no longer relevant. + continue + + try: + _, depth_str, name = name.split("_", 2) + depth = int(depth_str) + except ValueError: + continue + + cur_depth = local_overrides.get(name, (-1,))[0] + + if cur_depth < depth: + local_overrides[name] = (depth, value) + + # Modify the context with any derived context. + for name, (_, value) in local_overrides.items(): + if value is missing: + data.pop(name, None) + else: + data[name] = value + + return data diff --git a/venv/lib/python3.12/site-packages/jinja2/defaults.py b/venv/lib/python3.12/site-packages/jinja2/defaults.py new file mode 100644 index 0000000..638cad3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2/defaults.py @@ -0,0 +1,48 @@ +import typing as t + +from .filters import FILTERS as DEFAULT_FILTERS # noqa: F401 +from .tests import TESTS as DEFAULT_TESTS # noqa: F401 +from .utils import Cycler +from .utils import generate_lorem_ipsum +from .utils import Joiner +from .utils import Namespace + +if t.TYPE_CHECKING: + import typing_extensions as te + +# defaults for the parser / lexer +BLOCK_START_STRING = "{%" +BLOCK_END_STRING = "%}" +VARIABLE_START_STRING = "{{" +VARIABLE_END_STRING = "}}" +COMMENT_START_STRING = "{#" +COMMENT_END_STRING = "#}" +LINE_STATEMENT_PREFIX: t.Optional[str] = None +LINE_COMMENT_PREFIX: t.Optional[str] = None +TRIM_BLOCKS = False +LSTRIP_BLOCKS = False +NEWLINE_SEQUENCE: "te.Literal['\\n', '\\r\\n', '\\r']" = "\n" +KEEP_TRAILING_NEWLINE = False + +# default filters, tests and namespace + +DEFAULT_NAMESPACE = { + "range": range, + "dict": dict, + "lipsum": generate_lorem_ipsum, + "cycler": Cycler, + "joiner": Joiner, + "namespace": Namespace, +} + +# default policies +DEFAULT_POLICIES: t.Dict[str, t.Any] = { + "compiler.ascii_str": True, + "urlize.rel": "noopener", + "urlize.target": None, + "urlize.extra_schemes": None, + "truncate.leeway": 5, + "json.dumps_function": None, + "json.dumps_kwargs": {"sort_keys": True}, + "ext.i18n.trimmed": False, +} diff --git a/venv/lib/python3.12/site-packages/jinja2/environment.py b/venv/lib/python3.12/site-packages/jinja2/environment.py new file mode 100644 index 0000000..0fc6e5b --- /dev/null +++ b/venv/lib/python3.12/site-packages/jinja2/environment.py @@ -0,0 +1,1672 @@ +"""Classes for managing templates and their runtime and compile time +options. +""" + +import os +import typing +import typing as t +import weakref +from collections import ChainMap +from functools import lru_cache +from functools import partial +from functools import reduce +from types import CodeType + +from markupsafe import Markup + +from . import nodes +from .compiler import CodeGenerator +from .compiler import generate +from .defaults import BLOCK_END_STRING +from .defaults import BLOCK_START_STRING +from .defaults import COMMENT_END_STRING +from .defaults import COMMENT_START_STRING +from .defaults import DEFAULT_FILTERS # type: ignore[attr-defined] +from .defaults import DEFAULT_NAMESPACE +from .defaults import DEFAULT_POLICIES +from .defaults import DEFAULT_TESTS # type: ignore[attr-defined] +from .defaults import KEEP_TRAILING_NEWLINE +from .defaults import LINE_COMMENT_PREFIX +from .defaults import LINE_STATEMENT_PREFIX +from .defaults import LSTRIP_BLOCKS +from .defaults import NEWLINE_SEQUENCE +from .defaults import TRIM_BLOCKS +from .defaults import VARIABLE_END_STRING +from .defaults import VARIABLE_START_STRING +from .exceptions import TemplateNotFound +from .exceptions import TemplateRuntimeError +from .exceptions import TemplatesNotFound +from .exceptions import TemplateSyntaxError +from .exceptions import UndefinedError +from .lexer import get_lexer +from .lexer import Lexer +from .lexer import TokenStream +from .nodes import EvalContext +from .parser import Parser +from .runtime import Context +from .runtime import new_context +from .runtime import Undefined +from .utils import _PassArg +from .utils import concat +from .utils import consume +from .utils import import_string +from .utils import internalcode +from .utils import LRUCache +from .utils import missing + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .bccache import BytecodeCache + from .ext import Extension + from .loaders import BaseLoader + +_env_bound = t.TypeVar("_env_bound", bound="Environment") + + +# for direct template usage we have up to ten living environments +@lru_cache(maxsize=10) +def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any) -> _env_bound: + """Return a new spontaneous environment. A spontaneous environment + is used for templates created directly rather than through an + existing environment. + + :param cls: Environment class to create. + :param args: Positional arguments passed to environment. + """ + env = cls(*args) + env.shared = True + return env + + +def create_cache( + size: int, +) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]: + """Return the cache class for the given size.""" + if size == 0: + return None + + if size < 0: + return {} + + return LRUCache(size) # type: ignore + + +def copy_cache( + cache: t.Optional[t.MutableMapping[t.Any, t.Any]], +) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]: + """Create an empty copy of the given cache.""" + if cache is None: + return None + + if type(cache) is dict: # noqa E721 + return {} + + return LRUCache(cache.capacity) # type: ignore + + +def load_extensions( + environment: "Environment", + extensions: t.Sequence[t.Union[str, t.Type["Extension"]]], +) -> t.Dict[str, "Extension"]: + """Load the extensions from the list and bind it to the environment. + Returns a dict of instantiated extensions. + """ + result = {} + + for extension in extensions: + if isinstance(extension, str): + extension = t.cast(t.Type["Extension"], import_string(extension)) + + result[extension.identifier] = extension(environment) + + return result + + +def _environment_config_check(environment: _env_bound) -> _env_bound: + """Perform a sanity check on the environment.""" + assert issubclass( + environment.undefined, Undefined + ), "'undefined' must be a subclass of 'jinja2.Undefined'." + assert ( + environment.block_start_string + != environment.variable_start_string + != environment.comment_start_string + ), "block, variable and comment start strings must be different." + assert environment.newline_sequence in { + "\r", + "\r\n", + "\n", + }, "'newline_sequence' must be one of '\\n', '\\r\\n', or '\\r'." + return environment + + +class Environment: + r"""The core component of Jinja is the `Environment`. It contains + important shared variables like configuration, filters, tests, + globals and others. Instances of this class may be modified if + they are not shared and if no template was loaded so far. + Modifications on environments after the first template was loaded + will lead to surprising effects and undefined behavior. + + Here are the possible initialization parameters: + + `block_start_string` + The string marking the beginning of a block. Defaults to ``'{%'``. + + `block_end_string` + The string marking the end of a block. Defaults to ``'%}'``. + + `variable_start_string` + The string marking the beginning of a print statement. + Defaults to ``'{{'``. + + `variable_end_string` + The string marking the end of a print statement. Defaults to + ``'}}'``. + + `comment_start_string` + The string marking the beginning of a comment. Defaults to ``'{#'``. + + `comment_end_string` + The string marking the end of a comment. Defaults to ``'#}'``. + + `line_statement_prefix` + If given and a string, this will be used as prefix for line based + statements. See also :ref:`line-statements`. + + `line_comment_prefix` + If given and a string, this will be used as prefix for line based + comments. See also :ref:`line-statements`. + + .. versionadded:: 2.2 + + `trim_blocks` + If this is set to ``True`` the first newline after a block is + removed (block, not variable tag!). Defaults to `False`. + + `lstrip_blocks` + If this is set to ``True`` leading spaces and tabs are stripped + from the start of a line to a block. Defaults to `False`. + + `newline_sequence` + The sequence that starts a newline. Must be one of ``'\r'``, + ``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a + useful default for Linux and OS X systems as well as web + applications. + + `keep_trailing_newline` + Preserve the trailing newline when rendering templates. + The default is ``False``, which causes a single newline, + if present, to be stripped from the end of the template. + + .. versionadded:: 2.7 + + `extensions` + List of Jinja extensions to use. This can either be import paths + as strings or extension classes. For more information have a + look at :ref:`the extensions documentation `. + + `optimized` + should the optimizer be enabled? Default is ``True``. + + `undefined` + :class:`Undefined` or a subclass of it that is used to represent + undefined values in the template. + + `finalize` + A callable that can be used to process the result of a variable + expression before it is output. For example one can convert + ``None`` implicitly into an empty string here. + + `autoescape` + If set to ``True`` the XML/HTML autoescaping feature is enabled by + default. For more details about autoescaping see + :class:`~markupsafe.Markup`. As of Jinja 2.4 this can also + be a callable that is passed the template name and has to + return ``True`` or ``False`` depending on autoescape should be + enabled by default. + + .. versionchanged:: 2.4 + `autoescape` can now be a function + + `loader` + The template loader for this environment. + + `cache_size` + The size of the cache. Per default this is ``400`` which means + that if more than 400 templates are loaded the loader will clean + out the least recently used template. If the cache size is set to + ``0`` templates are recompiled all the time, if the cache size is + ``-1`` the cache will not be cleaned. + + .. versionchanged:: 2.8 + The cache size was increased to 400 from a low 50. + + `auto_reload` + Some loaders load templates from locations where the template + sources may change (ie: file system or database). If + ``auto_reload`` is set to ``True`` (default) every time a template is + requested the loader checks if the source changed and if yes, it + will reload the template. For higher performance it's possible to + disable that. + + `bytecode_cache` + If set to a bytecode cache object, this object will provide a + cache for the internal Jinja bytecode so that templates don't + have to be parsed if they were not changed. + + See :ref:`bytecode-cache` for more information. + + `enable_async` + If set to true this enables async template execution which + allows using async functions and generators. + """ + + #: if this environment is sandboxed. Modifying this variable won't make + #: the environment sandboxed though. For a real sandboxed environment + #: have a look at jinja2.sandbox. This flag alone controls the code + #: generation by the compiler. + sandboxed = False + + #: True if the environment is just an overlay + overlayed = False + + #: the environment this environment is linked to if it is an overlay + linked_to: t.Optional["Environment"] = None + + #: shared environments have this set to `True`. A shared environment + #: must not be modified + shared = False + + #: the class that is used for code generation. See + #: :class:`~jinja2.compiler.CodeGenerator` for more information. + code_generator_class: t.Type["CodeGenerator"] = CodeGenerator + + concat = "".join + + #: the context class that is used for templates. See + #: :class:`~jinja2.runtime.Context` for more information. + context_class: t.Type[Context] = Context + + template_class: t.Type["Template"] + + def __init__( + self, + block_start_string: str = BLOCK_START_STRING, + block_end_string: str = BLOCK_END_STRING, + variable_start_string: str = VARIABLE_START_STRING, + variable_end_string: str = VARIABLE_END_STRING, + comment_start_string: str = COMMENT_START_STRING, + comment_end_string: str = COMMENT_END_STRING, + line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX, + line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX, + trim_blocks: bool = TRIM_BLOCKS, + lstrip_blocks: bool = LSTRIP_BLOCKS, + newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE, + keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE, + extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (), + optimized: bool = True, + undefined: t.Type[Undefined] = Undefined, + finalize: t.Optional[t.Callable[..., t.Any]] = None, + autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False, + loader: t.Optional["BaseLoader"] = None, + cache_size: int = 400, + auto_reload: bool = True, + bytecode_cache: t.Optional["BytecodeCache"] = None, + enable_async: bool = False, + ): + # !!Important notice!! + # The constructor accepts quite a few arguments that should be + # passed by keyword rather than position. However it's important to + # not change the order of arguments because it's used at least + # internally in those cases: + # - spontaneous environments (i18n extension and Template) + # - unittests + # If parameter changes are required only add parameters at the end + # and don't change the arguments (or the defaults!) of the arguments + # existing already. + + # lexer / parser information + self.block_start_string = block_start_string + self.block_end_string = block_end_string + self.variable_start_string = variable_start_string + self.variable_end_string = variable_end_string + self.comment_start_string = comment_start_string + self.comment_end_string = comment_end_string + self.line_statement_prefix = line_statement_prefix + self.line_comment_prefix = line_comment_prefix + self.trim_blocks = trim_blocks + self.lstrip_blocks = lstrip_blocks + self.newline_sequence = newline_sequence + self.keep_trailing_newline = keep_trailing_newline + + # runtime information + self.undefined: t.Type[Undefined] = undefined + self.optimized = optimized + self.finalize = finalize + self.autoescape = autoescape + + # defaults + self.filters = DEFAULT_FILTERS.copy() + self.tests = DEFAULT_TESTS.copy() + self.globals = DEFAULT_NAMESPACE.copy() + + # set the loader provided + self.loader = loader + self.cache = create_cache(cache_size) + self.bytecode_cache = bytecode_cache + self.auto_reload = auto_reload + + # configurable policies + self.policies = DEFAULT_POLICIES.copy() + + # load extensions + self.extensions = load_extensions(self, extensions) + + self.is_async = enable_async + _environment_config_check(self) + + def add_extension(self, extension: t.Union[str, t.Type["Extension"]]) -> None: + """Adds an extension after the environment was created. + + .. versionadded:: 2.5 + """ + self.extensions.update(load_extensions(self, [extension])) + + def extend(self, **attributes: t.Any) -> None: + """Add the items to the instance of the environment if they do not exist + yet. This is used by :ref:`extensions ` to register + callbacks and configuration values without breaking inheritance. + """ + for key, value in attributes.items(): + if not hasattr(self, key): + setattr(self, key, value) + + def overlay( + self, + block_start_string: str = missing, + block_end_string: str = missing, + variable_start_string: str = missing, + variable_end_string: str = missing, + comment_start_string: str = missing, + comment_end_string: str = missing, + line_statement_prefix: t.Optional[str] = missing, + line_comment_prefix: t.Optional[str] = missing, + trim_blocks: bool = missing, + lstrip_blocks: bool = missing, + newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = missing, + keep_trailing_newline: bool = missing, + extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing, + optimized: bool = missing, + undefined: t.Type[Undefined] = missing, + finalize: t.Optional[t.Callable[..., t.Any]] = missing, + autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = missing, + loader: t.Optional["BaseLoader"] = missing, + cache_size: int = missing, + auto_reload: bool = missing, + bytecode_cache: t.Optional["BytecodeCache"] = missing, + enable_async: bool = missing, + ) -> "te.Self": + """Create a new overlay environment that shares all the data with the + current environment except for cache and the overridden attributes. + Extensions cannot be removed for an overlayed environment. An overlayed + environment automatically gets all the extensions of the environment it + is linked to plus optional extra extensions. + + Creating overlays should happen after the initial environment was set + up completely. Not all attributes are truly linked, some are just + copied over so modifications on the original environment may not shine + through. + + .. versionchanged:: 3.1.5 + ``enable_async`` is applied correctly. + + .. versionchanged:: 3.1.2 + Added the ``newline_sequence``, ``keep_trailing_newline``, + and ``enable_async`` parameters to match ``__init__``. + """ + args = dict(locals()) + del args["self"], args["cache_size"], args["extensions"], args["enable_async"] + + rv = object.__new__(self.__class__) + rv.__dict__.update(self.__dict__) + rv.overlayed = True + rv.linked_to = self + + for key, value in args.items(): + if value is not missing: + setattr(rv, key, value) + + if cache_size is not missing: + rv.cache = create_cache(cache_size) + else: + rv.cache = copy_cache(self.cache) + + rv.extensions = {} + for key, value in self.extensions.items(): + rv.extensions[key] = value.bind(rv) + if extensions is not missing: + rv.extensions.update(load_extensions(rv, extensions)) + + if enable_async is not missing: + rv.is_async = enable_async + + return _environment_config_check(rv) + + @property + def lexer(self) -> Lexer: + """The lexer for this environment.""" + return get_lexer(self) + + def iter_extensions(self) -> t.Iterator["Extension"]: + """Iterates over the extensions by priority.""" + return iter(sorted(self.extensions.values(), key=lambda x: x.priority)) + + def getitem( + self, obj: t.Any, argument: t.Union[str, t.Any] + ) -> t.Union[t.Any, Undefined]: + """Get an item or attribute of an object but prefer the item.""" + try: + return obj[argument] + except (AttributeError, TypeError, LookupError): + if isinstance(argument, str): + try: + attr = str(argument) + except Exception: + pass + else: + try: + return getattr(obj, attr) + except AttributeError: + pass + return self.undefined(obj=obj, name=argument) + + def getattr(self, obj: t.Any, attribute: str) -> t.Any: + """Get an item or attribute of an object but prefer the attribute. + Unlike :meth:`getitem` the attribute *must* be a string. + """ + try: + return getattr(obj, attribute) + except AttributeError: + pass + try: + return obj[attribute] + except (TypeError, LookupError, AttributeError): + return self.undefined(obj=obj, name=attribute) + + def _filter_test_common( + self, + name: t.Union[str, Undefined], + value: t.Any, + args: t.Optional[t.Sequence[t.Any]], + kwargs: t.Optional[t.Mapping[str, t.Any]], + context: t.Optional[Context], + eval_ctx: t.Optional[EvalContext], + is_filter: bool, + ) -> t.Any: + if is_filter: + env_map = self.filters + type_name = "filter" + else: + env_map = self.tests + type_name = "test" + + func = env_map.get(name) # type: ignore + + if func is None: + msg = f"No {type_name} named {name!r}." + + if isinstance(name, Undefined): + try: + name._fail_with_undefined_error() + except Exception as e: + msg = f"{msg} ({e}; did you forget to quote the callable name?)" + + raise TemplateRuntimeError(msg) + + args = [value, *(args if args is not None else ())] + kwargs = kwargs if kwargs is not None else {} + pass_arg = _PassArg.from_obj(func) + + if pass_arg is _PassArg.context: + if context is None: + raise TemplateRuntimeError( + f"Attempted to invoke a context {type_name} without context." + ) + + args.insert(0, context) + elif pass_arg is _PassArg.eval_context: + if eval_ctx is None: + if context is not None: + eval_ctx = context.eval_ctx + else: + eval_ctx = EvalContext(self) + + args.insert(0, eval_ctx) + elif pass_arg is _PassArg.environment: + args.insert(0, self) + + return func(*args, **kwargs) + + def call_filter( + self, + name: str, + value: t.Any, + args: t.Optional[t.Sequence[t.Any]] = None, + kwargs: t.Optional[t.Mapping[str, t.Any]] = None, + context: t.Optional[Context] = None, + eval_ctx: t.Optional[EvalContext] = None, + ) -> t.Any: + """Invoke a filter on a value the same way the compiler does. + + This might return a coroutine if the filter is running from an + environment in async mode and the filter supports async + execution. It's your responsibility to await this if needed. + + .. versionadded:: 2.7 + """ + return self._filter_test_common( + name, value, args, kwargs, context, eval_ctx, True + ) + + def call_test( + self, + name: str, + value: t.Any, + args: t.Optional[t.Sequence[t.Any]] = None, + kwargs: t.Optional[t.Mapping[str, t.Any]] = None, + context: t.Optional[Context] = None, + eval_ctx: t.Optional[EvalContext] = None, + ) -> t.Any: + """Invoke a test on a value the same way the compiler does. + + This might return a coroutine if the test is running from an + environment in async mode and the test supports async execution. + It's your responsibility to await this if needed. + + .. versionchanged:: 3.0 + Tests support ``@pass_context``, etc. decorators. Added + the ``context`` and ``eval_ctx`` parameters. + + .. versionadded:: 2.7 + """ + return self._filter_test_common( + name, value, args, kwargs, context, eval_ctx, False + ) + + @internalcode + def parse( + self, + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> nodes.Template: + """Parse the sourcecode and return the abstract syntax tree. This + tree of nodes is used by the compiler to convert the template into + executable source- or bytecode. This is useful for debugging or to + extract information from templates. + + If you are :ref:`developing Jinja extensions ` + this gives you a good overview of the node tree generated. + """ + try: + return self._parse(source, name, filename) + except TemplateSyntaxError: + self.handle_exception(source=source) + + def _parse( + self, source: str, name: t.Optional[str], filename: t.Optional[str] + ) -> nodes.Template: + """Internal parsing function used by `parse` and `compile`.""" + return Parser(self, source, name, filename).parse() + + def lex( + self, + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> t.Iterator[t.Tuple[int, str, str]]: + """Lex the given sourcecode and return a generator that yields + tokens as tuples in the form ``(lineno, token_type, value)``. + This can be useful for :ref:`extension development ` + and debugging templates. + + This does not perform preprocessing. If you want the preprocessing + of the extensions to be applied you have to filter source through + the :meth:`preprocess` method. + """ + source = str(source) + try: + return self.lexer.tokeniter(source, name, filename) + except TemplateSyntaxError: + self.handle_exception(source=source) + + def preprocess( + self, + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> str: + """Preprocesses the source with all extensions. This is automatically + called for all parsing and compiling methods but *not* for :meth:`lex` + because there you usually only want the actual source tokenized. + """ + return reduce( + lambda s, e: e.preprocess(s, name, filename), + self.iter_extensions(), + str(source), + ) + + def _tokenize( + self, + source: str, + name: t.Optional[str], + filename: t.Optional[str] = None, + state: t.Optional[str] = None, + ) -> TokenStream: + """Called by the parser to do the preprocessing and filtering + for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`. + """ + source = self.preprocess(source, name, filename) + stream = self.lexer.tokenize(source, name, filename, state) + + for ext in self.iter_extensions(): + stream = ext.filter_stream(stream) # type: ignore + + if not isinstance(stream, TokenStream): + stream = TokenStream(stream, name, filename) + + return stream + + def _generate( + self, + source: nodes.Template, + name: t.Optional[str], + filename: t.Optional[str], + defer_init: bool = False, + ) -> str: + """Internal hook that can be overridden to hook a different generate + method in. + + .. versionadded:: 2.5 + """ + return generate( # type: ignore + source, + self, + name, + filename, + defer_init=defer_init, + optimized=self.optimized, + ) + + def _compile(self, source: str, filename: str) -> CodeType: + """Internal hook that can be overridden to hook a different compile + method in. + + .. versionadded:: 2.5 + """ + return compile(source, filename, "exec") + + @typing.overload + def compile( + self, + source: t.Union[str, nodes.Template], + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + raw: "te.Literal[False]" = False, + defer_init: bool = False, + ) -> CodeType: ... + + @typing.overload + def compile( + self, + source: t.Union[str, nodes.Template], + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + raw: "te.Literal[True]" = ..., + defer_init: bool = False, + ) -> str: ... + + @internalcode + def compile( + self, + source: t.Union[str, nodes.Template], + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + raw: bool = False, + defer_init: bool = False, + ) -> t.Union[str, CodeType]: + """Compile a node or template source code. The `name` parameter is + the load name of the template after it was joined using + :meth:`join_path` if necessary, not the filename on the file system. + the `filename` parameter is the estimated filename of the template on + the file system. If the template came from a database or memory this + can be omitted. + + The return value of this method is a python code object. If the `raw` + parameter is `True` the return value will be a string with python + code equivalent to the bytecode returned otherwise. This method is + mainly used internally. + + `defer_init` is use internally to aid the module code generator. This + causes the generated code to be able to import without the global + environment variable to be set. + + .. versionadded:: 2.4 + `defer_init` parameter added. + """ + source_hint = None + try: + if isinstance(source, str): + source_hint = source + source = self._parse(source, name, filename) + source = self._generate(source, name, filename, defer_init=defer_init) + if raw: + return source + if filename is None: + filename = "

EH2zvbcFb_Ga?rJ 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 Zrz0!*p+M*R;lphK#J2yr#jAXh{|mUs!k7R6 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/__pycache__/blueprints.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/__pycache__/blueprints.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a85e71f197d723771ee288e170e2f46de51da130 GIT binary patch literal 30741 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/__pycache__/cli.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/__pycache__/cli.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ba3f704b32d7fb5b5494c5c049a3537dc0709e9 GIT binary patch literal 39867 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 b+BI{6eJpWHU;43N{^JASSPNxLI{p6uMKs}U literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/__pycache__/config.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01a755824faf1be93daceba8775ff90b2c21275f GIT binary patch literal 15412 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c245c03459a110641f028dda40c86647e067b221 GIT binary patch literal 8537 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 YfycwX5ZCo+pP$>Zv}GsP#Ww5z0WWehR{#J2 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/__pycache__/globals.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/__pycache__/globals.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70d96441d97c550ce5c26d7de7ea62640dc62f57 GIT binary patch literal 3613 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 Y<$X8wmS6g+^!g>g=38@{N=!ok7su^!`Tzg` literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/__pycache__/logging.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/__pycache__/logging.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23079a336657306f30b17eca535bc5490a91e221 GIT binary patch literal 3242 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/__pycache__/sessions.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/__pycache__/sessions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a84fc857adc14a9428eef8410cf55161ea639aa GIT binary patch literal 15891 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/__pycache__/testing.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/__pycache__/testing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a015ed13f56d068be4d1464f407bce88081cf780 GIT binary patch literal 13521 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/__pycache__/typing.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/__pycache__/typing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a2bb4e77dd61466239eaf9eec01896fab302409 GIT binary patch literal 3760 zcmbVOO-$Qp8n$DH{E(0Y!e6EXX*(??w8;dTRuldv{FDM^T0v&2%>|kGO9(YdYCEOj zvSlwrPbH_oiS*Q0#B?o>tmD*wP*%OI=l()810eZBF~ziDOhb?94>M-|P41 zd7t3EUO`BWJ>2A*C({NP_s&u=eMwK#Ey>pL#XCzOnhtY|8nw|CIz5g!`2ug3-k}xXM z_Zj5<997wCD{ZCQ{@nh9q3(%!AnT46Rjv9C#xAv(gxCtJnc9jxNgR2aOj~6TTN7H1 zY9WuJpK_+^2(iK6(mM%v>7vd_Sq+`O^tq0WzTV%~7qa#A>(l0e3>__6F}es(!)$Q9 zd;#{Hs;#zOe3u)TR{>M+PU=q4g6$VF(-w0q3H&-= z&xUeCzpLs-%c|i9u1`M$c5@_YP&JO-Y>}mZKGiR&-q{^dc27gT)e>1p3-|`~HF*1> z?pL2A(6&*h-%Zh8=R}}$H?7e(kT(q5{wp8*VO(mb(&-FO@u^Ij`v%?}kJlC!BiSsI ziYrcVneHQsMl1)Wc@=S4$V6-#l>1pDJXsPRl3k6=G88AL8aFbzG(G&KWL;lRQV(*ML;iIkC>t` z&vz8d!hbiIWPX^Hb*31}1qwf8XP{A*h2mwDm&~&i!%!R0(-PUCQoLfiBMIQQD!1kM zW`bu1B{D3!mtr~o(^z*s_NkkkOT~HV^R-RHFteF7kDl|7*i0O8T!wAp+Gi*Z%|DML zg{I_;B8sImJLzVEH;T5i48^0C5It(fy%Yn?o0U!f0+ZRGn5LiH$YdDFtyV?Pk4e%p z=gx0eAIzZym18skE`d)G_z?>NemaJGo@F!aeJV{e=NQ;mGdUpM5cvfA8FmEwWiGIp z*+zkQhS|sm;+quD=L64B`dNTUZ3MD;elwFE^9M%*T#84xvQ+#Dl|)=10cZS4pni<~ z+59i&iR}!XW6%_P7eI0N<9>vrUL$ZEe~(@H3G4hokl#=LY5G0se@_P9lfGIT=ISh( z3Z^RAQ6=GNsglO;@9o{&555Y$4DGFafY-&n#r^m}uoxr?8ZYCgrV~?zsH|1Y;*GC_p0C7? z@QZff;B>v(dAU<1?J{1y-MG*(Ad-V>gQs%B0e848^bU!R8zMPeGa8H|9|_WFcwwt` zV7-0CWFc84U2?_3-opOM;e=ot63H8yV@$BAnDvaYl1;GP6vA?MCqEKIA2e~-%-F!-@#CY%jf#Et;P(D=k?cFXeC#{&l~&8H z)6Y&mt3<`&uy8Fbdd3BE{3Br)fMO4**6%qs9l@T}cY}o;3Kzn{Kv3)&6&#}?Id(oY zUKl@odc1qIEBGeG!6~t8T5wE@2iGheP5)r*K0`_^e%U!%%xT{Wz7p(>B;iTvs7976voZ$FUB!l|F$m#OQ@);@K zoD>|BA~_|u|5|SEhW1Wq|HZX8;n(5vM&OSc%Es6aB0qbeyhMn3i?3S*U_|mZ0dlQtecJ~xxg;=SpM3>sjQNbG& zyGI4AvpVRNB`OC`!zbZN{ABtp@^0zv(%*P-;(_2>`3Eof*2KZ6fL*FNvA%uUXMfPXzan;82gEa&rzM#pS}X;2jg)w*|*-kqpT~+yxz6 LhCMqQf~)&KNd_y{ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/__pycache__/views.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53a196de51e21b6b53282035c47920f58354ad9b GIT binary patch literal 6824 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/__pycache__/wrappers.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/__pycache__/wrappers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10dd032229ff15cdc007f71298d0e253d0a8cd52 GIT binary patch literal 6062 zcma)AU2GKB6`t82dw0F-U3+bC0)c^mt>tG82_@7CNeDH;5KIbA(`*%u$2)huYtQa% z?#yDmb&!*$im4Rkr>dybN~L`wN*|)W_oc6`Dm%0Sji91R@(}gK37|&wrQf;pv$ip` zv(nvr&)hlp+;hHre(s-Bsf2{*FMs++`P2qU`kV&-6LmZC?N8CUB2}Tl9Qbd ztyVK_&90a=YZT+e3z~k0z0qKnjZu568#}|SIwrBNWcSO+lx5b4L$Sd*tJ$hnuVdJ& zT6Lphs}n|p)p-TEUPaUXK*o^(aPd-YBNGV-)j7aXpSvf<`Q%cR>}APux|cLI)__dU6FNDJZEGloTjg zy>kVnQ}0=jO0u4QCs|1Ay`rB%KeHg6mkV8IrQ8NiAkh8_g^ZP%!d zx-v1V=&YnQ3|pzxlw*cwP3INm46Cy01ml)smsvu=Q`PKZ8S`09ubWVsqSbWeT!qaj z8lP-bp$x>#@noW}}rBPQjOBpJHwKX(;p(K{@L>b3gBPC_lY$!7tnA#?U ztQ%UTh7YBj}bObE7+5=)Xq7PEyALz->#U^RDJ-kPfTWSrIlA09CA#9v2I z#JjbD8OEoZtTCCN;Q$nLl=m<=9(f*NT^F50A>UDogob0Ee17q zW5c6k>Py3;FOD4_A6JTItyGy*rrB)X>Aru)XfDL#;O(@z6FlUlCYd!T;5rzXx<2c4 zSGDshR{0|x9;*w#Wt#zK&R`9|y8)j@8?dx0Qy@KwqA55W zI6PQZ?1v`{Hl3KS2;PgK3l+W*UA_rTGvgc9pY|-x+J!VV6zS~bQ5lo7Ok-&vn-8Q;41`YVQaBJk};3~fy4?J z7CF;wsDS{LR}h)1+AQFC4)cLc@PwLlA@d5I3G}u02KbbqOGGDH%=NzlLigl6CkF>8 z5muZ8C&g6AiGqZ5%*BvJa}-5@%83VZHoJUi1*B7$Xgz@j*y@vd`Up`Av0EM4#g46) zk6tU?UO9(=4FhSq&h()n<%#^h{QiIe%(fNU;*?Z-CUhcV!z*o= zXl^5+%POlC z?b$k08RsTf#Rrs{7NhemVR2hBmL`#S?mXj3p zGBYR%<2z`ruzT#N@5EI%(^FN8u&pTbu3zuRpt;ev+nN>Mz)R~ins4W&8?n^I_NCa? zzs0uQO-QM}8<|blGTT1OY`dQ6xs>`awYcrzXPJZ7aZXQsbmGh8_x_pOb>+a{llzwA zQt#H~u+;SwHQUjo`dmvBiu{Lc2f5_iKSPHs9bb_|`L?;R2!cvH3mOY^Q) ze?2>M`EAMe3c?nG=H$0Sy7Z=eL7tIkr1Rm|q!~FUkLIF$CuDLqAhi_vB^iY>K{vv4 zl3XNOd;mR9=Z?(yZVXrJdM_x=ExxYT=QmGZJ&y(|Uk(Y^WSdoCYYOz!;8avX2(S>0grlg@7G zqxg|f_*dHxhyNHwCl_+sII|nP)=W_0;^xOV-h-HaFM-5iHK`dV&qMk0>)s?MLg`K} z!t?l|+4TbrmE$mB*!xg$;wik$bvrRtC6A%1PD1rEbM%v{`bI-Dyf^Kts+&bs<#Z%@ zCmKLsT~X-b6=f$pLroXGkD-(R&h1&=M>GmJuCF3b!wJ)zl1Tkb^fn@}>^^5QTCF!d#5gQPO^o1L}r5_a?!wltqq=;d3c*v)U^8Pytdt=1hc0UFaH`~Gb;Oo`+RN+z$gnXx{L43h)2!6D@rZ}!@(@gax9(OZ+8#E-{ zh`u6&M4bT7OzV0fb!6Sjo%WA}VYLQ@<{ntK3#mvG>zC~=tN&5Aw_-bLyV|6P?)&rm z1Zzq%PMkeGiUPe}H<7$TK_UYn)vpwHPhdg+-XZU1C$8~J4I|(;Q-%#t#8Zt-Y$nN& z*g27uqEVr2C(r@~Gr9#KAw1hHA57EYlylVdR$&q38 z>_}mFRVD9U8j*$0GUjjh!g`l1E)2X9yQK=;TLn7vFH{CB1rFA`EFs=b+PyCbWPmrP z3V(*rMlpXyTOXp>S7fAj~54aZ0$HHY95}9FA-|*}$H`ZIUJO2&XG} z timedelta | None: + if value is None or isinstance(value, timedelta): + return value + + return timedelta(seconds=value) + + +class Flask(Scaffold): + """The flask object implements a WSGI application and acts as the central + object. It is passed the name of the module or package of the + application. Once it is created it will act as a central registry for + the view functions, the URL rules, template configuration and much more. + + The name of the package is used to resolve resources from inside the + package or the folder the module is contained in depending on if the + package parameter resolves to an actual python package (a folder with + an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file). + + For more information about resource loading, see :func:`open_resource`. + + Usually you create a :class:`Flask` instance in your main module or + in the :file:`__init__.py` file of your package like this:: + + from flask import Flask + app = Flask(__name__) + + .. admonition:: About the First Parameter + + The idea of the first parameter is to give Flask an idea of what + belongs to your application. This name is used to find resources + on the filesystem, can be used by extensions to improve debugging + information and a lot more. + + So it's important what you provide there. If you are using a single + module, `__name__` is always the correct value. If you however are + using a package, it's usually recommended to hardcode the name of + your package there. + + For example if your application is defined in :file:`yourapplication/app.py` + you should create it with one of the two versions below:: + + app = Flask('yourapplication') + app = Flask(__name__.split('.')[0]) + + Why is that? The application will work even with `__name__`, thanks + to how resources are looked up. However it will make debugging more + painful. Certain extensions can make assumptions based on the + import name of your application. For example the Flask-SQLAlchemy + extension will look for the code in your application that triggered + an SQL query in debug mode. If the import name is not properly set + up, that debugging information is lost. (For example it would only + pick up SQL queries in `yourapplication.app` and not + `yourapplication.views.frontend`) + + .. versionadded:: 0.7 + The `static_url_path`, `static_folder`, and `template_folder` + parameters were added. + + .. versionadded:: 0.8 + The `instance_path` and `instance_relative_config` parameters were + added. + + .. versionadded:: 0.11 + The `root_path` parameter was added. + + .. versionadded:: 1.0 + The ``host_matching`` and ``static_host`` parameters were added. + + .. versionadded:: 1.0 + The ``subdomain_matching`` parameter was added. Subdomain + matching needs to be enabled manually now. Setting + :data:`SERVER_NAME` does not implicitly enable it. + + :param import_name: the name of the application package + :param static_url_path: can be used to specify a different path for the + static files on the web. Defaults to the name + of the `static_folder` folder. + :param static_folder: The folder with static files that is served at + ``static_url_path``. Relative to the application ``root_path`` + or an absolute path. Defaults to ``'static'``. + :param static_host: the host to use when adding the static route. + Defaults to None. Required when using ``host_matching=True`` + with a ``static_folder`` configured. + :param host_matching: set ``url_map.host_matching`` attribute. + Defaults to False. + :param subdomain_matching: consider the subdomain relative to + :data:`SERVER_NAME` when matching routes. Defaults to False. + :param template_folder: the folder that contains the templates that should + be used by the application. Defaults to + ``'templates'`` folder in the root path of the + application. + :param instance_path: An alternative instance path for the application. + By default the folder ``'instance'`` next to the + package or module is assumed to be the instance + path. + :param instance_relative_config: if set to ``True`` relative filenames + for loading the config are assumed to + be relative to the instance path instead + of the application root. + :param root_path: The path to the root of the application files. + This should only be set manually when it can't be detected + automatically, such as for namespace packages. + """ + + #: The class that is used for request objects. See :class:`~flask.Request` + #: for more information. + request_class = Request + + #: The class that is used for response objects. See + #: :class:`~flask.Response` for more information. + response_class = Response + + #: The class of the object assigned to :attr:`aborter`, created by + #: :meth:`create_aborter`. That object is called by + #: :func:`flask.abort` to raise HTTP errors, and can be + #: called directly as well. + #: + #: Defaults to :class:`werkzeug.exceptions.Aborter`. + #: + #: .. versionadded:: 2.2 + aborter_class = Aborter + + #: The class that is used for the Jinja environment. + #: + #: .. versionadded:: 0.11 + jinja_environment = Environment + + #: The class that is used for the :data:`~flask.g` instance. + #: + #: Example use cases for a custom class: + #: + #: 1. Store arbitrary attributes on flask.g. + #: 2. Add a property for lazy per-request database connectors. + #: 3. Return None instead of AttributeError on unexpected attributes. + #: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g. + #: + #: In Flask 0.9 this property was called `request_globals_class` but it + #: was changed in 0.10 to :attr:`app_ctx_globals_class` because the + #: flask.g object is now application context scoped. + #: + #: .. versionadded:: 0.10 + app_ctx_globals_class = _AppCtxGlobals + + #: The class that is used for the ``config`` attribute of this app. + #: Defaults to :class:`~flask.Config`. + #: + #: Example use cases for a custom class: + #: + #: 1. Default values for certain config options. + #: 2. Access to config values through attributes in addition to keys. + #: + #: .. versionadded:: 0.11 + config_class = Config + + #: The testing flag. Set this to ``True`` to enable the test mode of + #: Flask extensions (and in the future probably also Flask itself). + #: For example this might activate test helpers that have an + #: additional runtime cost which should not be enabled by default. + #: + #: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the + #: default it's implicitly enabled. + #: + #: This attribute can also be configured from the config with the + #: ``TESTING`` configuration key. Defaults to ``False``. + testing = ConfigAttribute("TESTING") + + #: If a secret key is set, cryptographic components can use this to + #: sign cookies and other things. Set this to a complex random value + #: when you want to use the secure cookie for instance. + #: + #: This attribute can also be configured from the config with the + #: :data:`SECRET_KEY` configuration key. Defaults to ``None``. + secret_key = ConfigAttribute("SECRET_KEY") + + #: A :class:`~datetime.timedelta` which is used to set the expiration + #: date of a permanent session. The default is 31 days which makes a + #: permanent session survive for roughly one month. + #: + #: This attribute can also be configured from the config with the + #: ``PERMANENT_SESSION_LIFETIME`` configuration key. Defaults to + #: ``timedelta(days=31)`` + permanent_session_lifetime = ConfigAttribute( + "PERMANENT_SESSION_LIFETIME", get_converter=_make_timedelta + ) + + json_provider_class: type[JSONProvider] = DefaultJSONProvider + """A subclass of :class:`~flask.json.provider.JSONProvider`. An + instance is created and assigned to :attr:`app.json` when creating + the app. + + The default, :class:`~flask.json.provider.DefaultJSONProvider`, uses + Python's built-in :mod:`json` library. A different provider can use + a different JSON library. + + .. versionadded:: 2.2 + """ + + #: Options that are passed to the Jinja environment in + #: :meth:`create_jinja_environment`. Changing these options after + #: the environment is created (accessing :attr:`jinja_env`) will + #: have no effect. + #: + #: .. versionchanged:: 1.1.0 + #: This is a ``dict`` instead of an ``ImmutableDict`` to allow + #: easier configuration. + #: + jinja_options: dict = {} + + #: Default configuration parameters. + default_config = ImmutableDict( + { + "DEBUG": None, + "TESTING": False, + "PROPAGATE_EXCEPTIONS": None, + "SECRET_KEY": None, + "PERMANENT_SESSION_LIFETIME": timedelta(days=31), + "USE_X_SENDFILE": False, + "SERVER_NAME": None, + "APPLICATION_ROOT": "/", + "SESSION_COOKIE_NAME": "session", + "SESSION_COOKIE_DOMAIN": None, + "SESSION_COOKIE_PATH": None, + "SESSION_COOKIE_HTTPONLY": True, + "SESSION_COOKIE_SECURE": False, + "SESSION_COOKIE_SAMESITE": None, + "SESSION_REFRESH_EACH_REQUEST": True, + "MAX_CONTENT_LENGTH": None, + "SEND_FILE_MAX_AGE_DEFAULT": None, + "TRAP_BAD_REQUEST_ERRORS": None, + "TRAP_HTTP_EXCEPTIONS": False, + "EXPLAIN_TEMPLATE_LOADING": False, + "PREFERRED_URL_SCHEME": "http", + "TEMPLATES_AUTO_RELOAD": None, + "MAX_COOKIE_SIZE": 4093, + } + ) + + #: The rule object to use for URL rules created. This is used by + #: :meth:`add_url_rule`. Defaults to :class:`werkzeug.routing.Rule`. + #: + #: .. versionadded:: 0.7 + url_rule_class = Rule + + #: The map object to use for storing the URL rules and routing + #: configuration parameters. Defaults to :class:`werkzeug.routing.Map`. + #: + #: .. versionadded:: 1.1.0 + url_map_class = Map + + #: The :meth:`test_client` method creates an instance of this test + #: client class. Defaults to :class:`~flask.testing.FlaskClient`. + #: + #: .. versionadded:: 0.7 + test_client_class: type[FlaskClient] | None = None + + #: The :class:`~click.testing.CliRunner` subclass, by default + #: :class:`~flask.testing.FlaskCliRunner` that is used by + #: :meth:`test_cli_runner`. Its ``__init__`` method should take a + #: Flask app object as the first argument. + #: + #: .. versionadded:: 1.0 + test_cli_runner_class: type[FlaskCliRunner] | None = None + + #: the session interface to use. By default an instance of + #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here. + #: + #: .. versionadded:: 0.8 + session_interface: SessionInterface = SecureCookieSessionInterface() + + def __init__( + self, + import_name: str, + static_url_path: str | None = None, + static_folder: str | os.PathLike | None = "static", + static_host: str | None = None, + host_matching: bool = False, + subdomain_matching: bool = False, + template_folder: str | os.PathLike | None = "templates", + instance_path: str | None = None, + instance_relative_config: bool = False, + root_path: str | None = None, + ): + super().__init__( + import_name=import_name, + static_folder=static_folder, + static_url_path=static_url_path, + template_folder=template_folder, + root_path=root_path, + ) + + if instance_path is None: + instance_path = self.auto_find_instance_path() + elif not os.path.isabs(instance_path): + raise ValueError( + "If an instance path is provided it must be absolute." + " A relative path was given instead." + ) + + #: Holds the path to the instance folder. + #: + #: .. versionadded:: 0.8 + self.instance_path = instance_path + + #: The configuration dictionary as :class:`Config`. This behaves + #: exactly like a regular dictionary but supports additional methods + #: to load a config from files. + self.config = self.make_config(instance_relative_config) + + #: An instance of :attr:`aborter_class` created by + #: :meth:`make_aborter`. This is called by :func:`flask.abort` + #: to raise HTTP errors, and can be called directly as well. + #: + #: .. versionadded:: 2.2 + #: Moved from ``flask.abort``, which calls this object. + self.aborter = self.make_aborter() + + self.json: JSONProvider = self.json_provider_class(self) + """Provides access to JSON methods. Functions in ``flask.json`` + will call methods on this provider when the application context + is active. Used for handling JSON requests and responses. + + An instance of :attr:`json_provider_class`. Can be customized by + changing that attribute on a subclass, or by assigning to this + attribute afterwards. + + The default, :class:`~flask.json.provider.DefaultJSONProvider`, + uses Python's built-in :mod:`json` library. A different provider + can use a different JSON library. + + .. versionadded:: 2.2 + """ + + #: A list of functions that are called by + #: :meth:`handle_url_build_error` when :meth:`.url_for` raises a + #: :exc:`~werkzeug.routing.BuildError`. Each function is called + #: with ``error``, ``endpoint`` and ``values``. If a function + #: returns ``None`` or raises a ``BuildError``, it is skipped. + #: Otherwise, its return value is returned by ``url_for``. + #: + #: .. versionadded:: 0.9 + self.url_build_error_handlers: list[ + t.Callable[[Exception, str, dict[str, t.Any]], str] + ] = [] + + #: A list of functions that are called when the application context + #: is destroyed. Since the application context is also torn down + #: if the request ends this is the place to store code that disconnects + #: from databases. + #: + #: .. versionadded:: 0.9 + self.teardown_appcontext_funcs: list[ft.TeardownCallable] = [] + + #: A list of shell context processor functions that should be run + #: when a shell context is created. + #: + #: .. versionadded:: 0.11 + self.shell_context_processors: list[ft.ShellContextProcessorCallable] = [] + + #: Maps registered blueprint names to blueprint objects. The + #: dict retains the order the blueprints were registered in. + #: Blueprints can be registered multiple times, this dict does + #: not track how often they were attached. + #: + #: .. versionadded:: 0.7 + self.blueprints: dict[str, Blueprint] = {} + + #: a place where extensions can store application specific state. For + #: example this is where an extension could store database engines and + #: similar things. + #: + #: The key must match the name of the extension module. For example in + #: case of a "Flask-Foo" extension in `flask_foo`, the key would be + #: ``'foo'``. + #: + #: .. versionadded:: 0.7 + self.extensions: dict = {} + + #: The :class:`~werkzeug.routing.Map` for this instance. You can use + #: this to change the routing converters after the class was created + #: but before any routes are connected. Example:: + #: + #: from werkzeug.routing import BaseConverter + #: + #: class ListConverter(BaseConverter): + #: def to_python(self, value): + #: return value.split(',') + #: def to_url(self, values): + #: return ','.join(super(ListConverter, self).to_url(value) + #: for value in values) + #: + #: app = Flask(__name__) + #: app.url_map.converters['list'] = ListConverter + self.url_map = self.url_map_class() + + self.url_map.host_matching = host_matching + self.subdomain_matching = subdomain_matching + + # tracks internally if the application already handled at least one + # request. + self._got_first_request = False + + # Add a static route using the provided static_url_path, static_host, + # and static_folder if there is a configured static_folder. + # Note we do this without checking if static_folder exists. + # For one, it might be created while the server is running (e.g. during + # development). Also, Google App Engine stores static files somewhere + if self.has_static_folder: + assert ( + bool(static_host) == host_matching + ), "Invalid static_host/host_matching combination" + # Use a weakref to avoid creating a reference cycle between the app + # and the view function (see #3761). + self_ref = weakref.ref(self) + self.add_url_rule( + f"{self.static_url_path}/", + endpoint="static", + host=static_host, + view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore # noqa: B950 + ) + + # Set the name of the Click group in case someone wants to add + # the app's commands to another CLI tool. + self.cli.name = self.name + + def _check_setup_finished(self, f_name: str) -> None: + if self._got_first_request: + raise AssertionError( + f"The setup method '{f_name}' can no longer be called" + " on the application. It has already handled its first" + " request, any changes will not be applied" + " consistently.\n" + "Make sure all imports, decorators, functions, etc." + " needed to set up the application are done before" + " running it." + ) + + @cached_property + def name(self) -> str: # type: ignore + """The name of the application. This is usually the import name + with the difference that it's guessed from the run file if the + import name is main. This name is used as a display name when + Flask needs the name of the application. It can be set and overridden + to change the value. + + .. versionadded:: 0.8 + """ + if self.import_name == "__main__": + fn = getattr(sys.modules["__main__"], "__file__", None) + if fn is None: + return "__main__" + return os.path.splitext(os.path.basename(fn))[0] + return self.import_name + + @cached_property + def logger(self) -> logging.Logger: + """A standard Python :class:`~logging.Logger` for the app, with + the same name as :attr:`name`. + + In debug mode, the logger's :attr:`~logging.Logger.level` will + be set to :data:`~logging.DEBUG`. + + If there are no handlers configured, a default handler will be + added. See :doc:`/logging` for more information. + + .. versionchanged:: 1.1.0 + The logger takes the same name as :attr:`name` rather than + hard-coding ``"flask.app"``. + + .. versionchanged:: 1.0.0 + Behavior was simplified. The logger is always named + ``"flask.app"``. The level is only set during configuration, + it doesn't check ``app.debug`` each time. Only one format is + used, not different ones depending on ``app.debug``. No + handlers are removed, and a handler is only added if no + handlers are already configured. + + .. versionadded:: 0.3 + """ + return create_logger(self) + + @cached_property + def jinja_env(self) -> Environment: + """The Jinja environment used to load templates. + + The environment is created the first time this property is + accessed. Changing :attr:`jinja_options` after that will have no + effect. + """ + return self.create_jinja_environment() + + @property + def got_first_request(self) -> bool: + """This attribute is set to ``True`` if the application started + handling the first request. + + .. deprecated:: 2.3 + Will be removed in Flask 2.4. + + .. versionadded:: 0.8 + """ + import warnings + + warnings.warn( + "'got_first_request' is deprecated and will be removed in Flask 2.4.", + DeprecationWarning, + stacklevel=2, + ) + return self._got_first_request + + def make_config(self, instance_relative: bool = False) -> Config: + """Used to create the config attribute by the Flask constructor. + The `instance_relative` parameter is passed in from the constructor + of Flask (there named `instance_relative_config`) and indicates if + the config should be relative to the instance path or the root path + of the application. + + .. versionadded:: 0.8 + """ + root_path = self.root_path + if instance_relative: + root_path = self.instance_path + defaults = dict(self.default_config) + defaults["DEBUG"] = get_debug_flag() + return self.config_class(root_path, defaults) + + def make_aborter(self) -> Aborter: + """Create the object to assign to :attr:`aborter`. That object + is called by :func:`flask.abort` to raise HTTP errors, and can + be called directly as well. + + By default, this creates an instance of :attr:`aborter_class`, + which defaults to :class:`werkzeug.exceptions.Aborter`. + + .. versionadded:: 2.2 + """ + return self.aborter_class() + + def auto_find_instance_path(self) -> str: + """Tries to locate the instance path if it was not provided to the + constructor of the application class. It will basically calculate + the path to a folder named ``instance`` next to your main file or + the package. + + .. versionadded:: 0.8 + """ + prefix, package_path = find_package(self.import_name) + if prefix is None: + return os.path.join(package_path, "instance") + return os.path.join(prefix, "var", f"{self.name}-instance") + + def open_instance_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]: + """Opens a resource from the application's instance folder + (:attr:`instance_path`). Otherwise works like + :meth:`open_resource`. Instance resources can also be opened for + writing. + + :param resource: the name of the resource. To access resources within + subfolders use forward slashes as separator. + :param mode: resource file opening mode, default is 'rb'. + """ + return open(os.path.join(self.instance_path, resource), mode) + + def create_jinja_environment(self) -> Environment: + """Create the Jinja environment based on :attr:`jinja_options` + and the various Jinja-related methods of the app. Changing + :attr:`jinja_options` after this will have no effect. Also adds + Flask-related globals and filters to the environment. + + .. versionchanged:: 0.11 + ``Environment.auto_reload`` set in accordance with + ``TEMPLATES_AUTO_RELOAD`` configuration option. + + .. versionadded:: 0.5 + """ + options = dict(self.jinja_options) + + if "autoescape" not in options: + options["autoescape"] = self.select_jinja_autoescape + + if "auto_reload" not in options: + auto_reload = self.config["TEMPLATES_AUTO_RELOAD"] + + if auto_reload is None: + auto_reload = self.debug + + options["auto_reload"] = auto_reload + + rv = self.jinja_environment(self, **options) + rv.globals.update( + url_for=self.url_for, + get_flashed_messages=get_flashed_messages, + config=self.config, + # request, session and g are normally added with the + # context processor for efficiency reasons but for imported + # templates we also want the proxies in there. + request=request, + session=session, + g=g, + ) + rv.policies["json.dumps_function"] = self.json.dumps + return rv + + def create_global_jinja_loader(self) -> DispatchingJinjaLoader: + """Creates the loader for the Jinja2 environment. Can be used to + override just the loader and keeping the rest unchanged. It's + discouraged to override this function. Instead one should override + the :meth:`jinja_loader` function instead. + + The global loader dispatches between the loaders of the application + and the individual blueprints. + + .. versionadded:: 0.7 + """ + return DispatchingJinjaLoader(self) + + def select_jinja_autoescape(self, filename: str) -> bool: + """Returns ``True`` if autoescaping should be active for the given + template name. If no template name is given, returns `True`. + + .. versionchanged:: 2.2 + Autoescaping is now enabled by default for ``.svg`` files. + + .. versionadded:: 0.5 + """ + if filename is None: + return True + return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg")) + + def update_template_context(self, context: dict) -> None: + """Update the template context with some commonly used variables. + This injects request, session, config and g into the template + context as well as everything template context processors want + to inject. Note that the as of Flask 0.6, the original values + in the context will not be overridden if a context processor + decides to return a value with the same key. + + :param context: the context as a dictionary that is updated in place + to add extra variables. + """ + names: t.Iterable[str | None] = (None,) + + # A template may be rendered outside a request context. + if request: + names = chain(names, reversed(request.blueprints)) + + # The values passed to render_template take precedence. Keep a + # copy to re-apply after all context functions. + orig_ctx = context.copy() + + for name in names: + if name in self.template_context_processors: + for func in self.template_context_processors[name]: + context.update(func()) + + context.update(orig_ctx) + + def make_shell_context(self) -> dict: + """Returns the shell context for an interactive shell for this + application. This runs all the registered shell context + processors. + + .. versionadded:: 0.11 + """ + rv = {"app": self, "g": g} + for processor in self.shell_context_processors: + rv.update(processor()) + return rv + + @property + def debug(self) -> bool: + """Whether debug mode is enabled. When using ``flask run`` to start the + development server, an interactive debugger will be shown for unhandled + exceptions, and the server will be reloaded when code changes. This maps to the + :data:`DEBUG` config key. It may not behave as expected if set late. + + **Do not enable debug mode when deploying in production.** + + Default: ``False`` + """ + return self.config["DEBUG"] + + @debug.setter + def debug(self, value: bool) -> None: + self.config["DEBUG"] = value + + if self.config["TEMPLATES_AUTO_RELOAD"] is None: + self.jinja_env.auto_reload = value + + def run( + self, + host: str | None = None, + port: int | None = None, + debug: bool | None = None, + load_dotenv: bool = True, + **options: t.Any, + ) -> None: + """Runs the application on a local development server. + + Do not use ``run()`` in a production setting. It is not intended to + meet security and performance requirements for a production server. + Instead, see :doc:`/deploying/index` for WSGI server recommendations. + + If the :attr:`debug` flag is set the server will automatically reload + for code changes and show a debugger in case an exception happened. + + If you want to run the application in debug mode, but disable the + code execution on the interactive debugger, you can pass + ``use_evalex=False`` as parameter. This will keep the debugger's + traceback screen active, but disable code execution. + + It is not recommended to use this function for development with + automatic reloading as this is badly supported. Instead you should + be using the :command:`flask` command line script's ``run`` support. + + .. admonition:: Keep in Mind + + Flask will suppress any server error with a generic error page + unless it is in debug mode. As such to enable just the + interactive debugger without the code reloading, you have to + invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``. + Setting ``use_debugger`` to ``True`` without being in debug mode + won't catch any exceptions because there won't be any to + catch. + + :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to + have the server available externally as well. Defaults to + ``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config variable + if present. + :param port: the port of the webserver. Defaults to ``5000`` or the + port defined in the ``SERVER_NAME`` config variable if present. + :param debug: if given, enable or disable debug mode. See + :attr:`debug`. + :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv` + files to set environment variables. Will also change the working + directory to the directory containing the first file found. + :param options: the options to be forwarded to the underlying Werkzeug + server. See :func:`werkzeug.serving.run_simple` for more + information. + + .. versionchanged:: 1.0 + If installed, python-dotenv will be used to load environment + variables from :file:`.env` and :file:`.flaskenv` files. + + The :envvar:`FLASK_DEBUG` environment variable will override :attr:`debug`. + + Threaded mode is enabled by default. + + .. versionchanged:: 0.10 + The default port is now picked from the ``SERVER_NAME`` + variable. + """ + # Ignore this call so that it doesn't start another server if + # the 'flask run' command is used. + if os.environ.get("FLASK_RUN_FROM_CLI") == "true": + if not is_running_from_reloader(): + click.secho( + " * Ignoring a call to 'app.run()' that would block" + " the current 'flask' CLI command.\n" + " Only call 'app.run()' in an 'if __name__ ==" + ' "__main__"\' guard.', + fg="red", + ) + + return + + if get_load_dotenv(load_dotenv): + cli.load_dotenv() + + # if set, env var overrides existing value + if "FLASK_DEBUG" in os.environ: + self.debug = get_debug_flag() + + # debug passed to method overrides all other sources + if debug is not None: + self.debug = bool(debug) + + server_name = self.config.get("SERVER_NAME") + sn_host = sn_port = None + + if server_name: + sn_host, _, sn_port = server_name.partition(":") + + if not host: + if sn_host: + host = sn_host + else: + host = "127.0.0.1" + + if port or port == 0: + port = int(port) + elif sn_port: + port = int(sn_port) + else: + port = 5000 + + options.setdefault("use_reloader", self.debug) + options.setdefault("use_debugger", self.debug) + options.setdefault("threaded", True) + + cli.show_server_banner(self.debug, self.name) + + from werkzeug.serving import run_simple + + try: + run_simple(t.cast(str, host), port, self, **options) + finally: + # reset the first request information if the development server + # reset normally. This makes it possible to restart the server + # without reloader and that stuff from an interactive shell. + self._got_first_request = False + + def test_client(self, use_cookies: bool = True, **kwargs: t.Any) -> FlaskClient: + """Creates a test client for this application. For information + about unit testing head over to :doc:`/testing`. + + Note that if you are testing for assertions or exceptions in your + application code, you must set ``app.testing = True`` in order for the + exceptions to propagate to the test client. Otherwise, the exception + will be handled by the application (not visible to the test client) and + the only indication of an AssertionError or other exception will be a + 500 status code response to the test client. See the :attr:`testing` + attribute. For example:: + + app.testing = True + client = app.test_client() + + The test client can be used in a ``with`` block to defer the closing down + of the context until the end of the ``with`` block. This is useful if + you want to access the context locals for testing:: + + with app.test_client() as c: + rv = c.get('/?vodka=42') + assert request.args['vodka'] == '42' + + Additionally, you may pass optional keyword arguments that will then + be passed to the application's :attr:`test_client_class` constructor. + For example:: + + from flask.testing import FlaskClient + + class CustomClient(FlaskClient): + def __init__(self, *args, **kwargs): + self._authentication = kwargs.pop("authentication") + super(CustomClient,self).__init__( *args, **kwargs) + + app.test_client_class = CustomClient + client = app.test_client(authentication='Basic ....') + + See :class:`~flask.testing.FlaskClient` for more information. + + .. versionchanged:: 0.4 + added support for ``with`` block usage for the client. + + .. versionadded:: 0.7 + The `use_cookies` parameter was added as well as the ability + to override the client to be used by setting the + :attr:`test_client_class` attribute. + + .. versionchanged:: 0.11 + Added `**kwargs` to support passing additional keyword arguments to + the constructor of :attr:`test_client_class`. + """ + cls = self.test_client_class + if cls is None: + from .testing import FlaskClient as cls + return cls( # type: ignore + self, self.response_class, use_cookies=use_cookies, **kwargs + ) + + def test_cli_runner(self, **kwargs: t.Any) -> FlaskCliRunner: + """Create a CLI runner for testing CLI commands. + See :ref:`testing-cli`. + + Returns an instance of :attr:`test_cli_runner_class`, by default + :class:`~flask.testing.FlaskCliRunner`. The Flask app object is + passed as the first argument. + + .. versionadded:: 1.0 + """ + cls = self.test_cli_runner_class + + if cls is None: + from .testing import FlaskCliRunner as cls + + return cls(self, **kwargs) # type: ignore + + @setupmethod + def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None: + """Register a :class:`~flask.Blueprint` on the application. Keyword + arguments passed to this method will override the defaults set on the + blueprint. + + Calls the blueprint's :meth:`~flask.Blueprint.register` method after + recording the blueprint in the application's :attr:`blueprints`. + + :param blueprint: The blueprint to register. + :param url_prefix: Blueprint routes will be prefixed with this. + :param subdomain: Blueprint routes will match on this subdomain. + :param url_defaults: Blueprint routes will use these default values for + view arguments. + :param options: Additional keyword arguments are passed to + :class:`~flask.blueprints.BlueprintSetupState`. They can be + accessed in :meth:`~flask.Blueprint.record` callbacks. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + + .. versionadded:: 0.7 + """ + blueprint.register(self, options) + + def iter_blueprints(self) -> t.ValuesView[Blueprint]: + """Iterates over all blueprints by the order they were registered. + + .. versionadded:: 0.11 + """ + return self.blueprints.values() + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, + **options: t.Any, + ) -> None: + if endpoint is None: + endpoint = _endpoint_from_view_func(view_func) # type: ignore + options["endpoint"] = endpoint + methods = options.pop("methods", None) + + # if the methods are not given and the view_func object knows its + # methods we can use that instead. If neither exists, we go with + # a tuple of only ``GET`` as default. + if methods is None: + methods = getattr(view_func, "methods", None) or ("GET",) + if isinstance(methods, str): + raise TypeError( + "Allowed methods must be a list of strings, for" + ' example: @app.route(..., methods=["POST"])' + ) + methods = {item.upper() for item in methods} + + # Methods that should always be added + required_methods = set(getattr(view_func, "required_methods", ())) + + # starting with Flask 0.8 the view_func object can disable and + # force-enable the automatic options handling. + if provide_automatic_options is None: + provide_automatic_options = getattr( + view_func, "provide_automatic_options", None + ) + + if provide_automatic_options is None: + if "OPTIONS" not in methods: + provide_automatic_options = True + required_methods.add("OPTIONS") + else: + provide_automatic_options = False + + # Add the required methods now. + methods |= required_methods + + rule = self.url_rule_class(rule, methods=methods, **options) + rule.provide_automatic_options = provide_automatic_options # type: ignore + + self.url_map.add(rule) + if view_func is not None: + old_func = self.view_functions.get(endpoint) + if old_func is not None and old_func != view_func: + raise AssertionError( + "View function mapping is overwriting an existing" + f" endpoint function: {endpoint}" + ) + self.view_functions[endpoint] = view_func + + @setupmethod + def template_filter( + self, name: str | None = None + ) -> t.Callable[[T_template_filter], T_template_filter]: + """A decorator that is used to register custom template filter. + You can specify a name for the filter, otherwise the function + name will be used. Example:: + + @app.template_filter() + def reverse(s): + return s[::-1] + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def decorator(f: T_template_filter) -> T_template_filter: + self.add_template_filter(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_filter( + self, f: ft.TemplateFilterCallable, name: str | None = None + ) -> None: + """Register a custom template filter. Works exactly like the + :meth:`template_filter` decorator. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + self.jinja_env.filters[name or f.__name__] = f + + @setupmethod + def template_test( + self, name: str | None = None + ) -> t.Callable[[T_template_test], T_template_test]: + """A decorator that is used to register custom template test. + You can specify a name for the test, otherwise the function + name will be used. Example:: + + @app.template_test() + def is_prime(n): + if n == 2: + return True + for i in range(2, int(math.ceil(math.sqrt(n))) + 1): + if n % i == 0: + return False + return True + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def decorator(f: T_template_test) -> T_template_test: + self.add_template_test(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_test( + self, f: ft.TemplateTestCallable, name: str | None = None + ) -> None: + """Register a custom template test. Works exactly like the + :meth:`template_test` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + self.jinja_env.tests[name or f.__name__] = f + + @setupmethod + def template_global( + self, name: str | None = None + ) -> t.Callable[[T_template_global], T_template_global]: + """A decorator that is used to register a custom template global function. + You can specify a name for the global function, otherwise the function + name will be used. Example:: + + @app.template_global() + def double(n): + return 2 * n + + .. versionadded:: 0.10 + + :param name: the optional name of the global function, otherwise the + function name will be used. + """ + + def decorator(f: T_template_global) -> T_template_global: + self.add_template_global(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_global( + self, f: ft.TemplateGlobalCallable, name: str | None = None + ) -> None: + """Register a custom template global function. Works exactly like the + :meth:`template_global` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the global function, otherwise the + function name will be used. + """ + self.jinja_env.globals[name or f.__name__] = f + + @setupmethod + def teardown_appcontext(self, f: T_teardown) -> T_teardown: + """Registers a function to be called when the application + context is popped. The application context is typically popped + after the request context for each request, at the end of CLI + commands, or after a manually pushed context ends. + + .. code-block:: python + + with app.app_context(): + ... + + When the ``with`` block exits (or ``ctx.pop()`` is called), the + teardown functions are called just before the app context is + made inactive. Since a request context typically also manages an + application context it would also be called when you pop a + request context. + + When a teardown function was called because of an unhandled + exception it will be passed an error object. If an + :meth:`errorhandler` is registered, it will handle the exception + and the teardown will not receive it. + + Teardown functions must avoid raising exceptions. If they + execute code that might fail they must surround that code with a + ``try``/``except`` block and log any errors. + + The return values of teardown functions are ignored. + + .. versionadded:: 0.9 + """ + self.teardown_appcontext_funcs.append(f) + return f + + @setupmethod + def shell_context_processor( + self, f: T_shell_context_processor + ) -> T_shell_context_processor: + """Registers a shell context processor function. + + .. versionadded:: 0.11 + """ + self.shell_context_processors.append(f) + return f + + def _find_error_handler(self, e: Exception) -> ft.ErrorHandlerCallable | None: + """Return a registered error handler for an exception in this order: + blueprint handler for a specific code, app handler for a specific code, + blueprint handler for an exception class, app handler for an exception + class, or ``None`` if a suitable handler is not found. + """ + exc_class, code = self._get_exc_class_and_code(type(e)) + names = (*request.blueprints, None) + + for c in (code, None) if code is not None else (None,): + for name in names: + handler_map = self.error_handler_spec[name][c] + + if not handler_map: + continue + + for cls in exc_class.__mro__: + handler = handler_map.get(cls) + + if handler is not None: + return handler + return None + + def handle_http_exception( + self, e: HTTPException + ) -> HTTPException | ft.ResponseReturnValue: + """Handles an HTTP exception. By default this will invoke the + registered error handlers and fall back to returning the + exception as response. + + .. versionchanged:: 1.0.3 + ``RoutingException``, used internally for actions such as + slash redirects during routing, is not passed to error + handlers. + + .. versionchanged:: 1.0 + Exceptions are looked up by code *and* by MRO, so + ``HTTPException`` subclasses can be handled with a catch-all + handler for the base ``HTTPException``. + + .. versionadded:: 0.3 + """ + # Proxy exceptions don't have error codes. We want to always return + # those unchanged as errors + if e.code is None: + return e + + # RoutingExceptions are used internally to trigger routing + # actions, such as slash redirects raising RequestRedirect. They + # are not raised or handled in user code. + if isinstance(e, RoutingException): + return e + + handler = self._find_error_handler(e) + if handler is None: + return e + return self.ensure_sync(handler)(e) + + def trap_http_exception(self, e: Exception) -> bool: + """Checks if an HTTP exception should be trapped or not. By default + this will return ``False`` for all exceptions except for a bad request + key error if ``TRAP_BAD_REQUEST_ERRORS`` is set to ``True``. It + also returns ``True`` if ``TRAP_HTTP_EXCEPTIONS`` is set to ``True``. + + This is called for all HTTP exceptions raised by a view function. + If it returns ``True`` for any exception the error handler for this + exception is not called and it shows up as regular exception in the + traceback. This is helpful for debugging implicitly raised HTTP + exceptions. + + .. versionchanged:: 1.0 + Bad request errors are not trapped by default in debug mode. + + .. versionadded:: 0.8 + """ + if self.config["TRAP_HTTP_EXCEPTIONS"]: + return True + + trap_bad_request = self.config["TRAP_BAD_REQUEST_ERRORS"] + + # if unset, trap key errors in debug mode + if ( + trap_bad_request is None + and self.debug + and isinstance(e, BadRequestKeyError) + ): + return True + + if trap_bad_request: + return isinstance(e, BadRequest) + + return False + + def handle_user_exception( + self, e: Exception + ) -> HTTPException | ft.ResponseReturnValue: + """This method is called whenever an exception occurs that + should be handled. A special case is :class:`~werkzeug + .exceptions.HTTPException` which is forwarded to the + :meth:`handle_http_exception` method. This function will either + return a response value or reraise the exception with the same + traceback. + + .. versionchanged:: 1.0 + Key errors raised from request data like ``form`` show the + bad key in debug mode rather than a generic bad request + message. + + .. versionadded:: 0.7 + """ + if isinstance(e, BadRequestKeyError) and ( + self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"] + ): + e.show_exception = True + + if isinstance(e, HTTPException) and not self.trap_http_exception(e): + return self.handle_http_exception(e) + + handler = self._find_error_handler(e) + + if handler is None: + raise + + return self.ensure_sync(handler)(e) + + def handle_exception(self, e: Exception) -> Response: + """Handle an exception that did not have an error handler + associated with it, or that was raised from an error handler. + This always causes a 500 ``InternalServerError``. + + Always sends the :data:`got_request_exception` signal. + + If :data:`PROPAGATE_EXCEPTIONS` is ``True``, such as in debug + mode, the error will be re-raised so that the debugger can + display it. Otherwise, the original exception is logged, and + an :exc:`~werkzeug.exceptions.InternalServerError` is returned. + + If an error handler is registered for ``InternalServerError`` or + ``500``, it will be used. For consistency, the handler will + always receive the ``InternalServerError``. The original + unhandled exception is available as ``e.original_exception``. + + .. versionchanged:: 1.1.0 + Always passes the ``InternalServerError`` instance to the + handler, setting ``original_exception`` to the unhandled + error. + + .. versionchanged:: 1.1.0 + ``after_request`` functions and other finalization is done + even for the default 500 response when there is no handler. + + .. versionadded:: 0.3 + """ + exc_info = sys.exc_info() + got_request_exception.send(self, _async_wrapper=self.ensure_sync, exception=e) + propagate = self.config["PROPAGATE_EXCEPTIONS"] + + if propagate is None: + propagate = self.testing or self.debug + + if propagate: + # Re-raise if called with an active exception, otherwise + # raise the passed in exception. + if exc_info[1] is e: + raise + + raise e + + self.log_exception(exc_info) + server_error: InternalServerError | ft.ResponseReturnValue + server_error = InternalServerError(original_exception=e) + handler = self._find_error_handler(server_error) + + if handler is not None: + server_error = self.ensure_sync(handler)(server_error) + + return self.finalize_request(server_error, from_error_handler=True) + + def log_exception( + self, + exc_info: (tuple[type, BaseException, TracebackType] | tuple[None, None, None]), + ) -> None: + """Logs an exception. This is called by :meth:`handle_exception` + if debugging is disabled and right before the handler is called. + The default implementation logs the exception as error on the + :attr:`logger`. + + .. versionadded:: 0.8 + """ + self.logger.error( + f"Exception on {request.path} [{request.method}]", exc_info=exc_info + ) + + def raise_routing_exception(self, request: Request) -> t.NoReturn: + """Intercept routing exceptions and possibly do something else. + + In debug mode, intercept a routing redirect and replace it with + an error if the body will be discarded. + + With modern Werkzeug this shouldn't occur, since it now uses a + 308 status which tells the browser to resend the method and + body. + + .. versionchanged:: 2.1 + Don't intercept 307 and 308 redirects. + + :meta private: + :internal: + """ + if ( + not self.debug + or not isinstance(request.routing_exception, RequestRedirect) + or request.routing_exception.code in {307, 308} + or request.method in {"GET", "HEAD", "OPTIONS"} + ): + raise request.routing_exception # type: ignore + + from .debughelpers import FormDataRoutingRedirect + + raise FormDataRoutingRedirect(request) + + def dispatch_request(self) -> ft.ResponseReturnValue: + """Does the request dispatching. Matches the URL and returns the + return value of the view or error handler. This does not have to + be a response object. In order to convert the return value to a + proper response object, call :func:`make_response`. + + .. versionchanged:: 0.7 + This no longer does the exception handling, this code was + moved to the new :meth:`full_dispatch_request`. + """ + req = request_ctx.request + if req.routing_exception is not None: + self.raise_routing_exception(req) + rule: Rule = req.url_rule # type: ignore[assignment] + # if we provide automatic options for this URL and the + # request came with the OPTIONS method, reply automatically + if ( + getattr(rule, "provide_automatic_options", False) + and req.method == "OPTIONS" + ): + return self.make_default_options_response() + # otherwise dispatch to the handler for that endpoint + view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment] + return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) + + def full_dispatch_request(self) -> Response: + """Dispatches the request and on top of that performs request + pre and postprocessing as well as HTTP exception catching and + error handling. + + .. versionadded:: 0.7 + """ + self._got_first_request = True + + try: + request_started.send(self, _async_wrapper=self.ensure_sync) + rv = self.preprocess_request() + if rv is None: + rv = self.dispatch_request() + except Exception as e: + rv = self.handle_user_exception(e) + return self.finalize_request(rv) + + def finalize_request( + self, + rv: ft.ResponseReturnValue | HTTPException, + from_error_handler: bool = False, + ) -> Response: + """Given the return value from a view function this finalizes + the request by converting it into a response and invoking the + postprocessing functions. This is invoked for both normal + request dispatching as well as error handlers. + + Because this means that it might be called as a result of a + failure a special safe mode is available which can be enabled + with the `from_error_handler` flag. If enabled, failures in + response processing will be logged and otherwise ignored. + + :internal: + """ + response = self.make_response(rv) + try: + response = self.process_response(response) + request_finished.send( + self, _async_wrapper=self.ensure_sync, response=response + ) + except Exception: + if not from_error_handler: + raise + self.logger.exception( + "Request finalizing failed with an error while handling an error" + ) + return response + + def make_default_options_response(self) -> Response: + """This method is called to create the default ``OPTIONS`` response. + This can be changed through subclassing to change the default + behavior of ``OPTIONS`` responses. + + .. versionadded:: 0.7 + """ + adapter = request_ctx.url_adapter + methods = adapter.allowed_methods() # type: ignore[union-attr] + rv = self.response_class() + rv.allow.update(methods) + return rv + + def should_ignore_error(self, error: BaseException | None) -> bool: + """This is called to figure out if an error should be ignored + or not as far as the teardown system is concerned. If this + function returns ``True`` then the teardown handlers will not be + passed the error. + + .. versionadded:: 0.10 + """ + return False + + def ensure_sync(self, func: t.Callable) -> t.Callable: + """Ensure that the function is synchronous for WSGI workers. + Plain ``def`` functions are returned as-is. ``async def`` + functions are wrapped to run and wait for the response. + + Override this method to change how the app runs async views. + + .. versionadded:: 2.0 + """ + if iscoroutinefunction(func): + return self.async_to_sync(func) + + return func + + def async_to_sync( + self, func: t.Callable[..., t.Coroutine] + ) -> t.Callable[..., t.Any]: + """Return a sync function that will run the coroutine function. + + .. code-block:: python + + result = app.async_to_sync(func)(*args, **kwargs) + + Override this method to change how the app converts async code + to be synchronously callable. + + .. versionadded:: 2.0 + """ + try: + from asgiref.sync import async_to_sync as asgiref_async_to_sync + except ImportError: + raise RuntimeError( + "Install Flask with the 'async' extra in order to use async views." + ) from None + + return asgiref_async_to_sync(func) + + def url_for( + self, + endpoint: str, + *, + _anchor: str | None = None, + _method: str | None = None, + _scheme: str | None = None, + _external: bool | None = None, + **values: t.Any, + ) -> str: + """Generate a URL to the given endpoint with the given values. + + This is called by :func:`flask.url_for`, and can be called + directly as well. + + An *endpoint* is the name of a URL rule, usually added with + :meth:`@app.route() `, and usually the same name as the + view function. A route defined in a :class:`~flask.Blueprint` + will prepend the blueprint's name separated by a ``.`` to the + endpoint. + + In some cases, such as email messages, you want URLs to include + the scheme and domain, like ``https://example.com/hello``. When + not in an active request, URLs will be external by default, but + this requires setting :data:`SERVER_NAME` so Flask knows what + domain to use. :data:`APPLICATION_ROOT` and + :data:`PREFERRED_URL_SCHEME` should also be configured as + needed. This config is only used when not in an active request. + + Functions can be decorated with :meth:`url_defaults` to modify + keyword arguments before the URL is built. + + If building fails for some reason, such as an unknown endpoint + or incorrect values, the app's :meth:`handle_url_build_error` + method is called. If that returns a string, that is returned, + otherwise a :exc:`~werkzeug.routing.BuildError` is raised. + + :param endpoint: The endpoint name associated with the URL to + generate. If this starts with a ``.``, the current blueprint + name (if any) will be used. + :param _anchor: If given, append this as ``#anchor`` to the URL. + :param _method: If given, generate the URL associated with this + method for the endpoint. + :param _scheme: If given, the URL will have this scheme if it + is external. + :param _external: If given, prefer the URL to be internal + (False) or require it to be external (True). External URLs + include the scheme and domain. When not in an active + request, URLs are external by default. + :param values: Values to use for the variable parts of the URL + rule. Unknown keys are appended as query string arguments, + like ``?a=b&c=d``. + + .. versionadded:: 2.2 + Moved from ``flask.url_for``, which calls this method. + """ + req_ctx = _cv_request.get(None) + + if req_ctx is not None: + url_adapter = req_ctx.url_adapter + blueprint_name = req_ctx.request.blueprint + + # If the endpoint starts with "." and the request matches a + # blueprint, the endpoint is relative to the blueprint. + if endpoint[:1] == ".": + if blueprint_name is not None: + endpoint = f"{blueprint_name}{endpoint}" + else: + endpoint = endpoint[1:] + + # When in a request, generate a URL without scheme and + # domain by default, unless a scheme is given. + if _external is None: + _external = _scheme is not None + else: + app_ctx = _cv_app.get(None) + + # If called by helpers.url_for, an app context is active, + # use its url_adapter. Otherwise, app.url_for was called + # directly, build an adapter. + if app_ctx is not None: + url_adapter = app_ctx.url_adapter + else: + url_adapter = self.create_url_adapter(None) + + if url_adapter is None: + raise RuntimeError( + "Unable to build URLs outside an active request" + " without 'SERVER_NAME' configured. Also configure" + " 'APPLICATION_ROOT' and 'PREFERRED_URL_SCHEME' as" + " needed." + ) + + # When outside a request, generate a URL with scheme and + # domain by default. + if _external is None: + _external = True + + # It is an error to set _scheme when _external=False, in order + # to avoid accidental insecure URLs. + if _scheme is not None and not _external: + raise ValueError("When specifying '_scheme', '_external' must be True.") + + self.inject_url_defaults(endpoint, values) + + try: + rv = url_adapter.build( # type: ignore[union-attr] + endpoint, + values, + method=_method, + url_scheme=_scheme, + force_external=_external, + ) + except BuildError as error: + values.update( + _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external + ) + return self.handle_url_build_error(error, endpoint, values) + + if _anchor is not None: + _anchor = _url_quote(_anchor, safe="%!#$&'()*+,/:;=?@") + rv = f"{rv}#{_anchor}" + + return rv + + def redirect(self, location: str, code: int = 302) -> BaseResponse: + """Create a redirect response object. + + This is called by :func:`flask.redirect`, and can be called + directly as well. + + :param location: The URL to redirect to. + :param code: The status code for the redirect. + + .. versionadded:: 2.2 + Moved from ``flask.redirect``, which calls this method. + """ + return _wz_redirect(location, code=code, Response=self.response_class) + + def make_response(self, rv: ft.ResponseReturnValue) -> Response: + """Convert the return value from a view function to an instance of + :attr:`response_class`. + + :param rv: the return value from the view function. The view function + must return a response. Returning ``None``, or the view ending + without returning, is not allowed. The following types are allowed + for ``view_rv``: + + ``str`` + A response object is created with the string encoded to UTF-8 + as the body. + + ``bytes`` + A response object is created with the bytes as the body. + + ``dict`` + A dictionary that will be jsonify'd before being returned. + + ``list`` + A list that will be jsonify'd before being returned. + + ``generator`` or ``iterator`` + A generator that returns ``str`` or ``bytes`` to be + streamed as the response. + + ``tuple`` + Either ``(body, status, headers)``, ``(body, status)``, or + ``(body, headers)``, where ``body`` is any of the other types + allowed here, ``status`` is a string or an integer, and + ``headers`` is a dictionary or a list of ``(key, value)`` + tuples. If ``body`` is a :attr:`response_class` instance, + ``status`` overwrites the exiting value and ``headers`` are + extended. + + :attr:`response_class` + The object is returned unchanged. + + other :class:`~werkzeug.wrappers.Response` class + The object is coerced to :attr:`response_class`. + + :func:`callable` + The function is called as a WSGI application. The result is + used to create a response object. + + .. versionchanged:: 2.2 + A generator will be converted to a streaming response. + A list will be converted to a JSON response. + + .. versionchanged:: 1.1 + A dict will be converted to a JSON response. + + .. versionchanged:: 0.9 + Previously a tuple was interpreted as the arguments for the + response object. + """ + + status = headers = None + + # unpack tuple returns + if isinstance(rv, tuple): + len_rv = len(rv) + + # a 3-tuple is unpacked directly + if len_rv == 3: + rv, status, headers = rv # type: ignore[misc] + # decide if a 2-tuple has status or headers + elif len_rv == 2: + if isinstance(rv[1], (Headers, dict, tuple, list)): + rv, headers = rv + else: + rv, status = rv # type: ignore[assignment,misc] + # other sized tuples are not allowed + else: + raise TypeError( + "The view function did not return a valid response tuple." + " The tuple must have the form (body, status, headers)," + " (body, status), or (body, headers)." + ) + + # the body must not be None + if rv is None: + raise TypeError( + f"The view function for {request.endpoint!r} did not" + " return a valid response. The function either returned" + " None or ended without a return statement." + ) + + # make sure the body is an instance of the response class + if not isinstance(rv, self.response_class): + if isinstance(rv, (str, bytes, bytearray)) or isinstance(rv, _abc_Iterator): + # let the response class set the status and headers instead of + # waiting to do it manually, so that the class can handle any + # special logic + rv = self.response_class( + rv, + status=status, + headers=headers, # type: ignore[arg-type] + ) + status = headers = None + elif isinstance(rv, (dict, list)): + rv = self.json.response(rv) + elif isinstance(rv, BaseResponse) or callable(rv): + # evaluate a WSGI callable, or coerce a different response + # class to the correct type + try: + rv = self.response_class.force_type( + rv, request.environ # type: ignore[arg-type] + ) + except TypeError as e: + raise TypeError( + f"{e}\nThe view function did not return a valid" + " response. The return type must be a string," + " dict, list, tuple with headers or status," + " Response instance, or WSGI callable, but it" + f" was a {type(rv).__name__}." + ).with_traceback(sys.exc_info()[2]) from None + else: + raise TypeError( + "The view function did not return a valid" + " response. The return type must be a string," + " dict, list, tuple with headers or status," + " Response instance, or WSGI callable, but it was a" + f" {type(rv).__name__}." + ) + + rv = t.cast(Response, rv) + # prefer the status if it was provided + if status is not None: + if isinstance(status, (str, bytes, bytearray)): + rv.status = status + else: + rv.status_code = status + + # extend existing headers with provided headers + if headers: + rv.headers.update(headers) # type: ignore[arg-type] + + return rv + + def create_url_adapter(self, request: Request | None) -> MapAdapter | None: + """Creates a URL adapter for the given request. The URL adapter + is created at a point where the request context is not yet set + up so the request is passed explicitly. + + .. versionadded:: 0.6 + + .. versionchanged:: 0.9 + This can now also be called without a request object when the + URL adapter is created for the application context. + + .. versionchanged:: 1.0 + :data:`SERVER_NAME` no longer implicitly enables subdomain + matching. Use :attr:`subdomain_matching` instead. + """ + if request is not None: + # If subdomain matching is disabled (the default), use the + # default subdomain in all cases. This should be the default + # in Werkzeug but it currently does not have that feature. + if not self.subdomain_matching: + subdomain = self.url_map.default_subdomain or None + else: + subdomain = None + + return self.url_map.bind_to_environ( + request.environ, + server_name=self.config["SERVER_NAME"], + subdomain=subdomain, + ) + # We need at the very least the server name to be set for this + # to work. + if self.config["SERVER_NAME"] is not None: + return self.url_map.bind( + self.config["SERVER_NAME"], + script_name=self.config["APPLICATION_ROOT"], + url_scheme=self.config["PREFERRED_URL_SCHEME"], + ) + + return None + + def inject_url_defaults(self, endpoint: str, values: dict) -> None: + """Injects the URL defaults for the given endpoint directly into + the values dictionary passed. This is used internally and + automatically called on URL building. + + .. versionadded:: 0.7 + """ + names: t.Iterable[str | None] = (None,) + + # url_for may be called outside a request context, parse the + # passed endpoint instead of using request.blueprints. + if "." in endpoint: + names = chain( + names, reversed(_split_blueprint_path(endpoint.rpartition(".")[0])) + ) + + for name in names: + if name in self.url_default_functions: + for func in self.url_default_functions[name]: + func(endpoint, values) + + def handle_url_build_error( + self, error: BuildError, endpoint: str, values: dict[str, t.Any] + ) -> str: + """Called by :meth:`.url_for` if a + :exc:`~werkzeug.routing.BuildError` was raised. If this returns + a value, it will be returned by ``url_for``, otherwise the error + will be re-raised. + + Each function in :attr:`url_build_error_handlers` is called with + ``error``, ``endpoint`` and ``values``. If a function returns + ``None`` or raises a ``BuildError``, it is skipped. Otherwise, + its return value is returned by ``url_for``. + + :param error: The active ``BuildError`` being handled. + :param endpoint: The endpoint being built. + :param values: The keyword arguments passed to ``url_for``. + """ + for handler in self.url_build_error_handlers: + try: + rv = handler(error, endpoint, values) + except BuildError as e: + # make error available outside except block + error = e + else: + if rv is not None: + return rv + + # Re-raise if called with an active exception, otherwise raise + # the passed in exception. + if error is sys.exc_info()[1]: + raise + + raise error + + def preprocess_request(self) -> ft.ResponseReturnValue | None: + """Called before the request is dispatched. Calls + :attr:`url_value_preprocessors` registered with the app and the + current blueprint (if any). Then calls :attr:`before_request_funcs` + registered with the app and the blueprint. + + If any :meth:`before_request` handler returns a non-None value, the + value is handled as if it was the return value from the view, and + further request handling is stopped. + """ + names = (None, *reversed(request.blueprints)) + + for name in names: + if name in self.url_value_preprocessors: + for url_func in self.url_value_preprocessors[name]: + url_func(request.endpoint, request.view_args) + + for name in names: + if name in self.before_request_funcs: + for before_func in self.before_request_funcs[name]: + rv = self.ensure_sync(before_func)() + + if rv is not None: + return rv + + return None + + def process_response(self, response: Response) -> Response: + """Can be overridden in order to modify the response object + before it's sent to the WSGI server. By default this will + call all the :meth:`after_request` decorated functions. + + .. versionchanged:: 0.5 + As of Flask 0.5 the functions registered for after request + execution are called in reverse order of registration. + + :param response: a :attr:`response_class` object. + :return: a new response object or the same, has to be an + instance of :attr:`response_class`. + """ + ctx = request_ctx._get_current_object() # type: ignore[attr-defined] + + for func in ctx._after_request_functions: + response = self.ensure_sync(func)(response) + + for name in chain(request.blueprints, (None,)): + if name in self.after_request_funcs: + for func in reversed(self.after_request_funcs[name]): + response = self.ensure_sync(func)(response) + + if not self.session_interface.is_null_session(ctx.session): + self.session_interface.save_session(self, ctx.session, response) + + return response + + def do_teardown_request( + self, exc: BaseException | None = _sentinel # type: ignore + ) -> None: + """Called after the request is dispatched and the response is + returned, right before the request context is popped. + + This calls all functions decorated with + :meth:`teardown_request`, and :meth:`Blueprint.teardown_request` + if a blueprint handled the request. Finally, the + :data:`request_tearing_down` signal is sent. + + This is called by + :meth:`RequestContext.pop() `, + which may be delayed during testing to maintain access to + resources. + + :param exc: An unhandled exception raised while dispatching the + request. Detected from the current exception information if + not passed. Passed to each teardown function. + + .. versionchanged:: 0.9 + Added the ``exc`` argument. + """ + if exc is _sentinel: + exc = sys.exc_info()[1] + + for name in chain(request.blueprints, (None,)): + if name in self.teardown_request_funcs: + for func in reversed(self.teardown_request_funcs[name]): + self.ensure_sync(func)(exc) + + request_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc) + + def do_teardown_appcontext( + self, exc: BaseException | None = _sentinel # type: ignore + ) -> None: + """Called right before the application context is popped. + + When handling a request, the application context is popped + after the request context. See :meth:`do_teardown_request`. + + This calls all functions decorated with + :meth:`teardown_appcontext`. Then the + :data:`appcontext_tearing_down` signal is sent. + + This is called by + :meth:`AppContext.pop() `. + + .. versionadded:: 0.9 + """ + if exc is _sentinel: + exc = sys.exc_info()[1] + + for func in reversed(self.teardown_appcontext_funcs): + self.ensure_sync(func)(exc) + + appcontext_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc) + + def app_context(self) -> AppContext: + """Create an :class:`~flask.ctx.AppContext`. Use as a ``with`` + block to push the context, which will make :data:`current_app` + point at this application. + + An application context is automatically pushed by + :meth:`RequestContext.push() ` + when handling a request, and when running a CLI command. Use + this to manually create a context outside of these situations. + + :: + + with app.app_context(): + init_db() + + See :doc:`/appcontext`. + + .. versionadded:: 0.9 + """ + return AppContext(self) + + def request_context(self, environ: dict) -> RequestContext: + """Create a :class:`~flask.ctx.RequestContext` representing a + WSGI environment. Use a ``with`` block to push the context, + which will make :data:`request` point at this request. + + See :doc:`/reqcontext`. + + Typically you should not call this from your own code. A request + context is automatically pushed by the :meth:`wsgi_app` when + handling a request. Use :meth:`test_request_context` to create + an environment and context instead of this method. + + :param environ: a WSGI environment + """ + return RequestContext(self, environ) + + def test_request_context(self, *args: t.Any, **kwargs: t.Any) -> RequestContext: + """Create a :class:`~flask.ctx.RequestContext` for a WSGI + environment created from the given values. This is mostly useful + during testing, where you may want to run a function that uses + request data without dispatching a full request. + + See :doc:`/reqcontext`. + + Use a ``with`` block to push the context, which will make + :data:`request` point at the request for the created + environment. :: + + with app.test_request_context(...): + generate_report() + + When using the shell, it may be easier to push and pop the + context manually to avoid indentation. :: + + ctx = app.test_request_context(...) + ctx.push() + ... + ctx.pop() + + Takes the same arguments as Werkzeug's + :class:`~werkzeug.test.EnvironBuilder`, with some defaults from + the application. See the linked Werkzeug docs for most of the + available arguments. Flask-specific behavior is listed here. + + :param path: URL path being requested. + :param base_url: Base URL where the app is being served, which + ``path`` is relative to. If not given, built from + :data:`PREFERRED_URL_SCHEME`, ``subdomain``, + :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`. + :param subdomain: Subdomain name to append to + :data:`SERVER_NAME`. + :param url_scheme: Scheme to use instead of + :data:`PREFERRED_URL_SCHEME`. + :param data: The request body, either as a string or a dict of + form keys and values. + :param json: If given, this is serialized as JSON and passed as + ``data``. Also defaults ``content_type`` to + ``application/json``. + :param args: other positional arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + :param kwargs: other keyword arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + """ + from .testing import EnvironBuilder + + builder = EnvironBuilder(self, *args, **kwargs) + + try: + return self.request_context(builder.get_environ()) + finally: + builder.close() + + def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any: + """The actual WSGI application. This is not implemented in + :meth:`__call__` so that middlewares can be applied without + losing a reference to the app object. Instead of doing this:: + + app = MyMiddleware(app) + + It's a better idea to do this instead:: + + app.wsgi_app = MyMiddleware(app.wsgi_app) + + Then you still have the original application object around and + can continue to call methods on it. + + .. versionchanged:: 0.7 + Teardown events for the request and app contexts are called + even if an unhandled error occurs. Other events may not be + called depending on when an error occurs during dispatch. + See :ref:`callbacks-and-errors`. + + :param environ: A WSGI environment. + :param start_response: A callable accepting a status code, + a list of headers, and an optional exception context to + start the response. + """ + ctx = self.request_context(environ) + error: BaseException | None = None + try: + try: + ctx.push() + response = self.full_dispatch_request() + except Exception as e: + error = e + response = self.handle_exception(e) + except: # noqa: B001 + error = sys.exc_info()[1] + raise + return response(environ, start_response) + finally: + if "werkzeug.debug.preserve_context" in environ: + environ["werkzeug.debug.preserve_context"](_cv_app.get()) + environ["werkzeug.debug.preserve_context"](_cv_request.get()) + + if error is not None and self.should_ignore_error(error): + error = None + + ctx.pop(error) + + def __call__(self, environ: dict, start_response: t.Callable) -> t.Any: + """The WSGI server calls the Flask application object as the + WSGI application. This calls :meth:`wsgi_app`, which can be + wrapped to apply middleware. + """ + return self.wsgi_app(environ, start_response) diff --git a/venv/lib/python3.12/site-packages/flask/blueprints.py b/venv/lib/python3.12/site-packages/flask/blueprints.py new file mode 100644 index 0000000..0407f86 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/blueprints.py @@ -0,0 +1,626 @@ +from __future__ import annotations + +import os +import typing as t +from collections import defaultdict +from functools import update_wrapper + +from . import typing as ft +from .scaffold import _endpoint_from_view_func +from .scaffold import _sentinel +from .scaffold import Scaffold +from .scaffold import setupmethod + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + +DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable] +T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable) +T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable) +T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable) +T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) +T_template_context_processor = t.TypeVar( + "T_template_context_processor", bound=ft.TemplateContextProcessorCallable +) +T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable) +T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable) +T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable) +T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable) +T_url_value_preprocessor = t.TypeVar( + "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable +) + + +class BlueprintSetupState: + """Temporary holder object for registering a blueprint with the + application. An instance of this class is created by the + :meth:`~flask.Blueprint.make_setup_state` method and later passed + to all register callback functions. + """ + + def __init__( + self, + blueprint: Blueprint, + app: Flask, + options: t.Any, + first_registration: bool, + ) -> None: + #: a reference to the current application + self.app = app + + #: a reference to the blueprint that created this setup state. + self.blueprint = blueprint + + #: a dictionary with all options that were passed to the + #: :meth:`~flask.Flask.register_blueprint` method. + self.options = options + + #: as blueprints can be registered multiple times with the + #: application and not everything wants to be registered + #: multiple times on it, this attribute can be used to figure + #: out if the blueprint was registered in the past already. + self.first_registration = first_registration + + subdomain = self.options.get("subdomain") + if subdomain is None: + subdomain = self.blueprint.subdomain + + #: The subdomain that the blueprint should be active for, ``None`` + #: otherwise. + self.subdomain = subdomain + + url_prefix = self.options.get("url_prefix") + if url_prefix is None: + url_prefix = self.blueprint.url_prefix + #: The prefix that should be used for all URLs defined on the + #: blueprint. + self.url_prefix = url_prefix + + self.name = self.options.get("name", blueprint.name) + self.name_prefix = self.options.get("name_prefix", "") + + #: A dictionary with URL defaults that is added to each and every + #: URL that was defined with the blueprint. + self.url_defaults = dict(self.blueprint.url_values_defaults) + self.url_defaults.update(self.options.get("url_defaults", ())) + + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: t.Callable | None = None, + **options: t.Any, + ) -> None: + """A helper method to register a rule (and optionally a view function) + to the application. The endpoint is automatically prefixed with the + blueprint's name. + """ + if self.url_prefix is not None: + if rule: + rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/"))) + else: + rule = self.url_prefix + options.setdefault("subdomain", self.subdomain) + if endpoint is None: + endpoint = _endpoint_from_view_func(view_func) # type: ignore + defaults = self.url_defaults + if "defaults" in options: + defaults = dict(defaults, **options.pop("defaults")) + + self.app.add_url_rule( + rule, + f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."), + view_func, + defaults=defaults, + **options, + ) + + +class Blueprint(Scaffold): + """Represents a blueprint, a collection of routes and other + app-related functions that can be registered on a real application + later. + + A blueprint is an object that allows defining application functions + without requiring an application object ahead of time. It uses the + same decorators as :class:`~flask.Flask`, but defers the need for an + application by recording them for later registration. + + Decorating a function with a blueprint creates a deferred function + that is called with :class:`~flask.blueprints.BlueprintSetupState` + when the blueprint is registered on an application. + + See :doc:`/blueprints` for more information. + + :param name: The name of the blueprint. Will be prepended to each + endpoint name. + :param import_name: The name of the blueprint package, usually + ``__name__``. This helps locate the ``root_path`` for the + blueprint. + :param static_folder: A folder with static files that should be + served by the blueprint's static route. The path is relative to + the blueprint's root path. Blueprint static files are disabled + by default. + :param static_url_path: The url to serve static files from. + Defaults to ``static_folder``. If the blueprint does not have + a ``url_prefix``, the app's static route will take precedence, + and the blueprint's static files won't be accessible. + :param template_folder: A folder with templates that should be added + to the app's template search path. The path is relative to the + blueprint's root path. Blueprint templates are disabled by + default. Blueprint templates have a lower precedence than those + in the app's templates folder. + :param url_prefix: A path to prepend to all of the blueprint's URLs, + to make them distinct from the rest of the app's routes. + :param subdomain: A subdomain that blueprint routes will match on by + default. + :param url_defaults: A dict of default values that blueprint routes + will receive by default. + :param root_path: By default, the blueprint will automatically set + this based on ``import_name``. In certain situations this + automatic detection can fail, so the path can be specified + manually instead. + + .. versionchanged:: 1.1.0 + Blueprints have a ``cli`` group to register nested CLI commands. + The ``cli_group`` parameter controls the name of the group under + the ``flask`` command. + + .. versionadded:: 0.7 + """ + + _got_registered_once = False + + def __init__( + self, + name: str, + import_name: str, + static_folder: str | os.PathLike | None = None, + static_url_path: str | None = None, + template_folder: str | os.PathLike | None = None, + url_prefix: str | None = None, + subdomain: str | None = None, + url_defaults: dict | None = None, + root_path: str | None = None, + cli_group: str | None = _sentinel, # type: ignore + ): + super().__init__( + import_name=import_name, + static_folder=static_folder, + static_url_path=static_url_path, + template_folder=template_folder, + root_path=root_path, + ) + + if not name: + raise ValueError("'name' may not be empty.") + + if "." in name: + raise ValueError("'name' may not contain a dot '.' character.") + + self.name = name + self.url_prefix = url_prefix + self.subdomain = subdomain + self.deferred_functions: list[DeferredSetupFunction] = [] + + if url_defaults is None: + url_defaults = {} + + self.url_values_defaults = url_defaults + self.cli_group = cli_group + self._blueprints: list[tuple[Blueprint, dict]] = [] + + def _check_setup_finished(self, f_name: str) -> None: + if self._got_registered_once: + raise AssertionError( + f"The setup method '{f_name}' can no longer be called on the blueprint" + f" '{self.name}'. It has already been registered at least once, any" + " changes will not be applied consistently.\n" + "Make sure all imports, decorators, functions, etc. needed to set up" + " the blueprint are done before registering it." + ) + + @setupmethod + def record(self, func: t.Callable) -> None: + """Registers a function that is called when the blueprint is + registered on the application. This function is called with the + state as argument as returned by the :meth:`make_setup_state` + method. + """ + self.deferred_functions.append(func) + + @setupmethod + def record_once(self, func: t.Callable) -> None: + """Works like :meth:`record` but wraps the function in another + function that will ensure the function is only called once. If the + blueprint is registered a second time on the application, the + function passed is not called. + """ + + def wrapper(state: BlueprintSetupState) -> None: + if state.first_registration: + func(state) + + self.record(update_wrapper(wrapper, func)) + + def make_setup_state( + self, app: Flask, options: dict, first_registration: bool = False + ) -> BlueprintSetupState: + """Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState` + object that is later passed to the register callback functions. + Subclasses can override this to return a subclass of the setup state. + """ + return BlueprintSetupState(self, app, options, first_registration) + + @setupmethod + def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None: + """Register a :class:`~flask.Blueprint` on this blueprint. Keyword + arguments passed to this method will override the defaults set + on the blueprint. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + + .. versionadded:: 2.0 + """ + if blueprint is self: + raise ValueError("Cannot register a blueprint on itself") + self._blueprints.append((blueprint, options)) + + def register(self, app: Flask, options: dict) -> None: + """Called by :meth:`Flask.register_blueprint` to register all + views and callbacks registered on the blueprint with the + application. Creates a :class:`.BlueprintSetupState` and calls + each :meth:`record` callback with it. + + :param app: The application this blueprint is being registered + with. + :param options: Keyword arguments forwarded from + :meth:`~Flask.register_blueprint`. + + .. versionchanged:: 2.3 + Nested blueprints now correctly apply subdomains. + + .. versionchanged:: 2.1 + Registering the same blueprint with the same name multiple + times is an error. + + .. versionchanged:: 2.0.1 + Nested blueprints are registered with their dotted name. + This allows different blueprints with the same name to be + nested at different locations. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + """ + name_prefix = options.get("name_prefix", "") + self_name = options.get("name", self.name) + name = f"{name_prefix}.{self_name}".lstrip(".") + + if name in app.blueprints: + bp_desc = "this" if app.blueprints[name] is self else "a different" + existing_at = f" '{name}'" if self_name != name else "" + + raise ValueError( + f"The name '{self_name}' is already registered for" + f" {bp_desc} blueprint{existing_at}. Use 'name=' to" + f" provide a unique name." + ) + + first_bp_registration = not any(bp is self for bp in app.blueprints.values()) + first_name_registration = name not in app.blueprints + + app.blueprints[name] = self + self._got_registered_once = True + state = self.make_setup_state(app, options, first_bp_registration) + + if self.has_static_folder: + state.add_url_rule( + f"{self.static_url_path}/", + view_func=self.send_static_file, + endpoint="static", + ) + + # Merge blueprint data into parent. + if first_bp_registration or first_name_registration: + + def extend(bp_dict, parent_dict): + for key, values in bp_dict.items(): + key = name if key is None else f"{name}.{key}" + parent_dict[key].extend(values) + + for key, value in self.error_handler_spec.items(): + key = name if key is None else f"{name}.{key}" + value = defaultdict( + dict, + { + code: { + exc_class: func for exc_class, func in code_values.items() + } + for code, code_values in value.items() + }, + ) + app.error_handler_spec[key] = value + + for endpoint, func in self.view_functions.items(): + app.view_functions[endpoint] = func + + extend(self.before_request_funcs, app.before_request_funcs) + extend(self.after_request_funcs, app.after_request_funcs) + extend( + self.teardown_request_funcs, + app.teardown_request_funcs, + ) + extend(self.url_default_functions, app.url_default_functions) + extend(self.url_value_preprocessors, app.url_value_preprocessors) + extend(self.template_context_processors, app.template_context_processors) + + for deferred in self.deferred_functions: + deferred(state) + + cli_resolved_group = options.get("cli_group", self.cli_group) + + if self.cli.commands: + if cli_resolved_group is None: + app.cli.commands.update(self.cli.commands) + elif cli_resolved_group is _sentinel: + self.cli.name = name + app.cli.add_command(self.cli) + else: + self.cli.name = cli_resolved_group + app.cli.add_command(self.cli) + + for blueprint, bp_options in self._blueprints: + bp_options = bp_options.copy() + bp_url_prefix = bp_options.get("url_prefix") + bp_subdomain = bp_options.get("subdomain") + + if bp_subdomain is None: + bp_subdomain = blueprint.subdomain + + if state.subdomain is not None and bp_subdomain is not None: + bp_options["subdomain"] = bp_subdomain + "." + state.subdomain + elif bp_subdomain is not None: + bp_options["subdomain"] = bp_subdomain + elif state.subdomain is not None: + bp_options["subdomain"] = state.subdomain + + if bp_url_prefix is None: + bp_url_prefix = blueprint.url_prefix + + if state.url_prefix is not None and bp_url_prefix is not None: + bp_options["url_prefix"] = ( + state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/") + ) + elif bp_url_prefix is not None: + bp_options["url_prefix"] = bp_url_prefix + elif state.url_prefix is not None: + bp_options["url_prefix"] = state.url_prefix + + bp_options["name_prefix"] = name + blueprint.register(app, bp_options) + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, + **options: t.Any, + ) -> None: + """Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for + full documentation. + + The URL rule is prefixed with the blueprint's URL prefix. The endpoint name, + used with :func:`url_for`, is prefixed with the blueprint's name. + """ + if endpoint and "." in endpoint: + raise ValueError("'endpoint' may not contain a dot '.' character.") + + if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__: + raise ValueError("'view_func' name may not contain a dot '.' character.") + + self.record( + lambda s: s.add_url_rule( + rule, + endpoint, + view_func, + provide_automatic_options=provide_automatic_options, + **options, + ) + ) + + @setupmethod + def app_template_filter( + self, name: str | None = None + ) -> t.Callable[[T_template_filter], T_template_filter]: + """Register a template filter, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_filter`. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def decorator(f: T_template_filter) -> T_template_filter: + self.add_app_template_filter(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_filter( + self, f: ft.TemplateFilterCallable, name: str | None = None + ) -> None: + """Register a template filter, available in any template rendered by the + application. Works like the :meth:`app_template_filter` decorator. Equivalent to + :meth:`.Flask.add_template_filter`. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.filters[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def app_template_test( + self, name: str | None = None + ) -> t.Callable[[T_template_test], T_template_test]: + """Register a template test, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_test`. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def decorator(f: T_template_test) -> T_template_test: + self.add_app_template_test(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_test( + self, f: ft.TemplateTestCallable, name: str | None = None + ) -> None: + """Register a template test, available in any template rendered by the + application. Works like the :meth:`app_template_test` decorator. Equivalent to + :meth:`.Flask.add_template_test`. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.tests[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def app_template_global( + self, name: str | None = None + ) -> t.Callable[[T_template_global], T_template_global]: + """Register a template global, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_global`. + + .. versionadded:: 0.10 + + :param name: the optional name of the global, otherwise the + function name will be used. + """ + + def decorator(f: T_template_global) -> T_template_global: + self.add_app_template_global(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_global( + self, f: ft.TemplateGlobalCallable, name: str | None = None + ) -> None: + """Register a template global, available in any template rendered by the + application. Works like the :meth:`app_template_global` decorator. Equivalent to + :meth:`.Flask.add_template_global`. + + .. versionadded:: 0.10 + + :param name: the optional name of the global, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.globals[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def before_app_request(self, f: T_before_request) -> T_before_request: + """Like :meth:`before_request`, but before every request, not only those handled + by the blueprint. Equivalent to :meth:`.Flask.before_request`. + """ + self.record_once( + lambda s: s.app.before_request_funcs.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def after_app_request(self, f: T_after_request) -> T_after_request: + """Like :meth:`after_request`, but after every request, not only those handled + by the blueprint. Equivalent to :meth:`.Flask.after_request`. + """ + self.record_once( + lambda s: s.app.after_request_funcs.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def teardown_app_request(self, f: T_teardown) -> T_teardown: + """Like :meth:`teardown_request`, but after every request, not only those + handled by the blueprint. Equivalent to :meth:`.Flask.teardown_request`. + """ + self.record_once( + lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def app_context_processor( + self, f: T_template_context_processor + ) -> T_template_context_processor: + """Like :meth:`context_processor`, but for templates rendered by every view, not + only by the blueprint. Equivalent to :meth:`.Flask.context_processor`. + """ + self.record_once( + lambda s: s.app.template_context_processors.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def app_errorhandler( + self, code: type[Exception] | int + ) -> t.Callable[[T_error_handler], T_error_handler]: + """Like :meth:`errorhandler`, but for every request, not only those handled by + the blueprint. Equivalent to :meth:`.Flask.errorhandler`. + """ + + def decorator(f: T_error_handler) -> T_error_handler: + self.record_once(lambda s: s.app.errorhandler(code)(f)) + return f + + return decorator + + @setupmethod + def app_url_value_preprocessor( + self, f: T_url_value_preprocessor + ) -> T_url_value_preprocessor: + """Like :meth:`url_value_preprocessor`, but for every request, not only those + handled by the blueprint. Equivalent to :meth:`.Flask.url_value_preprocessor`. + """ + self.record_once( + lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults: + """Like :meth:`url_defaults`, but for every request, not only those handled by + the blueprint. Equivalent to :meth:`.Flask.url_defaults`. + """ + self.record_once( + lambda s: s.app.url_default_functions.setdefault(None, []).append(f) + ) + return f diff --git a/venv/lib/python3.12/site-packages/flask/cli.py b/venv/lib/python3.12/site-packages/flask/cli.py new file mode 100644 index 0000000..dda266b --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/cli.py @@ -0,0 +1,1068 @@ +from __future__ import annotations + +import ast +import importlib.metadata +import inspect +import os +import platform +import re +import sys +import traceback +import typing as t +from functools import update_wrapper +from operator import itemgetter + +import click +from click.core import ParameterSource +from werkzeug import run_simple +from werkzeug.serving import is_running_from_reloader +from werkzeug.utils import import_string + +from .globals import current_app +from .helpers import get_debug_flag +from .helpers import get_load_dotenv + +if t.TYPE_CHECKING: + from .app import Flask + + +class NoAppException(click.UsageError): + """Raised if an application cannot be found or loaded.""" + + +def find_best_app(module): + """Given a module instance this tries to find the best possible + application in the module or raises an exception. + """ + from . import Flask + + # Search for the most common names first. + for attr_name in ("app", "application"): + app = getattr(module, attr_name, None) + + if isinstance(app, Flask): + return app + + # Otherwise find the only object that is a Flask instance. + matches = [v for v in module.__dict__.values() if isinstance(v, Flask)] + + if len(matches) == 1: + return matches[0] + elif len(matches) > 1: + raise NoAppException( + "Detected multiple Flask applications in module" + f" '{module.__name__}'. Use '{module.__name__}:name'" + " to specify the correct one." + ) + + # Search for app factory functions. + for attr_name in ("create_app", "make_app"): + app_factory = getattr(module, attr_name, None) + + if inspect.isfunction(app_factory): + try: + app = app_factory() + + if isinstance(app, Flask): + return app + except TypeError as e: + if not _called_with_wrong_args(app_factory): + raise + + raise NoAppException( + f"Detected factory '{attr_name}' in module '{module.__name__}'," + " but could not call it without arguments. Use" + f" '{module.__name__}:{attr_name}(args)'" + " to specify arguments." + ) from e + + raise NoAppException( + "Failed to find Flask application or factory in module" + f" '{module.__name__}'. Use '{module.__name__}:name'" + " to specify one." + ) + + +def _called_with_wrong_args(f): + """Check whether calling a function raised a ``TypeError`` because + the call failed or because something in the factory raised the + error. + + :param f: The function that was called. + :return: ``True`` if the call failed. + """ + tb = sys.exc_info()[2] + + try: + while tb is not None: + if tb.tb_frame.f_code is f.__code__: + # In the function, it was called successfully. + return False + + tb = tb.tb_next + + # Didn't reach the function. + return True + finally: + # Delete tb to break a circular reference. + # https://docs.python.org/2/library/sys.html#sys.exc_info + del tb + + +def find_app_by_string(module, app_name): + """Check if the given string is a variable name or a function. Call + a function to get the app instance, or return the variable directly. + """ + from . import Flask + + # Parse app_name as a single expression to determine if it's a valid + # attribute name or function call. + try: + expr = ast.parse(app_name.strip(), mode="eval").body + except SyntaxError: + raise NoAppException( + f"Failed to parse {app_name!r} as an attribute name or function call." + ) from None + + if isinstance(expr, ast.Name): + name = expr.id + args = [] + kwargs = {} + elif isinstance(expr, ast.Call): + # Ensure the function name is an attribute name only. + if not isinstance(expr.func, ast.Name): + raise NoAppException( + f"Function reference must be a simple name: {app_name!r}." + ) + + name = expr.func.id + + # Parse the positional and keyword arguments as literals. + try: + args = [ast.literal_eval(arg) for arg in expr.args] + kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in expr.keywords} + except ValueError: + # literal_eval gives cryptic error messages, show a generic + # message with the full expression instead. + raise NoAppException( + f"Failed to parse arguments as literal values: {app_name!r}." + ) from None + else: + raise NoAppException( + f"Failed to parse {app_name!r} as an attribute name or function call." + ) + + try: + attr = getattr(module, name) + except AttributeError as e: + raise NoAppException( + f"Failed to find attribute {name!r} in {module.__name__!r}." + ) from e + + # If the attribute is a function, call it with any args and kwargs + # to get the real application. + if inspect.isfunction(attr): + try: + app = attr(*args, **kwargs) + except TypeError as e: + if not _called_with_wrong_args(attr): + raise + + raise NoAppException( + f"The factory {app_name!r} in module" + f" {module.__name__!r} could not be called with the" + " specified arguments." + ) from e + else: + app = attr + + if isinstance(app, Flask): + return app + + raise NoAppException( + "A valid Flask application was not obtained from" + f" '{module.__name__}:{app_name}'." + ) + + +def prepare_import(path): + """Given a filename this will try to calculate the python path, add it + to the search path and return the actual module name that is expected. + """ + path = os.path.realpath(path) + + fname, ext = os.path.splitext(path) + if ext == ".py": + path = fname + + if os.path.basename(path) == "__init__": + path = os.path.dirname(path) + + module_name = [] + + # move up until outside package structure (no __init__.py) + while True: + path, name = os.path.split(path) + module_name.append(name) + + if not os.path.exists(os.path.join(path, "__init__.py")): + break + + if sys.path[0] != path: + sys.path.insert(0, path) + + return ".".join(module_name[::-1]) + + +def locate_app(module_name, app_name, raise_if_not_found=True): + try: + __import__(module_name) + except ImportError: + # Reraise the ImportError if it occurred within the imported module. + # Determine this by checking whether the trace has a depth > 1. + if sys.exc_info()[2].tb_next: + raise NoAppException( + f"While importing {module_name!r}, an ImportError was" + f" raised:\n\n{traceback.format_exc()}" + ) from None + elif raise_if_not_found: + raise NoAppException(f"Could not import {module_name!r}.") from None + else: + return + + module = sys.modules[module_name] + + if app_name is None: + return find_best_app(module) + else: + return find_app_by_string(module, app_name) + + +def get_version(ctx, param, value): + if not value or ctx.resilient_parsing: + return + + flask_version = importlib.metadata.version("flask") + werkzeug_version = importlib.metadata.version("werkzeug") + + click.echo( + f"Python {platform.python_version()}\n" + f"Flask {flask_version}\n" + f"Werkzeug {werkzeug_version}", + color=ctx.color, + ) + ctx.exit() + + +version_option = click.Option( + ["--version"], + help="Show the Flask version.", + expose_value=False, + callback=get_version, + is_flag=True, + is_eager=True, +) + + +class ScriptInfo: + """Helper object to deal with Flask applications. This is usually not + necessary to interface with as it's used internally in the dispatching + to click. In future versions of Flask this object will most likely play + a bigger role. Typically it's created automatically by the + :class:`FlaskGroup` but you can also manually create it and pass it + onwards as click object. + """ + + def __init__( + self, + app_import_path: str | None = None, + create_app: t.Callable[..., Flask] | None = None, + set_debug_flag: bool = True, + ) -> None: + #: Optionally the import path for the Flask application. + self.app_import_path = app_import_path + #: Optionally a function that is passed the script info to create + #: the instance of the application. + self.create_app = create_app + #: A dictionary with arbitrary data that can be associated with + #: this script info. + self.data: dict[t.Any, t.Any] = {} + self.set_debug_flag = set_debug_flag + self._loaded_app: Flask | None = None + + def load_app(self) -> Flask: + """Loads the Flask app (if not yet loaded) and returns it. Calling + this multiple times will just result in the already loaded app to + be returned. + """ + if self._loaded_app is not None: + return self._loaded_app + + if self.create_app is not None: + app = self.create_app() + else: + if self.app_import_path: + path, name = ( + re.split(r":(?![\\/])", self.app_import_path, maxsplit=1) + [None] + )[:2] + import_name = prepare_import(path) + app = locate_app(import_name, name) + else: + for path in ("wsgi.py", "app.py"): + import_name = prepare_import(path) + app = locate_app(import_name, None, raise_if_not_found=False) + + if app: + break + + if not app: + raise NoAppException( + "Could not locate a Flask application. Use the" + " 'flask --app' option, 'FLASK_APP' environment" + " variable, or a 'wsgi.py' or 'app.py' file in the" + " current directory." + ) + + if self.set_debug_flag: + # Update the app's debug flag through the descriptor so that + # other values repopulate as well. + app.debug = get_debug_flag() + + self._loaded_app = app + return app + + +pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True) + + +def with_appcontext(f): + """Wraps a callback so that it's guaranteed to be executed with the + script's application context. + + Custom commands (and their options) registered under ``app.cli`` or + ``blueprint.cli`` will always have an app context available, this + decorator is not required in that case. + + .. versionchanged:: 2.2 + The app context is active for subcommands as well as the + decorated callback. The app context is always available to + ``app.cli`` command and parameter callbacks. + """ + + @click.pass_context + def decorator(__ctx, *args, **kwargs): + if not current_app: + app = __ctx.ensure_object(ScriptInfo).load_app() + __ctx.with_resource(app.app_context()) + + return __ctx.invoke(f, *args, **kwargs) + + return update_wrapper(decorator, f) + + +class AppGroup(click.Group): + """This works similar to a regular click :class:`~click.Group` but it + changes the behavior of the :meth:`command` decorator so that it + automatically wraps the functions in :func:`with_appcontext`. + + Not to be confused with :class:`FlaskGroup`. + """ + + def command(self, *args, **kwargs): + """This works exactly like the method of the same name on a regular + :class:`click.Group` but it wraps callbacks in :func:`with_appcontext` + unless it's disabled by passing ``with_appcontext=False``. + """ + wrap_for_ctx = kwargs.pop("with_appcontext", True) + + def decorator(f): + if wrap_for_ctx: + f = with_appcontext(f) + return click.Group.command(self, *args, **kwargs)(f) + + return decorator + + def group(self, *args, **kwargs): + """This works exactly like the method of the same name on a regular + :class:`click.Group` but it defaults the group class to + :class:`AppGroup`. + """ + kwargs.setdefault("cls", AppGroup) + return click.Group.group(self, *args, **kwargs) + + +def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str | None: + if value is None: + return None + + info = ctx.ensure_object(ScriptInfo) + info.app_import_path = value + return value + + +# This option is eager so the app will be available if --help is given. +# --help is also eager, so --app must be before it in the param list. +# no_args_is_help bypasses eager processing, so this option must be +# processed manually in that case to ensure FLASK_APP gets picked up. +_app_option = click.Option( + ["-A", "--app"], + metavar="IMPORT", + help=( + "The Flask application or factory function to load, in the form 'module:name'." + " Module can be a dotted import or file path. Name is not required if it is" + " 'app', 'application', 'create_app', or 'make_app', and can be 'name(args)' to" + " pass arguments." + ), + is_eager=True, + expose_value=False, + callback=_set_app, +) + + +def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None: + # If the flag isn't provided, it will default to False. Don't use + # that, let debug be set by env in that case. + source = ctx.get_parameter_source(param.name) # type: ignore[arg-type] + + if source is not None and source in ( + ParameterSource.DEFAULT, + ParameterSource.DEFAULT_MAP, + ): + return None + + # Set with env var instead of ScriptInfo.load so that it can be + # accessed early during a factory function. + os.environ["FLASK_DEBUG"] = "1" if value else "0" + return value + + +_debug_option = click.Option( + ["--debug/--no-debug"], + help="Set debug mode.", + expose_value=False, + callback=_set_debug, +) + + +def _env_file_callback( + ctx: click.Context, param: click.Option, value: str | None +) -> str | None: + if value is None: + return None + + import importlib + + try: + importlib.import_module("dotenv") + except ImportError: + raise click.BadParameter( + "python-dotenv must be installed to load an env file.", + ctx=ctx, + param=param, + ) from None + + # Don't check FLASK_SKIP_DOTENV, that only disables automatically + # loading .env and .flaskenv files. + load_dotenv(value) + return value + + +# This option is eager so env vars are loaded as early as possible to be +# used by other options. +_env_file_option = click.Option( + ["-e", "--env-file"], + type=click.Path(exists=True, dir_okay=False), + help="Load environment variables from this file. python-dotenv must be installed.", + is_eager=True, + expose_value=False, + callback=_env_file_callback, +) + + +class FlaskGroup(AppGroup): + """Special subclass of the :class:`AppGroup` group that supports + loading more commands from the configured Flask app. Normally a + developer does not have to interface with this class but there are + some very advanced use cases for which it makes sense to create an + instance of this. see :ref:`custom-scripts`. + + :param add_default_commands: if this is True then the default run and + shell commands will be added. + :param add_version_option: adds the ``--version`` option. + :param create_app: an optional callback that is passed the script info and + returns the loaded app. + :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv` + files to set environment variables. Will also change the working + directory to the directory containing the first file found. + :param set_debug_flag: Set the app's debug flag. + + .. versionchanged:: 2.2 + Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options. + + .. versionchanged:: 2.2 + An app context is pushed when running ``app.cli`` commands, so + ``@with_appcontext`` is no longer required for those commands. + + .. versionchanged:: 1.0 + If installed, python-dotenv will be used to load environment variables + from :file:`.env` and :file:`.flaskenv` files. + """ + + def __init__( + self, + add_default_commands: bool = True, + create_app: t.Callable[..., Flask] | None = None, + add_version_option: bool = True, + load_dotenv: bool = True, + set_debug_flag: bool = True, + **extra: t.Any, + ) -> None: + params = list(extra.pop("params", None) or ()) + # Processing is done with option callbacks instead of a group + # callback. This allows users to make a custom group callback + # without losing the behavior. --env-file must come first so + # that it is eagerly evaluated before --app. + params.extend((_env_file_option, _app_option, _debug_option)) + + if add_version_option: + params.append(version_option) + + if "context_settings" not in extra: + extra["context_settings"] = {} + + extra["context_settings"].setdefault("auto_envvar_prefix", "FLASK") + + super().__init__(params=params, **extra) + + self.create_app = create_app + self.load_dotenv = load_dotenv + self.set_debug_flag = set_debug_flag + + if add_default_commands: + self.add_command(run_command) + self.add_command(shell_command) + self.add_command(routes_command) + + self._loaded_plugin_commands = False + + def _load_plugin_commands(self): + if self._loaded_plugin_commands: + return + + if sys.version_info >= (3, 10): + from importlib import metadata + else: + # Use a backport on Python < 3.10. We technically have + # importlib.metadata on 3.8+, but the API changed in 3.10, + # so use the backport for consistency. + import importlib_metadata as metadata + + for ep in metadata.entry_points(group="flask.commands"): + self.add_command(ep.load(), ep.name) + + self._loaded_plugin_commands = True + + def get_command(self, ctx, name): + self._load_plugin_commands() + # Look up built-in and plugin commands, which should be + # available even if the app fails to load. + rv = super().get_command(ctx, name) + + if rv is not None: + return rv + + info = ctx.ensure_object(ScriptInfo) + + # Look up commands provided by the app, showing an error and + # continuing if the app couldn't be loaded. + try: + app = info.load_app() + except NoAppException as e: + click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") + return None + + # Push an app context for the loaded app unless it is already + # active somehow. This makes the context available to parameter + # and command callbacks without needing @with_appcontext. + if not current_app or current_app._get_current_object() is not app: + ctx.with_resource(app.app_context()) + + return app.cli.get_command(ctx, name) + + def list_commands(self, ctx): + self._load_plugin_commands() + # Start with the built-in and plugin commands. + rv = set(super().list_commands(ctx)) + info = ctx.ensure_object(ScriptInfo) + + # Add commands provided by the app, showing an error and + # continuing if the app couldn't be loaded. + try: + rv.update(info.load_app().cli.list_commands(ctx)) + except NoAppException as e: + # When an app couldn't be loaded, show the error message + # without the traceback. + click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") + except Exception: + # When any other errors occurred during loading, show the + # full traceback. + click.secho(f"{traceback.format_exc()}\n", err=True, fg="red") + + return sorted(rv) + + def make_context( + self, + info_name: str | None, + args: list[str], + parent: click.Context | None = None, + **extra: t.Any, + ) -> click.Context: + # Set a flag to tell app.run to become a no-op. If app.run was + # not in a __name__ == __main__ guard, it would start the server + # when importing, blocking whatever command is being called. + os.environ["FLASK_RUN_FROM_CLI"] = "true" + + # Attempt to load .env and .flask env files. The --env-file + # option can cause another file to be loaded. + if get_load_dotenv(self.load_dotenv): + load_dotenv() + + if "obj" not in extra and "obj" not in self.context_settings: + extra["obj"] = ScriptInfo( + create_app=self.create_app, set_debug_flag=self.set_debug_flag + ) + + return super().make_context(info_name, args, parent=parent, **extra) + + def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]: + if not args and self.no_args_is_help: + # Attempt to load --env-file and --app early in case they + # were given as env vars. Otherwise no_args_is_help will not + # see commands from app.cli. + _env_file_option.handle_parse_result(ctx, {}, []) + _app_option.handle_parse_result(ctx, {}, []) + + return super().parse_args(ctx, args) + + +def _path_is_ancestor(path, other): + """Take ``other`` and remove the length of ``path`` from it. Then join it + to ``path``. If it is the original value, ``path`` is an ancestor of + ``other``.""" + return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other + + +def load_dotenv(path: str | os.PathLike | None = None) -> bool: + """Load "dotenv" files in order of precedence to set environment variables. + + If an env var is already set it is not overwritten, so earlier files in the + list are preferred over later files. + + This is a no-op if `python-dotenv`_ is not installed. + + .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme + + :param path: Load the file at this location instead of searching. + :return: ``True`` if a file was loaded. + + .. versionchanged:: 2.0 + The current directory is not changed to the location of the + loaded file. + + .. versionchanged:: 2.0 + When loading the env files, set the default encoding to UTF-8. + + .. versionchanged:: 1.1.0 + Returns ``False`` when python-dotenv is not installed, or when + the given path isn't a file. + + .. versionadded:: 1.0 + """ + try: + import dotenv + except ImportError: + if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"): + click.secho( + " * Tip: There are .env or .flaskenv files present." + ' Do "pip install python-dotenv" to use them.', + fg="yellow", + err=True, + ) + + return False + + # Always return after attempting to load a given path, don't load + # the default files. + if path is not None: + if os.path.isfile(path): + return dotenv.load_dotenv(path, encoding="utf-8") + + return False + + loaded = False + + for name in (".env", ".flaskenv"): + path = dotenv.find_dotenv(name, usecwd=True) + + if not path: + continue + + dotenv.load_dotenv(path, encoding="utf-8") + loaded = True + + return loaded # True if at least one file was located and loaded. + + +def show_server_banner(debug, app_import_path): + """Show extra startup messages the first time the server is run, + ignoring the reloader. + """ + if is_running_from_reloader(): + return + + if app_import_path is not None: + click.echo(f" * Serving Flask app '{app_import_path}'") + + if debug is not None: + click.echo(f" * Debug mode: {'on' if debug else 'off'}") + + +class CertParamType(click.ParamType): + """Click option type for the ``--cert`` option. Allows either an + existing file, the string ``'adhoc'``, or an import for a + :class:`~ssl.SSLContext` object. + """ + + name = "path" + + def __init__(self): + self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True) + + def convert(self, value, param, ctx): + try: + import ssl + except ImportError: + raise click.BadParameter( + 'Using "--cert" requires Python to be compiled with SSL support.', + ctx, + param, + ) from None + + try: + return self.path_type(value, param, ctx) + except click.BadParameter: + value = click.STRING(value, param, ctx).lower() + + if value == "adhoc": + try: + import cryptography # noqa: F401 + except ImportError: + raise click.BadParameter( + "Using ad-hoc certificates requires the cryptography library.", + ctx, + param, + ) from None + + return value + + obj = import_string(value, silent=True) + + if isinstance(obj, ssl.SSLContext): + return obj + + raise + + +def _validate_key(ctx, param, value): + """The ``--key`` option must be specified when ``--cert`` is a file. + Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed. + """ + cert = ctx.params.get("cert") + is_adhoc = cert == "adhoc" + + try: + import ssl + except ImportError: + is_context = False + else: + is_context = isinstance(cert, ssl.SSLContext) + + if value is not None: + if is_adhoc: + raise click.BadParameter( + 'When "--cert" is "adhoc", "--key" is not used.', ctx, param + ) + + if is_context: + raise click.BadParameter( + 'When "--cert" is an SSLContext object, "--key is not used.', ctx, param + ) + + if not cert: + raise click.BadParameter('"--cert" must also be specified.', ctx, param) + + ctx.params["cert"] = cert, value + + else: + if cert and not (is_adhoc or is_context): + raise click.BadParameter('Required when using "--cert".', ctx, param) + + return value + + +class SeparatedPathType(click.Path): + """Click option type that accepts a list of values separated by the + OS's path separator (``:``, ``;`` on Windows). Each value is + validated as a :class:`click.Path` type. + """ + + def convert(self, value, param, ctx): + items = self.split_envvar_value(value) + super_convert = super().convert + return [super_convert(item, param, ctx) for item in items] + + +@click.command("run", short_help="Run a development server.") +@click.option("--host", "-h", default="127.0.0.1", help="The interface to bind to.") +@click.option("--port", "-p", default=5000, help="The port to bind to.") +@click.option( + "--cert", + type=CertParamType(), + help="Specify a certificate file to use HTTPS.", + is_eager=True, +) +@click.option( + "--key", + type=click.Path(exists=True, dir_okay=False, resolve_path=True), + callback=_validate_key, + expose_value=False, + help="The key file to use when specifying a certificate.", +) +@click.option( + "--reload/--no-reload", + default=None, + help="Enable or disable the reloader. By default the reloader " + "is active if debug is enabled.", +) +@click.option( + "--debugger/--no-debugger", + default=None, + help="Enable or disable the debugger. By default the debugger " + "is active if debug is enabled.", +) +@click.option( + "--with-threads/--without-threads", + default=True, + help="Enable or disable multithreading.", +) +@click.option( + "--extra-files", + default=None, + type=SeparatedPathType(), + help=( + "Extra files that trigger a reload on change. Multiple paths" + f" are separated by {os.path.pathsep!r}." + ), +) +@click.option( + "--exclude-patterns", + default=None, + type=SeparatedPathType(), + help=( + "Files matching these fnmatch patterns will not trigger a reload" + " on change. Multiple patterns are separated by" + f" {os.path.pathsep!r}." + ), +) +@pass_script_info +def run_command( + info, + host, + port, + reload, + debugger, + with_threads, + cert, + extra_files, + exclude_patterns, +): + """Run a local development server. + + This server is for development purposes only. It does not provide + the stability, security, or performance of production WSGI servers. + + The reloader and debugger are enabled by default with the '--debug' + option. + """ + try: + app = info.load_app() + except Exception as e: + if is_running_from_reloader(): + # When reloading, print out the error immediately, but raise + # it later so the debugger or server can handle it. + traceback.print_exc() + err = e + + def app(environ, start_response): + raise err from None + + else: + # When not reloading, raise the error immediately so the + # command fails. + raise e from None + + debug = get_debug_flag() + + if reload is None: + reload = debug + + if debugger is None: + debugger = debug + + show_server_banner(debug, info.app_import_path) + + run_simple( + host, + port, + app, + use_reloader=reload, + use_debugger=debugger, + threaded=with_threads, + ssl_context=cert, + extra_files=extra_files, + exclude_patterns=exclude_patterns, + ) + + +run_command.params.insert(0, _debug_option) + + +@click.command("shell", short_help="Run a shell in the app context.") +@with_appcontext +def shell_command() -> None: + """Run an interactive Python shell in the context of a given + Flask application. The application will populate the default + namespace of this shell according to its configuration. + + This is useful for executing small snippets of management code + without having to manually configure the application. + """ + import code + + banner = ( + f"Python {sys.version} on {sys.platform}\n" + f"App: {current_app.import_name}\n" + f"Instance: {current_app.instance_path}" + ) + ctx: dict = {} + + # Support the regular Python interpreter startup script if someone + # is using it. + startup = os.environ.get("PYTHONSTARTUP") + if startup and os.path.isfile(startup): + with open(startup) as f: + eval(compile(f.read(), startup, "exec"), ctx) + + ctx.update(current_app.make_shell_context()) + + # Site, customize, or startup script can set a hook to call when + # entering interactive mode. The default one sets up readline with + # tab and history completion. + interactive_hook = getattr(sys, "__interactivehook__", None) + + if interactive_hook is not None: + try: + import readline + from rlcompleter import Completer + except ImportError: + pass + else: + # rlcompleter uses __main__.__dict__ by default, which is + # flask.__main__. Use the shell context instead. + readline.set_completer(Completer(ctx).complete) + + interactive_hook() + + code.interact(banner=banner, local=ctx) + + +@click.command("routes", short_help="Show the routes for the app.") +@click.option( + "--sort", + "-s", + type=click.Choice(("endpoint", "methods", "domain", "rule", "match")), + default="endpoint", + help=( + "Method to sort routes by. 'match' is the order that Flask will match routes" + " when dispatching a request." + ), +) +@click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.") +@with_appcontext +def routes_command(sort: str, all_methods: bool) -> None: + """Show all registered routes with endpoints and methods.""" + rules = list(current_app.url_map.iter_rules()) + + if not rules: + click.echo("No routes were registered.") + return + + ignored_methods = set() if all_methods else {"HEAD", "OPTIONS"} + host_matching = current_app.url_map.host_matching + has_domain = any(rule.host if host_matching else rule.subdomain for rule in rules) + rows = [] + + for rule in rules: + row = [ + rule.endpoint, + ", ".join(sorted((rule.methods or set()) - ignored_methods)), + ] + + if has_domain: + row.append((rule.host if host_matching else rule.subdomain) or "") + + row.append(rule.rule) + rows.append(row) + + headers = ["Endpoint", "Methods"] + sorts = ["endpoint", "methods"] + + if has_domain: + headers.append("Host" if host_matching else "Subdomain") + sorts.append("domain") + + headers.append("Rule") + sorts.append("rule") + + try: + rows.sort(key=itemgetter(sorts.index(sort))) + except ValueError: + pass + + rows.insert(0, headers) + widths = [max(len(row[i]) for row in rows) for i in range(len(headers))] + rows.insert(1, ["-" * w for w in widths]) + template = " ".join(f"{{{i}:<{w}}}" for i, w in enumerate(widths)) + + for row in rows: + click.echo(template.format(*row)) + + +cli = FlaskGroup( + name="flask", + help="""\ +A general utility script for Flask applications. + +An application to load must be given with the '--app' option, +'FLASK_APP' environment variable, or with a 'wsgi.py' or 'app.py' file +in the current directory. +""", +) + + +def main() -> None: + cli.main() + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.12/site-packages/flask/config.py b/venv/lib/python3.12/site-packages/flask/config.py new file mode 100644 index 0000000..5f921b4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/config.py @@ -0,0 +1,347 @@ +from __future__ import annotations + +import errno +import json +import os +import types +import typing as t + +from werkzeug.utils import import_string + + +class ConfigAttribute: + """Makes an attribute forward to the config""" + + def __init__(self, name: str, get_converter: t.Callable | None = None) -> None: + self.__name__ = name + self.get_converter = get_converter + + def __get__(self, obj: t.Any, owner: t.Any = None) -> t.Any: + if obj is None: + return self + rv = obj.config[self.__name__] + if self.get_converter is not None: + rv = self.get_converter(rv) + return rv + + def __set__(self, obj: t.Any, value: t.Any) -> None: + obj.config[self.__name__] = value + + +class Config(dict): + """Works exactly like a dict but provides ways to fill it from files + or special dictionaries. There are two common patterns to populate the + config. + + Either you can fill the config from a config file:: + + app.config.from_pyfile('yourconfig.cfg') + + Or alternatively you can define the configuration options in the + module that calls :meth:`from_object` or provide an import path to + a module that should be loaded. It is also possible to tell it to + use the same module and with that provide the configuration values + just before the call:: + + DEBUG = True + SECRET_KEY = 'development key' + app.config.from_object(__name__) + + In both cases (loading from any Python file or loading from modules), + only uppercase keys are added to the config. This makes it possible to use + lowercase values in the config file for temporary values that are not added + to the config or to define the config keys in the same file that implements + the application. + + Probably the most interesting way to load configurations is from an + environment variable pointing to a file:: + + app.config.from_envvar('YOURAPPLICATION_SETTINGS') + + In this case before launching the application you have to set this + environment variable to the file you want to use. On Linux and OS X + use the export statement:: + + export YOURAPPLICATION_SETTINGS='/path/to/config/file' + + On windows use `set` instead. + + :param root_path: path to which files are read relative from. When the + config object is created by the application, this is + the application's :attr:`~flask.Flask.root_path`. + :param defaults: an optional dictionary of default values + """ + + def __init__( + self, root_path: str | os.PathLike, defaults: dict | None = None + ) -> None: + super().__init__(defaults or {}) + self.root_path = root_path + + def from_envvar(self, variable_name: str, silent: bool = False) -> bool: + """Loads a configuration from an environment variable pointing to + a configuration file. This is basically just a shortcut with nicer + error messages for this line of code:: + + app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS']) + + :param variable_name: name of the environment variable + :param silent: set to ``True`` if you want silent failure for missing + files. + :return: ``True`` if the file was loaded successfully. + """ + rv = os.environ.get(variable_name) + if not rv: + if silent: + return False + raise RuntimeError( + f"The environment variable {variable_name!r} is not set" + " and as such configuration could not be loaded. Set" + " this variable and make it point to a configuration" + " file" + ) + return self.from_pyfile(rv, silent=silent) + + def from_prefixed_env( + self, prefix: str = "FLASK", *, loads: t.Callable[[str], t.Any] = json.loads + ) -> bool: + """Load any environment variables that start with ``FLASK_``, + dropping the prefix from the env key for the config key. Values + are passed through a loading function to attempt to convert them + to more specific types than strings. + + Keys are loaded in :func:`sorted` order. + + The default loading function attempts to parse values as any + valid JSON type, including dicts and lists. + + Specific items in nested dicts can be set by separating the + keys with double underscores (``__``). If an intermediate key + doesn't exist, it will be initialized to an empty dict. + + :param prefix: Load env vars that start with this prefix, + separated with an underscore (``_``). + :param loads: Pass each string value to this function and use + the returned value as the config value. If any error is + raised it is ignored and the value remains a string. The + default is :func:`json.loads`. + + .. versionadded:: 2.1 + """ + prefix = f"{prefix}_" + len_prefix = len(prefix) + + for key in sorted(os.environ): + if not key.startswith(prefix): + continue + + value = os.environ[key] + + try: + value = loads(value) + except Exception: + # Keep the value as a string if loading failed. + pass + + # Change to key.removeprefix(prefix) on Python >= 3.9. + key = key[len_prefix:] + + if "__" not in key: + # A non-nested key, set directly. + self[key] = value + continue + + # Traverse nested dictionaries with keys separated by "__". + current = self + *parts, tail = key.split("__") + + for part in parts: + # If an intermediate dict does not exist, create it. + if part not in current: + current[part] = {} + + current = current[part] + + current[tail] = value + + return True + + def from_pyfile(self, filename: str | os.PathLike, silent: bool = False) -> bool: + """Updates the values in the config from a Python file. This function + behaves as if the file was imported as module with the + :meth:`from_object` function. + + :param filename: the filename of the config. This can either be an + absolute filename or a filename relative to the + root path. + :param silent: set to ``True`` if you want silent failure for missing + files. + :return: ``True`` if the file was loaded successfully. + + .. versionadded:: 0.7 + `silent` parameter. + """ + filename = os.path.join(self.root_path, filename) + d = types.ModuleType("config") + d.__file__ = filename + try: + with open(filename, mode="rb") as config_file: + exec(compile(config_file.read(), filename, "exec"), d.__dict__) + except OSError as e: + if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR): + return False + e.strerror = f"Unable to load configuration file ({e.strerror})" + raise + self.from_object(d) + return True + + def from_object(self, obj: object | str) -> None: + """Updates the values from the given object. An object can be of one + of the following two types: + + - a string: in this case the object with that name will be imported + - an actual object reference: that object is used directly + + Objects are usually either modules or classes. :meth:`from_object` + loads only the uppercase attributes of the module/class. A ``dict`` + object will not work with :meth:`from_object` because the keys of a + ``dict`` are not attributes of the ``dict`` class. + + Example of module-based configuration:: + + app.config.from_object('yourapplication.default_config') + from yourapplication import default_config + app.config.from_object(default_config) + + Nothing is done to the object before loading. If the object is a + class and has ``@property`` attributes, it needs to be + instantiated before being passed to this method. + + You should not use this function to load the actual configuration but + rather configuration defaults. The actual config should be loaded + with :meth:`from_pyfile` and ideally from a location not within the + package because the package might be installed system wide. + + See :ref:`config-dev-prod` for an example of class-based configuration + using :meth:`from_object`. + + :param obj: an import name or object + """ + if isinstance(obj, str): + obj = import_string(obj) + for key in dir(obj): + if key.isupper(): + self[key] = getattr(obj, key) + + def from_file( + self, + filename: str | os.PathLike, + load: t.Callable[[t.IO[t.Any]], t.Mapping], + silent: bool = False, + text: bool = True, + ) -> bool: + """Update the values in the config from a file that is loaded + using the ``load`` parameter. The loaded data is passed to the + :meth:`from_mapping` method. + + .. code-block:: python + + import json + app.config.from_file("config.json", load=json.load) + + import tomllib + app.config.from_file("config.toml", load=tomllib.load, text=False) + + :param filename: The path to the data file. This can be an + absolute path or relative to the config root path. + :param load: A callable that takes a file handle and returns a + mapping of loaded data from the file. + :type load: ``Callable[[Reader], Mapping]`` where ``Reader`` + implements a ``read`` method. + :param silent: Ignore the file if it doesn't exist. + :param text: Open the file in text or binary mode. + :return: ``True`` if the file was loaded successfully. + + .. versionchanged:: 2.3 + The ``text`` parameter was added. + + .. versionadded:: 2.0 + """ + filename = os.path.join(self.root_path, filename) + + try: + with open(filename, "r" if text else "rb") as f: + obj = load(f) + except OSError as e: + if silent and e.errno in (errno.ENOENT, errno.EISDIR): + return False + + e.strerror = f"Unable to load configuration file ({e.strerror})" + raise + + return self.from_mapping(obj) + + def from_mapping( + self, mapping: t.Mapping[str, t.Any] | None = None, **kwargs: t.Any + ) -> bool: + """Updates the config like :meth:`update` ignoring items with + non-upper keys. + + :return: Always returns ``True``. + + .. versionadded:: 0.11 + """ + mappings: dict[str, t.Any] = {} + if mapping is not None: + mappings.update(mapping) + mappings.update(kwargs) + for key, value in mappings.items(): + if key.isupper(): + self[key] = value + return True + + def get_namespace( + self, namespace: str, lowercase: bool = True, trim_namespace: bool = True + ) -> dict[str, t.Any]: + """Returns a dictionary containing a subset of configuration options + that match the specified namespace/prefix. Example usage:: + + app.config['IMAGE_STORE_TYPE'] = 'fs' + app.config['IMAGE_STORE_PATH'] = '/var/app/images' + app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com' + image_store_config = app.config.get_namespace('IMAGE_STORE_') + + The resulting dictionary `image_store_config` would look like:: + + { + 'type': 'fs', + 'path': '/var/app/images', + 'base_url': 'http://img.website.com' + } + + This is often useful when configuration options map directly to + keyword arguments in functions or class constructors. + + :param namespace: a configuration namespace + :param lowercase: a flag indicating if the keys of the resulting + dictionary should be lowercase + :param trim_namespace: a flag indicating if the keys of the resulting + dictionary should not include the namespace + + .. versionadded:: 0.11 + """ + rv = {} + for k, v in self.items(): + if not k.startswith(namespace): + continue + if trim_namespace: + key = k[len(namespace) :] + else: + key = k + if lowercase: + key = key.lower() + rv[key] = v + return rv + + def __repr__(self) -> str: + return f"<{type(self).__name__} {dict.__repr__(self)}>" diff --git a/venv/lib/python3.12/site-packages/flask/ctx.py b/venv/lib/python3.12/site-packages/flask/ctx.py new file mode 100644 index 0000000..b37e4e0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/ctx.py @@ -0,0 +1,440 @@ +from __future__ import annotations + +import contextvars +import sys +import typing as t +from functools import update_wrapper +from types import TracebackType + +from werkzeug.exceptions import HTTPException + +from . import typing as ft +from .globals import _cv_app +from .globals import _cv_request +from .signals import appcontext_popped +from .signals import appcontext_pushed + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + from .sessions import SessionMixin + from .wrappers import Request + + +# a singleton sentinel value for parameter defaults +_sentinel = object() + + +class _AppCtxGlobals: + """A plain object. Used as a namespace for storing data during an + application context. + + Creating an app context automatically creates this object, which is + made available as the :data:`g` proxy. + + .. describe:: 'key' in g + + Check whether an attribute is present. + + .. versionadded:: 0.10 + + .. describe:: iter(g) + + Return an iterator over the attribute names. + + .. versionadded:: 0.10 + """ + + # Define attr methods to let mypy know this is a namespace object + # that has arbitrary attributes. + + def __getattr__(self, name: str) -> t.Any: + try: + return self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + + def __setattr__(self, name: str, value: t.Any) -> None: + self.__dict__[name] = value + + def __delattr__(self, name: str) -> None: + try: + del self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + + def get(self, name: str, default: t.Any | None = None) -> t.Any: + """Get an attribute by name, or a default value. Like + :meth:`dict.get`. + + :param name: Name of attribute to get. + :param default: Value to return if the attribute is not present. + + .. versionadded:: 0.10 + """ + return self.__dict__.get(name, default) + + def pop(self, name: str, default: t.Any = _sentinel) -> t.Any: + """Get and remove an attribute by name. Like :meth:`dict.pop`. + + :param name: Name of attribute to pop. + :param default: Value to return if the attribute is not present, + instead of raising a ``KeyError``. + + .. versionadded:: 0.11 + """ + if default is _sentinel: + return self.__dict__.pop(name) + else: + return self.__dict__.pop(name, default) + + def setdefault(self, name: str, default: t.Any = None) -> t.Any: + """Get the value of an attribute if it is present, otherwise + set and return a default value. Like :meth:`dict.setdefault`. + + :param name: Name of attribute to get. + :param default: Value to set and return if the attribute is not + present. + + .. versionadded:: 0.11 + """ + return self.__dict__.setdefault(name, default) + + def __contains__(self, item: str) -> bool: + return item in self.__dict__ + + def __iter__(self) -> t.Iterator[str]: + return iter(self.__dict__) + + def __repr__(self) -> str: + ctx = _cv_app.get(None) + if ctx is not None: + return f"" + return object.__repr__(self) + + +def after_this_request(f: ft.AfterRequestCallable) -> ft.AfterRequestCallable: + """Executes a function after this request. This is useful to modify + response objects. The function is passed the response object and has + to return the same or a new one. + + Example:: + + @app.route('/') + def index(): + @after_this_request + def add_header(response): + response.headers['X-Foo'] = 'Parachute' + return response + return 'Hello World!' + + This is more useful if a function other than the view function wants to + modify a response. For instance think of a decorator that wants to add + some headers without converting the return value into a response object. + + .. versionadded:: 0.9 + """ + ctx = _cv_request.get(None) + + if ctx is None: + raise RuntimeError( + "'after_this_request' can only be used when a request" + " context is active, such as in a view function." + ) + + ctx._after_request_functions.append(f) + return f + + +def copy_current_request_context(f: t.Callable) -> t.Callable: + """A helper function that decorates a function to retain the current + request context. This is useful when working with greenlets. The moment + the function is decorated a copy of the request context is created and + then pushed when the function is called. The current session is also + included in the copied request context. + + Example:: + + import gevent + from flask import copy_current_request_context + + @app.route('/') + def index(): + @copy_current_request_context + def do_some_work(): + # do some work here, it can access flask.request or + # flask.session like you would otherwise in the view function. + ... + gevent.spawn(do_some_work) + return 'Regular response' + + .. versionadded:: 0.10 + """ + ctx = _cv_request.get(None) + + if ctx is None: + raise RuntimeError( + "'copy_current_request_context' can only be used when a" + " request context is active, such as in a view function." + ) + + ctx = ctx.copy() + + def wrapper(*args, **kwargs): + with ctx: + return ctx.app.ensure_sync(f)(*args, **kwargs) + + return update_wrapper(wrapper, f) + + +def has_request_context() -> bool: + """If you have code that wants to test if a request context is there or + not this function can be used. For instance, you may want to take advantage + of request information if the request object is available, but fail + silently if it is unavailable. + + :: + + class User(db.Model): + + def __init__(self, username, remote_addr=None): + self.username = username + if remote_addr is None and has_request_context(): + remote_addr = request.remote_addr + self.remote_addr = remote_addr + + Alternatively you can also just test any of the context bound objects + (such as :class:`request` or :class:`g`) for truthness:: + + class User(db.Model): + + def __init__(self, username, remote_addr=None): + self.username = username + if remote_addr is None and request: + remote_addr = request.remote_addr + self.remote_addr = remote_addr + + .. versionadded:: 0.7 + """ + return _cv_request.get(None) is not None + + +def has_app_context() -> bool: + """Works like :func:`has_request_context` but for the application + context. You can also just do a boolean check on the + :data:`current_app` object instead. + + .. versionadded:: 0.9 + """ + return _cv_app.get(None) is not None + + +class AppContext: + """The app context contains application-specific information. An app + context is created and pushed at the beginning of each request if + one is not already active. An app context is also pushed when + running CLI commands. + """ + + def __init__(self, app: Flask) -> None: + self.app = app + self.url_adapter = app.create_url_adapter(None) + self.g: _AppCtxGlobals = app.app_ctx_globals_class() + self._cv_tokens: list[contextvars.Token] = [] + + def push(self) -> None: + """Binds the app context to the current context.""" + self._cv_tokens.append(_cv_app.set(self)) + appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync) + + def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore + """Pops the app context.""" + try: + if len(self._cv_tokens) == 1: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_appcontext(exc) + finally: + ctx = _cv_app.get() + _cv_app.reset(self._cv_tokens.pop()) + + if ctx is not self: + raise AssertionError( + f"Popped wrong app context. ({ctx!r} instead of {self!r})" + ) + + appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync) + + def __enter__(self) -> AppContext: + self.push() + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.pop(exc_value) + + +class RequestContext: + """The request context contains per-request information. The Flask + app creates and pushes it at the beginning of the request, then pops + it at the end of the request. It will create the URL adapter and + request object for the WSGI environment provided. + + Do not attempt to use this class directly, instead use + :meth:`~flask.Flask.test_request_context` and + :meth:`~flask.Flask.request_context` to create this object. + + When the request context is popped, it will evaluate all the + functions registered on the application for teardown execution + (:meth:`~flask.Flask.teardown_request`). + + The request context is automatically popped at the end of the + request. When using the interactive debugger, the context will be + restored so ``request`` is still accessible. Similarly, the test + client can preserve the context after the request ends. However, + teardown functions may already have closed some resources such as + database connections. + """ + + def __init__( + self, + app: Flask, + environ: dict, + request: Request | None = None, + session: SessionMixin | None = None, + ) -> None: + self.app = app + if request is None: + request = app.request_class(environ) + request.json_module = app.json + self.request: Request = request + self.url_adapter = None + try: + self.url_adapter = app.create_url_adapter(self.request) + except HTTPException as e: + self.request.routing_exception = e + self.flashes: list[tuple[str, str]] | None = None + self.session: SessionMixin | None = session + # Functions that should be executed after the request on the response + # object. These will be called before the regular "after_request" + # functions. + self._after_request_functions: list[ft.AfterRequestCallable] = [] + + self._cv_tokens: list[tuple[contextvars.Token, AppContext | None]] = [] + + def copy(self) -> RequestContext: + """Creates a copy of this request context with the same request object. + This can be used to move a request context to a different greenlet. + Because the actual request object is the same this cannot be used to + move a request context to a different thread unless access to the + request object is locked. + + .. versionadded:: 0.10 + + .. versionchanged:: 1.1 + The current session object is used instead of reloading the original + data. This prevents `flask.session` pointing to an out-of-date object. + """ + return self.__class__( + self.app, + environ=self.request.environ, + request=self.request, + session=self.session, + ) + + def match_request(self) -> None: + """Can be overridden by a subclass to hook into the matching + of the request. + """ + try: + result = self.url_adapter.match(return_rule=True) # type: ignore + self.request.url_rule, self.request.view_args = result # type: ignore + except HTTPException as e: + self.request.routing_exception = e + + def push(self) -> None: + # Before we push the request context we have to ensure that there + # is an application context. + app_ctx = _cv_app.get(None) + + if app_ctx is None or app_ctx.app is not self.app: + app_ctx = self.app.app_context() + app_ctx.push() + else: + app_ctx = None + + self._cv_tokens.append((_cv_request.set(self), app_ctx)) + + # Open the session at the moment that the request context is available. + # This allows a custom open_session method to use the request context. + # Only open a new session if this is the first time the request was + # pushed, otherwise stream_with_context loses the session. + if self.session is None: + session_interface = self.app.session_interface + self.session = session_interface.open_session(self.app, self.request) + + if self.session is None: + self.session = session_interface.make_null_session(self.app) + + # Match the request URL after loading the session, so that the + # session is available in custom URL converters. + if self.url_adapter is not None: + self.match_request() + + def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore + """Pops the request context and unbinds it by doing that. This will + also trigger the execution of functions registered by the + :meth:`~flask.Flask.teardown_request` decorator. + + .. versionchanged:: 0.9 + Added the `exc` argument. + """ + clear_request = len(self._cv_tokens) == 1 + + try: + if clear_request: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_request(exc) + + request_close = getattr(self.request, "close", None) + if request_close is not None: + request_close() + finally: + ctx = _cv_request.get() + token, app_ctx = self._cv_tokens.pop() + _cv_request.reset(token) + + # get rid of circular dependencies at the end of the request + # so that we don't require the GC to be active. + if clear_request: + ctx.request.environ["werkzeug.request"] = None + + if app_ctx is not None: + app_ctx.pop(exc) + + if ctx is not self: + raise AssertionError( + f"Popped wrong request context. ({ctx!r} instead of {self!r})" + ) + + def __enter__(self) -> RequestContext: + self.push() + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.pop(exc_value) + + def __repr__(self) -> str: + return ( + f"<{type(self).__name__} {self.request.url!r}" + f" [{self.request.method}] of {self.app.name}>" + ) diff --git a/venv/lib/python3.12/site-packages/flask/debughelpers.py b/venv/lib/python3.12/site-packages/flask/debughelpers.py new file mode 100644 index 0000000..6061441 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/debughelpers.py @@ -0,0 +1,160 @@ +from __future__ import annotations + +import typing as t + +from .app import Flask +from .blueprints import Blueprint +from .globals import request_ctx + + +class UnexpectedUnicodeError(AssertionError, UnicodeError): + """Raised in places where we want some better error reporting for + unexpected unicode or binary data. + """ + + +class DebugFilesKeyError(KeyError, AssertionError): + """Raised from request.files during debugging. The idea is that it can + provide a better error message than just a generic KeyError/BadRequest. + """ + + def __init__(self, request, key): + form_matches = request.form.getlist(key) + buf = [ + f"You tried to access the file {key!r} in the request.files" + " dictionary but it does not exist. The mimetype for the" + f" request is {request.mimetype!r} instead of" + " 'multipart/form-data' which means that no file contents" + " were transmitted. To fix this error you should provide" + ' enctype="multipart/form-data" in your form.' + ] + if form_matches: + names = ", ".join(repr(x) for x in form_matches) + buf.append( + "\n\nThe browser instead transmitted some file names. " + f"This was submitted: {names}" + ) + self.msg = "".join(buf) + + def __str__(self): + return self.msg + + +class FormDataRoutingRedirect(AssertionError): + """This exception is raised in debug mode if a routing redirect + would cause the browser to drop the method or body. This happens + when method is not GET, HEAD or OPTIONS and the status code is not + 307 or 308. + """ + + def __init__(self, request): + exc = request.routing_exception + buf = [ + f"A request was sent to '{request.url}', but routing issued" + f" a redirect to the canonical URL '{exc.new_url}'." + ] + + if f"{request.base_url}/" == exc.new_url.partition("?")[0]: + buf.append( + " The URL was defined with a trailing slash. Flask" + " will redirect to the URL with a trailing slash if it" + " was accessed without one." + ) + + buf.append( + " Send requests to the canonical URL, or use 307 or 308 for" + " routing redirects. Otherwise, browsers will drop form" + " data.\n\n" + "This exception is only raised in debug mode." + ) + super().__init__("".join(buf)) + + +def attach_enctype_error_multidict(request): + """Patch ``request.files.__getitem__`` to raise a descriptive error + about ``enctype=multipart/form-data``. + + :param request: The request to patch. + :meta private: + """ + oldcls = request.files.__class__ + + class newcls(oldcls): + def __getitem__(self, key): + try: + return super().__getitem__(key) + except KeyError as e: + if key not in request.form: + raise + + raise DebugFilesKeyError(request, key).with_traceback( + e.__traceback__ + ) from None + + newcls.__name__ = oldcls.__name__ + newcls.__module__ = oldcls.__module__ + request.files.__class__ = newcls + + +def _dump_loader_info(loader) -> t.Generator: + yield f"class: {type(loader).__module__}.{type(loader).__name__}" + for key, value in sorted(loader.__dict__.items()): + if key.startswith("_"): + continue + if isinstance(value, (tuple, list)): + if not all(isinstance(x, str) for x in value): + continue + yield f"{key}:" + for item in value: + yield f" - {item}" + continue + elif not isinstance(value, (str, int, float, bool)): + continue + yield f"{key}: {value!r}" + + +def explain_template_loading_attempts(app: Flask, template, attempts) -> None: + """This should help developers understand what failed""" + info = [f"Locating template {template!r}:"] + total_found = 0 + blueprint = None + if request_ctx and request_ctx.request.blueprint is not None: + blueprint = request_ctx.request.blueprint + + for idx, (loader, srcobj, triple) in enumerate(attempts): + if isinstance(srcobj, Flask): + src_info = f"application {srcobj.import_name!r}" + elif isinstance(srcobj, Blueprint): + src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})" + else: + src_info = repr(srcobj) + + info.append(f"{idx + 1:5}: trying loader of {src_info}") + + for line in _dump_loader_info(loader): + info.append(f" {line}") + + if triple is None: + detail = "no match" + else: + detail = f"found ({triple[1] or ''!r})" + total_found += 1 + info.append(f" -> {detail}") + + seems_fishy = False + if total_found == 0: + info.append("Error: the template could not be found.") + seems_fishy = True + elif total_found > 1: + info.append("Warning: multiple loaders returned a match for the template.") + seems_fishy = True + + if blueprint is not None and seems_fishy: + info.append( + " The template was looked up from an endpoint that belongs" + f" to the blueprint {blueprint!r}." + ) + info.append(" Maybe you did not place a template in the right folder?") + info.append(" See https://flask.palletsprojects.com/blueprints/#templates") + + app.logger.info("\n".join(info)) diff --git a/venv/lib/python3.12/site-packages/flask/globals.py b/venv/lib/python3.12/site-packages/flask/globals.py new file mode 100644 index 0000000..e9cd4ac --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/globals.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +import typing as t +from contextvars import ContextVar + +from werkzeug.local import LocalProxy + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + from .ctx import _AppCtxGlobals + from .ctx import AppContext + from .ctx import RequestContext + from .sessions import SessionMixin + from .wrappers import Request + + +class _FakeStack: + def __init__(self, name: str, cv: ContextVar[t.Any]) -> None: + self.name = name + self.cv = cv + + @property + def top(self) -> t.Any | None: + import warnings + + warnings.warn( + f"'_{self.name}_ctx_stack' is deprecated and will be removed in Flask 2.4." + f" Use 'g' to store data, or '{self.name}_ctx' to access the current" + " context.", + DeprecationWarning, + stacklevel=2, + ) + return self.cv.get(None) + + +_no_app_msg = """\ +Working outside of application context. + +This typically means that you attempted to use functionality that needed +the current application. To solve this, set up an application context +with app.app_context(). See the documentation for more information.\ +""" +_cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx") +__app_ctx_stack = _FakeStack("app", _cv_app) +app_ctx: AppContext = LocalProxy( # type: ignore[assignment] + _cv_app, unbound_message=_no_app_msg +) +current_app: Flask = LocalProxy( # type: ignore[assignment] + _cv_app, "app", unbound_message=_no_app_msg +) +g: _AppCtxGlobals = LocalProxy( # type: ignore[assignment] + _cv_app, "g", unbound_message=_no_app_msg +) + +_no_req_msg = """\ +Working outside of request context. + +This typically means that you attempted to use functionality that needed +an active HTTP request. Consult the documentation on testing for +information about how to avoid this problem.\ +""" +_cv_request: ContextVar[RequestContext] = ContextVar("flask.request_ctx") +__request_ctx_stack = _FakeStack("request", _cv_request) +request_ctx: RequestContext = LocalProxy( # type: ignore[assignment] + _cv_request, unbound_message=_no_req_msg +) +request: Request = LocalProxy( # type: ignore[assignment] + _cv_request, "request", unbound_message=_no_req_msg +) +session: SessionMixin = LocalProxy( # type: ignore[assignment] + _cv_request, "session", unbound_message=_no_req_msg +) + + +def __getattr__(name: str) -> t.Any: + if name == "_app_ctx_stack": + import warnings + + warnings.warn( + "'_app_ctx_stack' is deprecated and will be removed in Flask 2.4.", + DeprecationWarning, + stacklevel=2, + ) + return __app_ctx_stack + + if name == "_request_ctx_stack": + import warnings + + warnings.warn( + "'_request_ctx_stack' is deprecated and will be removed in Flask 2.4.", + DeprecationWarning, + stacklevel=2, + ) + return __request_ctx_stack + + raise AttributeError(name) diff --git a/venv/lib/python3.12/site-packages/flask/helpers.py b/venv/lib/python3.12/site-packages/flask/helpers.py new file mode 100644 index 0000000..284c369 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/helpers.py @@ -0,0 +1,701 @@ +from __future__ import annotations + +import importlib.util +import os +import socket +import sys +import typing as t +import warnings +from datetime import datetime +from functools import lru_cache +from functools import update_wrapper +from threading import RLock + +import werkzeug.utils +from werkzeug.exceptions import abort as _wz_abort +from werkzeug.utils import redirect as _wz_redirect + +from .globals import _cv_request +from .globals import current_app +from .globals import request +from .globals import request_ctx +from .globals import session +from .signals import message_flashed + +if t.TYPE_CHECKING: # pragma: no cover + from werkzeug.wrappers import Response as BaseResponse + from .wrappers import Response + + +def get_debug_flag() -> bool: + """Get whether debug mode should be enabled for the app, indicated by the + :envvar:`FLASK_DEBUG` environment variable. The default is ``False``. + """ + val = os.environ.get("FLASK_DEBUG") + return bool(val and val.lower() not in {"0", "false", "no"}) + + +def get_load_dotenv(default: bool = True) -> bool: + """Get whether the user has disabled loading default dotenv files by + setting :envvar:`FLASK_SKIP_DOTENV`. The default is ``True``, load + the files. + + :param default: What to return if the env var isn't set. + """ + val = os.environ.get("FLASK_SKIP_DOTENV") + + if not val: + return default + + return val.lower() in ("0", "false", "no") + + +def stream_with_context( + generator_or_function: ( + t.Iterator[t.AnyStr] | t.Callable[..., t.Iterator[t.AnyStr]] + ) +) -> t.Iterator[t.AnyStr]: + """Request contexts disappear when the response is started on the server. + This is done for efficiency reasons and to make it less likely to encounter + memory leaks with badly written WSGI middlewares. The downside is that if + you are using streamed responses, the generator cannot access request bound + information any more. + + This function however can help you keep the context around for longer:: + + from flask import stream_with_context, request, Response + + @app.route('/stream') + def streamed_response(): + @stream_with_context + def generate(): + yield 'Hello ' + yield request.args['name'] + yield '!' + return Response(generate()) + + Alternatively it can also be used around a specific generator:: + + from flask import stream_with_context, request, Response + + @app.route('/stream') + def streamed_response(): + def generate(): + yield 'Hello ' + yield request.args['name'] + yield '!' + return Response(stream_with_context(generate())) + + .. versionadded:: 0.9 + """ + try: + gen = iter(generator_or_function) # type: ignore + except TypeError: + + def decorator(*args: t.Any, **kwargs: t.Any) -> t.Any: + gen = generator_or_function(*args, **kwargs) # type: ignore + return stream_with_context(gen) + + return update_wrapper(decorator, generator_or_function) # type: ignore + + def generator() -> t.Generator: + ctx = _cv_request.get(None) + if ctx is None: + raise RuntimeError( + "'stream_with_context' can only be used when a request" + " context is active, such as in a view function." + ) + with ctx: + # Dummy sentinel. Has to be inside the context block or we're + # not actually keeping the context around. + yield None + + # The try/finally is here so that if someone passes a WSGI level + # iterator in we're still running the cleanup logic. Generators + # don't need that because they are closed on their destruction + # automatically. + try: + yield from gen + finally: + if hasattr(gen, "close"): + gen.close() + + # The trick is to start the generator. Then the code execution runs until + # the first dummy None is yielded at which point the context was already + # pushed. This item is discarded. Then when the iteration continues the + # real generator is executed. + wrapped_g = generator() + next(wrapped_g) + return wrapped_g + + +def make_response(*args: t.Any) -> Response: + """Sometimes it is necessary to set additional headers in a view. Because + views do not have to return response objects but can return a value that + is converted into a response object by Flask itself, it becomes tricky to + add headers to it. This function can be called instead of using a return + and you will get a response object which you can use to attach headers. + + If view looked like this and you want to add a new header:: + + def index(): + return render_template('index.html', foo=42) + + You can now do something like this:: + + def index(): + response = make_response(render_template('index.html', foo=42)) + response.headers['X-Parachutes'] = 'parachutes are cool' + return response + + This function accepts the very same arguments you can return from a + view function. This for example creates a response with a 404 error + code:: + + response = make_response(render_template('not_found.html'), 404) + + The other use case of this function is to force the return value of a + view function into a response which is helpful with view + decorators:: + + response = make_response(view_function()) + response.headers['X-Parachutes'] = 'parachutes are cool' + + Internally this function does the following things: + + - if no arguments are passed, it creates a new response argument + - if one argument is passed, :meth:`flask.Flask.make_response` + is invoked with it. + - if more than one argument is passed, the arguments are passed + to the :meth:`flask.Flask.make_response` function as tuple. + + .. versionadded:: 0.6 + """ + if not args: + return current_app.response_class() + if len(args) == 1: + args = args[0] + return current_app.make_response(args) # type: ignore + + +def url_for( + endpoint: str, + *, + _anchor: str | None = None, + _method: str | None = None, + _scheme: str | None = None, + _external: bool | None = None, + **values: t.Any, +) -> str: + """Generate a URL to the given endpoint with the given values. + + This requires an active request or application context, and calls + :meth:`current_app.url_for() `. See that method + for full documentation. + + :param endpoint: The endpoint name associated with the URL to + generate. If this starts with a ``.``, the current blueprint + name (if any) will be used. + :param _anchor: If given, append this as ``#anchor`` to the URL. + :param _method: If given, generate the URL associated with this + method for the endpoint. + :param _scheme: If given, the URL will have this scheme if it is + external. + :param _external: If given, prefer the URL to be internal (False) or + require it to be external (True). External URLs include the + scheme and domain. When not in an active request, URLs are + external by default. + :param values: Values to use for the variable parts of the URL rule. + Unknown keys are appended as query string arguments, like + ``?a=b&c=d``. + + .. versionchanged:: 2.2 + Calls ``current_app.url_for``, allowing an app to override the + behavior. + + .. versionchanged:: 0.10 + The ``_scheme`` parameter was added. + + .. versionchanged:: 0.9 + The ``_anchor`` and ``_method`` parameters were added. + + .. versionchanged:: 0.9 + Calls ``app.handle_url_build_error`` on build errors. + """ + return current_app.url_for( + endpoint, + _anchor=_anchor, + _method=_method, + _scheme=_scheme, + _external=_external, + **values, + ) + + +def redirect( + location: str, code: int = 302, Response: type[BaseResponse] | None = None +) -> BaseResponse: + """Create a redirect response object. + + If :data:`~flask.current_app` is available, it will use its + :meth:`~flask.Flask.redirect` method, otherwise it will use + :func:`werkzeug.utils.redirect`. + + :param location: The URL to redirect to. + :param code: The status code for the redirect. + :param Response: The response class to use. Not used when + ``current_app`` is active, which uses ``app.response_class``. + + .. versionadded:: 2.2 + Calls ``current_app.redirect`` if available instead of always + using Werkzeug's default ``redirect``. + """ + if current_app: + return current_app.redirect(location, code=code) + + return _wz_redirect(location, code=code, Response=Response) + + +def abort(code: int | BaseResponse, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: + """Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given + status code. + + If :data:`~flask.current_app` is available, it will call its + :attr:`~flask.Flask.aborter` object, otherwise it will use + :func:`werkzeug.exceptions.abort`. + + :param code: The status code for the exception, which must be + registered in ``app.aborter``. + :param args: Passed to the exception. + :param kwargs: Passed to the exception. + + .. versionadded:: 2.2 + Calls ``current_app.aborter`` if available instead of always + using Werkzeug's default ``abort``. + """ + if current_app: + current_app.aborter(code, *args, **kwargs) + + _wz_abort(code, *args, **kwargs) + + +def get_template_attribute(template_name: str, attribute: str) -> t.Any: + """Loads a macro (or variable) a template exports. This can be used to + invoke a macro from within Python code. If you for example have a + template named :file:`_cider.html` with the following contents: + + .. sourcecode:: html+jinja + + {% macro hello(name) %}Hello {{ name }}!{% endmacro %} + + You can access this from Python code like this:: + + hello = get_template_attribute('_cider.html', 'hello') + return hello('World') + + .. versionadded:: 0.2 + + :param template_name: the name of the template + :param attribute: the name of the variable of macro to access + """ + return getattr(current_app.jinja_env.get_template(template_name).module, attribute) + + +def flash(message: str, category: str = "message") -> None: + """Flashes a message to the next request. In order to remove the + flashed message from the session and to display it to the user, + the template has to call :func:`get_flashed_messages`. + + .. versionchanged:: 0.3 + `category` parameter added. + + :param message: the message to be flashed. + :param category: the category for the message. The following values + are recommended: ``'message'`` for any kind of message, + ``'error'`` for errors, ``'info'`` for information + messages and ``'warning'`` for warnings. However any + kind of string can be used as category. + """ + # Original implementation: + # + # session.setdefault('_flashes', []).append((category, message)) + # + # This assumed that changes made to mutable structures in the session are + # always in sync with the session object, which is not true for session + # implementations that use external storage for keeping their keys/values. + flashes = session.get("_flashes", []) + flashes.append((category, message)) + session["_flashes"] = flashes + app = current_app._get_current_object() # type: ignore + message_flashed.send( + app, + _async_wrapper=app.ensure_sync, + message=message, + category=category, + ) + + +def get_flashed_messages( + with_categories: bool = False, category_filter: t.Iterable[str] = () +) -> list[str] | list[tuple[str, str]]: + """Pulls all flashed messages from the session and returns them. + Further calls in the same request to the function will return + the same messages. By default just the messages are returned, + but when `with_categories` is set to ``True``, the return value will + be a list of tuples in the form ``(category, message)`` instead. + + Filter the flashed messages to one or more categories by providing those + categories in `category_filter`. This allows rendering categories in + separate html blocks. The `with_categories` and `category_filter` + arguments are distinct: + + * `with_categories` controls whether categories are returned with message + text (``True`` gives a tuple, where ``False`` gives just the message text). + * `category_filter` filters the messages down to only those matching the + provided categories. + + See :doc:`/patterns/flashing` for examples. + + .. versionchanged:: 0.3 + `with_categories` parameter added. + + .. versionchanged:: 0.9 + `category_filter` parameter added. + + :param with_categories: set to ``True`` to also receive categories. + :param category_filter: filter of categories to limit return values. Only + categories in the list will be returned. + """ + flashes = request_ctx.flashes + if flashes is None: + flashes = session.pop("_flashes") if "_flashes" in session else [] + request_ctx.flashes = flashes + if category_filter: + flashes = list(filter(lambda f: f[0] in category_filter, flashes)) + if not with_categories: + return [x[1] for x in flashes] + return flashes + + +def _prepare_send_file_kwargs(**kwargs: t.Any) -> dict[str, t.Any]: + if kwargs.get("max_age") is None: + kwargs["max_age"] = current_app.get_send_file_max_age + + kwargs.update( + environ=request.environ, + use_x_sendfile=current_app.config["USE_X_SENDFILE"], + response_class=current_app.response_class, + _root_path=current_app.root_path, # type: ignore + ) + return kwargs + + +def send_file( + path_or_file: os.PathLike | str | t.BinaryIO, + mimetype: str | None = None, + as_attachment: bool = False, + download_name: str | None = None, + conditional: bool = True, + etag: bool | str = True, + last_modified: datetime | int | float | None = None, + max_age: None | (int | t.Callable[[str | None], int | None]) = None, +) -> Response: + """Send the contents of a file to the client. + + The first argument can be a file path or a file-like object. Paths + are preferred in most cases because Werkzeug can manage the file and + get extra information from the path. Passing a file-like object + requires that the file is opened in binary mode, and is mostly + useful when building a file in memory with :class:`io.BytesIO`. + + Never pass file paths provided by a user. The path is assumed to be + trusted, so a user could craft a path to access a file you didn't + intend. Use :func:`send_from_directory` to safely serve + user-requested paths from within a directory. + + If the WSGI server sets a ``file_wrapper`` in ``environ``, it is + used, otherwise Werkzeug's built-in wrapper is used. Alternatively, + if the HTTP server supports ``X-Sendfile``, configuring Flask with + ``USE_X_SENDFILE = True`` will tell the server to send the given + path, which is much more efficient than reading it in Python. + + :param path_or_file: The path to the file to send, relative to the + current working directory if a relative path is given. + Alternatively, a file-like object opened in binary mode. Make + sure the file pointer is seeked to the start of the data. + :param mimetype: The MIME type to send for the file. If not + provided, it will try to detect it from the file name. + :param as_attachment: Indicate to a browser that it should offer to + save the file instead of displaying it. + :param download_name: The default name browsers will use when saving + the file. Defaults to the passed file name. + :param conditional: Enable conditional and range responses based on + request headers. Requires passing a file path and ``environ``. + :param etag: Calculate an ETag for the file, which requires passing + a file path. Can also be a string to use instead. + :param last_modified: The last modified time to send for the file, + in seconds. If not provided, it will try to detect it from the + file path. + :param max_age: How long the client should cache the file, in + seconds. If set, ``Cache-Control`` will be ``public``, otherwise + it will be ``no-cache`` to prefer conditional caching. + + .. versionchanged:: 2.0 + ``download_name`` replaces the ``attachment_filename`` + parameter. If ``as_attachment=False``, it is passed with + ``Content-Disposition: inline`` instead. + + .. versionchanged:: 2.0 + ``max_age`` replaces the ``cache_timeout`` parameter. + ``conditional`` is enabled and ``max_age`` is not set by + default. + + .. versionchanged:: 2.0 + ``etag`` replaces the ``add_etags`` parameter. It can be a + string to use instead of generating one. + + .. versionchanged:: 2.0 + Passing a file-like object that inherits from + :class:`~io.TextIOBase` will raise a :exc:`ValueError` rather + than sending an empty file. + + .. versionadded:: 2.0 + Moved the implementation to Werkzeug. This is now a wrapper to + pass some Flask-specific arguments. + + .. versionchanged:: 1.1 + ``filename`` may be a :class:`~os.PathLike` object. + + .. versionchanged:: 1.1 + Passing a :class:`~io.BytesIO` object supports range requests. + + .. versionchanged:: 1.0.3 + Filenames are encoded with ASCII instead of Latin-1 for broader + compatibility with WSGI servers. + + .. versionchanged:: 1.0 + UTF-8 filenames as specified in :rfc:`2231` are supported. + + .. versionchanged:: 0.12 + The filename is no longer automatically inferred from file + objects. If you want to use automatic MIME and etag support, + pass a filename via ``filename_or_fp`` or + ``attachment_filename``. + + .. versionchanged:: 0.12 + ``attachment_filename`` is preferred over ``filename`` for MIME + detection. + + .. versionchanged:: 0.9 + ``cache_timeout`` defaults to + :meth:`Flask.get_send_file_max_age`. + + .. versionchanged:: 0.7 + MIME guessing and etag support for file-like objects was + deprecated because it was unreliable. Pass a filename if you are + able to, otherwise attach an etag yourself. + + .. versionchanged:: 0.5 + The ``add_etags``, ``cache_timeout`` and ``conditional`` + parameters were added. The default behavior is to add etags. + + .. versionadded:: 0.2 + """ + return werkzeug.utils.send_file( # type: ignore[return-value] + **_prepare_send_file_kwargs( + path_or_file=path_or_file, + environ=request.environ, + mimetype=mimetype, + as_attachment=as_attachment, + download_name=download_name, + conditional=conditional, + etag=etag, + last_modified=last_modified, + max_age=max_age, + ) + ) + + +def send_from_directory( + directory: os.PathLike | str, + path: os.PathLike | str, + **kwargs: t.Any, +) -> Response: + """Send a file from within a directory using :func:`send_file`. + + .. code-block:: python + + @app.route("/uploads/") + def download_file(name): + return send_from_directory( + app.config['UPLOAD_FOLDER'], name, as_attachment=True + ) + + This is a secure way to serve files from a folder, such as static + files or uploads. Uses :func:`~werkzeug.security.safe_join` to + ensure the path coming from the client is not maliciously crafted to + point outside the specified directory. + + If the final path does not point to an existing regular file, + raises a 404 :exc:`~werkzeug.exceptions.NotFound` error. + + :param directory: The directory that ``path`` must be located under, + relative to the current application's root path. + :param path: The path to the file to send, relative to + ``directory``. + :param kwargs: Arguments to pass to :func:`send_file`. + + .. versionchanged:: 2.0 + ``path`` replaces the ``filename`` parameter. + + .. versionadded:: 2.0 + Moved the implementation to Werkzeug. This is now a wrapper to + pass some Flask-specific arguments. + + .. versionadded:: 0.5 + """ + return werkzeug.utils.send_from_directory( # type: ignore[return-value] + directory, path, **_prepare_send_file_kwargs(**kwargs) + ) + + +def get_root_path(import_name: str) -> str: + """Find the root path of a package, or the path that contains a + module. If it cannot be found, returns the current working + directory. + + Not to be confused with the value returned by :func:`find_package`. + + :meta private: + """ + # Module already imported and has a file attribute. Use that first. + mod = sys.modules.get(import_name) + + if mod is not None and hasattr(mod, "__file__") and mod.__file__ is not None: + return os.path.dirname(os.path.abspath(mod.__file__)) + + # Next attempt: check the loader. + try: + spec = importlib.util.find_spec(import_name) + + if spec is None: + raise ValueError + except (ImportError, ValueError): + loader = None + else: + loader = spec.loader + + # Loader does not exist or we're referring to an unloaded main + # module or a main module without path (interactive sessions), go + # with the current working directory. + if loader is None: + return os.getcwd() + + if hasattr(loader, "get_filename"): + filepath = loader.get_filename(import_name) + else: + # Fall back to imports. + __import__(import_name) + mod = sys.modules[import_name] + filepath = getattr(mod, "__file__", None) + + # If we don't have a file path it might be because it is a + # namespace package. In this case pick the root path from the + # first module that is contained in the package. + if filepath is None: + raise RuntimeError( + "No root path can be found for the provided module" + f" {import_name!r}. This can happen because the module" + " came from an import hook that does not provide file" + " name information or because it's a namespace package." + " In this case the root path needs to be explicitly" + " provided." + ) + + # filepath is import_name.py for a module, or __init__.py for a package. + return os.path.dirname(os.path.abspath(filepath)) + + +class locked_cached_property(werkzeug.utils.cached_property): + """A :func:`property` that is only evaluated once. Like + :class:`werkzeug.utils.cached_property` except access uses a lock + for thread safety. + + .. deprecated:: 2.3 + Will be removed in Flask 2.4. Use a lock inside the decorated function if + locking is needed. + + .. versionchanged:: 2.0 + Inherits from Werkzeug's ``cached_property`` (and ``property``). + """ + + def __init__( + self, + fget: t.Callable[[t.Any], t.Any], + name: str | None = None, + doc: str | None = None, + ) -> None: + import warnings + + warnings.warn( + "'locked_cached_property' is deprecated and will be removed in Flask 2.4." + " Use a lock inside the decorated function if locking is needed.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(fget, name=name, doc=doc) + self.lock = RLock() + + def __get__(self, obj: object, type: type = None) -> t.Any: # type: ignore + if obj is None: + return self + + with self.lock: + return super().__get__(obj, type=type) + + def __set__(self, obj: object, value: t.Any) -> None: + with self.lock: + super().__set__(obj, value) + + def __delete__(self, obj: object) -> None: + with self.lock: + super().__delete__(obj) + + +def is_ip(value: str) -> bool: + """Determine if the given string is an IP address. + + :param value: value to check + :type value: str + + :return: True if string is an IP address + :rtype: bool + + .. deprecated:: 2.3 + Will be removed in Flask 2.4. + """ + warnings.warn( + "The 'is_ip' function is deprecated and will be removed in Flask 2.4.", + DeprecationWarning, + stacklevel=2, + ) + + for family in (socket.AF_INET, socket.AF_INET6): + try: + socket.inet_pton(family, value) + except OSError: + pass + else: + return True + + return False + + +@lru_cache(maxsize=None) +def _split_blueprint_path(name: str) -> list[str]: + out: list[str] = [name] + + if "." in name: + out.extend(_split_blueprint_path(name.rpartition(".")[0])) + + return out diff --git a/venv/lib/python3.12/site-packages/flask/json/__init__.py b/venv/lib/python3.12/site-packages/flask/json/__init__.py new file mode 100644 index 0000000..f15296f --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/json/__init__.py @@ -0,0 +1,170 @@ +from __future__ import annotations + +import json as _json +import typing as t + +from ..globals import current_app +from .provider import _default + +if t.TYPE_CHECKING: # pragma: no cover + from ..wrappers import Response + + +def dumps(obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.dumps() ` + method, otherwise it will use :func:`json.dumps`. + + :param obj: The data to serialize. + :param kwargs: Arguments passed to the ``dumps`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.dumps``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0.2 + :class:`decimal.Decimal` is supported by converting to a string. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. + + .. versionchanged:: 1.0.3 + ``app`` can be passed directly, rather than requiring an app + context for configuration. + """ + if current_app: + return current_app.json.dumps(obj, **kwargs) + + kwargs.setdefault("default", _default) + return _json.dumps(obj, **kwargs) + + +def dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: + """Serialize data as JSON and write to a file. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.dump() ` + method, otherwise it will use :func:`json.dump`. + + :param obj: The data to serialize. + :param fp: A file opened for writing text. Should use the UTF-8 + encoding to be valid JSON. + :param kwargs: Arguments passed to the ``dump`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.dump``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0 + Writing to a binary file, and the ``encoding`` argument, will be + removed in Flask 2.1. + """ + if current_app: + current_app.json.dump(obj, fp, **kwargs) + else: + kwargs.setdefault("default", _default) + _json.dump(obj, fp, **kwargs) + + +def loads(s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.loads() ` + method, otherwise it will use :func:`json.loads`. + + :param s: Text or UTF-8 bytes. + :param kwargs: Arguments passed to the ``loads`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.loads``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. The data must be a + string or UTF-8 bytes. + + .. versionchanged:: 1.0.3 + ``app`` can be passed directly, rather than requiring an app + context for configuration. + """ + if current_app: + return current_app.json.loads(s, **kwargs) + + return _json.loads(s, **kwargs) + + +def load(fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON read from a file. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.load() ` + method, otherwise it will use :func:`json.load`. + + :param fp: A file opened for reading text or UTF-8 bytes. + :param kwargs: Arguments passed to the ``load`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.load``, allowing an app to override + the behavior. + + .. versionchanged:: 2.2 + The ``app`` parameter will be removed in Flask 2.3. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. The file must be text + mode, or binary mode with UTF-8 bytes. + """ + if current_app: + return current_app.json.load(fp, **kwargs) + + return _json.load(fp, **kwargs) + + +def jsonify(*args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with the ``application/json`` + mimetype. A dict or list returned from a view will be converted to a + JSON response automatically without needing to call this. + + This requires an active request or application context, and calls + :meth:`app.json.response() `. + + In debug mode, the output is formatted with indentation to make it + easier to read. This may also be controlled by the provider. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + + .. versionchanged:: 2.2 + Calls ``current_app.json.response``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0.2 + :class:`decimal.Decimal` is supported by converting to a string. + + .. versionchanged:: 0.11 + Added support for serializing top-level arrays. This was a + security risk in ancient browsers. See :ref:`security-json`. + + .. versionadded:: 0.2 + """ + return current_app.json.response(*args, **kwargs) diff --git a/venv/lib/python3.12/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38ea65caf9ee663764d07dbaf686bda887782e5b GIT binary patch literal 6673 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/json/__pycache__/provider.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/json/__pycache__/provider.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ecdb97a298a8b8d8df8b338b306cd5824b2a140b GIT binary patch literal 9224 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/json/__pycache__/tag.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask/json/__pycache__/tag.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a77b3dfc614dd83f4d3915ae1f1a7a591d238996 GIT binary patch literal 13322 zcmc&)Z)_aLb>F?aJ?zS?0A2ECXW!1wy!Yn)nVI+gbI+czgzG0i`med~ACjbB(t~#?q9T`* zvLtOthBPG^vf<0fS>KdTrn*1lp9us4(Har#P zbv4tT?U?G|^-yL{wsWeJ*TbksrXr}f8y%Twwri@3*Y}{_J=HBsKIyZP(RocWBCq%a zc2hkZ76q)!?0LoSLVG#18_=E_Xdj360@_yt-OHhS0o_*v-N&K*fZkUF?dQ+|K<}@C z-p8SXfbOq>4shrLfF7uU-p`>Lpa&~73@-a)hx7;-9zL(n&YFhyxyh#|!p~H%aQKXt zva$<$%F&p)z)aiBIl7a!a@vf=w2Woj8Pm2k+hl1ylP-u?){K_3awly^&lx&%dv%r< zOgpZPrZT#1k0zg)!F@h%<7@P0$0yBHo|$JYYd&pG3gCFoG1-isGLu>!?KEdDZHFDJ zGMEX7i6`EJX6TN7LYvMzTH4XF`m#1{(q}XIjF!%67Me0`A%ot4#JCeq={ag)UUAHv z0m`Z0{KA689Jj5&Hyn<~H9hZG0_{;vFpBsz>C$Ynu!d_iYi6fSW{);~LXGiRW2P{I}yzGs9%2F_um_$+$*0TQjWO zu%pfCi@Z~dS`hVw!3oW_hxyrp*^M@8?G{8 z_(ZJIhFJNS##t$FpbrgmMoZ0^srh5JnVE5CtEOXW!%Qd7a#|XkAk|En$M~F{&}wjs z)pzMw2_ox*JH2>u9#ev$q0zz=n2eZlY%z9;n+m*MBvFKfF)n3d@YtoM0R?+Mc*q*8 z-M&Ou`oe-|RuXB?gw&$N%vs2`$&S@e#|fZkm{%VASSADuVd=AeWk||BeaEZPPaD-ES!v{y`pHhr~88nnf(E=_k zcr7s1W^@>BxU0qaR{8<u?F=9g|Pr}X3>$9dk!WDXiB!2{B#}}4M zY67y9b`ps~)a6;+gN)H|Ar!09oq?gXz&9ghJV67EEN7e?M;6x9G@fG2VmE#4KJ;Buco!}s z*Z>D{6H(aXk-#7S7;kJ9#j5nH&VxnuARENPhd_Vktd(2z4DlDaan!8o%kWd=RpH#c z2l8#eT_<0X7Rv%IVDXA=BNqVDgjWL9@PzQq8HfRkvj>0{(@@mW)aKf#Ld2uW1(3d! zX_>hy?FOaDp^re>bS-7ned8i%8WKZ9l31oJ#yHItX9h|tlyN_+XCTrBUz#v8Yb~0_ zy{HO-2qB(@t{S=o=ubctJ3>X%Oa%btYDKnSLt2q8y>~VjOiQ-J4$%ESGQ*bqAg?OD=c`ORjPCVvg`!E6ZX}Ul_fz+WsNh@}rdkZeO);NbRaq}VsdxlGdj!Qd zq+bLk?nDo)j^9|pgyRaoB(2C! zrJc7|x+1etS#sRHDYfyG#gONZNfO>4$NM(kGYW?s0>7#)y)U4CeD~|M5ckU+_0ROQ zC_xc@6K@e}v9?mUx^ycAh*u?l#>|xhIqYIe0a{rJ9SDHKc9V8-kt^(R=P%A}{|q{| z&!B+q@7TMZ|CX`carhm{w`cT?XwUjnFN8Ps8Yzi_r>#@M_#zN9X+^p{`-mV zCT<@&ee1~SV)XRgwm>M{Y>F!UT|ocvc@_RT;JUb)d2`jb;`@6nZNBXTmjYh9>iu$s z!&Mm84nFdD9)G#{@=cU{i3F^|6L`b^hzeR42u1UA56P_n*HN1=q7^cENv@TJh{q?_ zn`H6EK80dcdZV-Z#Z#Ng3y*Af9$545^z2M79Z~Fp@dbdOB>RBD%2}f4XmII0sE_eEr$97@nj*8Fe zqZEFX%hg!9JOogKT~iDn>{q}D8h+drQ<(}b2cS>t7jYCp#*ABtI^z&PFJPZsCZuZT zr_81i!idxSmbg$DJPbl9q8kHj`!1~C2n@9escRO;ls0s?}u%Xo% z$7T%`cy;-5MVSC_uUFjrnDo5wd4I~E#v1O@LGTu=x$3*>Um=xp+2nmg_sbQdR)wzk zJ!N{q_uS!tl=5HoU-eC*N7=0}N$m4zAM=+KzMZRT7j*7Eyy4nalIKhEqD|a{de!Pz zw89Hl!B)`5ei#LJ^`Cn5hx0#}|Cj9T$k~&1(T*!xL)$QeC}&xa-3jPEaGPqCD){#X!q)P zIUp;;<)B}AO1>MG+PlAYR(wTmTZ`feylD_c+OM?EX5QF!7M2b?^gp-Ekx5BSBNIbS)FM1ZAXTS>M*Cc4x=Dia?$(?R7f;Q z{8GMI62AQ;cn~_gX}nr4Kz*Lj-S9nk07B=hh#YFJz+rdO(vEZgm#sH>Lm0IXtx)C) z+OvR`g~i)>_>rGS9(~zZ4{StkL~ch9--;gI8hbq&`zbfuzs0RgEdR!#=v}|?>`GC+pYy#|velN7{X3Z4 zNkWE-YcvimMP2dU33rmMm6c=VpbT4!Vo(S;wQeoo#NY-2e_vaB4(Bevx2>%c>Hr;6 zHZzx0yui&Z4Jl;$ZvkVpDg6HzV)hk6vSOD#%UNNCD#;!3Lk2 zq=r=d4SqJsXTANx++r(LHDA=U_3VB`^Y!GiW7?kinyynD(j{&zXnyL|rg_QjDUfHr zrg^^_zHDv2__$)K{}GMYm#G*;QHMfBbIH}zqu^<7E8ApL*86-#UQ%>Bl}^Lwll?0N z#i3xc9Fl@?3nY+pC-i%O@|htO@85)NJ#ag6^j75P*3x!lq^OF$TWhpG(BLp)ax*3s zb5p@Yn46c;y1WNP6_=uVpwZqivhIzZ(j*ua|A61UP07c|lxV_~Q0vyh_7S(EAsA3` z%v$(yg}~wnL(qa_QZj)6LpOvdEF9Z9y&4Si%C{ zczH|MD(bGWFZp5R8Eqt+F&zSwZ){vlT_yV=z;-VR!nJ$-lN*oUcznC(@OEUVs17w_ zO4x8rKUl$N^WKaGSFoNc!+?g+uzw>&Jcr)5Za#aps0z>A9Kon~mAW{L zg0@FK<*bYoAhADkefB8%>?5RT`-P&>S1J4|mrvB>@<*`hbml`b4Noxhb%I%$;}37t zSqNA)vy#gm9aPYuLh^n@4bM^0WYK9W#AlASlzel9d*kIf6S?G{Gp{rc$%Tl-oe$}X z(0`y8cjk5?z0WUSUoNWs%?M!d-&J&IBiB4Q7aiJ~dxHzX`EhmlHzGKVdG;@h>Hr_U zR&ZKb<|P`2ihrjrS}pRhk&%`ANMOX;lDGbY2~0a^8N;Fdc?85fp)vkoLPM6YnTcFz z`1yuAun?RgaiDgS-M<|8O2A+i7->2X(%wyAlBiYDRx5VF(Ra!@Wi zeGP*O`T03Muszp~^M&n*R#b%}{vZLmN@G#+6Y8Ut06j%uLcoA=1CqN|Yxa2ZX z-mpu+HrQ0mpxI`sX2=Yi?PkZUI)%_A-x_y@q!0p`R962G&{VD|QxOC(=`=cObn*aa zml5Fs&Tb>h1DriL+38}(F{Hi%%TQSpQ0NeznkSO*h@xgMBM6)&-|S|%;LN&`G6o6nb&!yl|E95}XN*yQE$d{l+0S`83O(RWFOYamKaZ6-7(7M8N`Dw>C z^c(`3R-V#cyl6RP8c?(I=xGLN9@Y}ALU6LnfG*|EXoP{A6=E7lGojYUE8Wt0aIyi$ zcl?b&zK(7LwpbH2^z}?WpElxTp_;z+V&8GEB3fW|dja#1xzv6x`OV9NMq-A$69^eSEuVc_s2V&eh7I6s& zhvg;Ji%$S)I`b*{QyIH%pFv7c*bhZ(ihNfh!T*CMHl2sAN;}7nuLYh9?F=2oEqteE z-|e2ETRlVXNOI`F+S#3s=<{E={)NrMuXjA~wjUs2)l(5b$KPw`pXrbU$Ej| z9mIRu_B33V$(c7?D*+E<8A1JfeAnLa5gu3k_|hlf^kpodU%=y++{_J4=SfAFIT0P= zA@Qolxtc`!F^Xgh9ZIgxCPW4o4=;;E2WY-G>v8Q*DGv$}XR%0kNQd3f6Hhy%zPzhC zklQ+CRwLN%fXG!ynWGv)yljc`T__B0Ucp%e-;5vtrj5^NGkONWvSY3w#t`4)I_Hfu zYFa3^RRxBhr9GN3{KN%JaJ`IG$kV-sGH-0^aABd6az|i1P_I-KmQwj>6qbWBZA)qKEuB32Ga7<3wMLhl_Vosz&0xJ=A z`UalwVPHJs-a`|65yh(X_E8B(2Z3iTM4&pmo}axwyXL>s-nsU~FM9enC%4AFKmOhE zTl-JGvG;+kzAbypEIxR)cwlUM?^DI-Q+M`jYh%xkUmssjU7xtq9$kBk4ASSWf9`h2 z{#zaUH&d^79J*5nF}EgP?}#-oU&vr_BSl-Yd!-w|Rv+aj!M;{|zG@86mY?fe@wE&` zR8C(g05K^AU<`SR!l%6y1)i0VhkR3l0(ei!tQBW2vQi;CIWvV!LY*zgV}3q;JTAlM z1OS>i5fhA|R(~=_kU~G?q}lpoGz})&ja|UhZ;aj;MGQ4`6ox+1P1}aezSkoMcJ>Wy zEZtbz%)ju=PoqcfDrkUhT(obkqnWQ2Yc+OSD`f2@Egc@6#_lC$T9W@k2t8>zNB5IRnES8>4BLhI>0kKCwZcJg1<5m;Q zB)wy_&}CyN>L#Y7WYG{@KyZuD^O>r>B4I+^^~)xlo^V zVv%@uDPr9|Gy>}oi#WgWl^b8#G+#)(Ht@51#I-91L7eB+3ND1p z_2}rZa=j|oQ0^<7Ifn#Dp36-+yjjp%LnF8BH3w^C>X8p8v|S{b*>TX{F6xOnWRrsd zlwru$snAf=E$ZmiJs7|`zl|oQg>*l2n*fUlDwwQOn6lF8-^W_~tKdB2Rd;EawSN&!kK6$;7OM8}gn|KWFG zJ7;1&W8YOD71?Nyvn_N5VU_Ifp}6apW%+GiOb-0_d0CPV|5ECHQyS#|``?uMel4Lq z{H8SYYpMTDssA^@2jr2rB^0|)%KPM_YqPr&Zo8-2H-kx8KJ|`7C7*`>1OM8z0{{R3 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask/json/provider.py b/venv/lib/python3.12/site-packages/flask/json/provider.py new file mode 100644 index 0000000..0edd3d5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/json/provider.py @@ -0,0 +1,216 @@ +from __future__ import annotations + +import dataclasses +import decimal +import json +import typing as t +import uuid +import weakref +from datetime import date + +from werkzeug.http import http_date + +if t.TYPE_CHECKING: # pragma: no cover + from ..app import Flask + from ..wrappers import Response + + +class JSONProvider: + """A standard set of JSON operations for an application. Subclasses + of this can be used to customize JSON behavior or use different + JSON libraries. + + To implement a provider for a specific library, subclass this base + class and implement at least :meth:`dumps` and :meth:`loads`. All + other methods have default implementations. + + To use a different provider, either subclass ``Flask`` and set + :attr:`~flask.Flask.json_provider_class` to a provider class, or set + :attr:`app.json ` to an instance of the class. + + :param app: An application instance. This will be stored as a + :class:`weakref.proxy` on the :attr:`_app` attribute. + + .. versionadded:: 2.2 + """ + + def __init__(self, app: Flask) -> None: + self._app = weakref.proxy(app) + + def dumps(self, obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON. + + :param obj: The data to serialize. + :param kwargs: May be passed to the underlying JSON library. + """ + raise NotImplementedError + + def dump(self, obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: + """Serialize data as JSON and write to a file. + + :param obj: The data to serialize. + :param fp: A file opened for writing text. Should use the UTF-8 + encoding to be valid JSON. + :param kwargs: May be passed to the underlying JSON library. + """ + fp.write(self.dumps(obj, **kwargs)) + + def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON. + + :param s: Text or UTF-8 bytes. + :param kwargs: May be passed to the underlying JSON library. + """ + raise NotImplementedError + + def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON read from a file. + + :param fp: A file opened for reading text or UTF-8 bytes. + :param kwargs: May be passed to the underlying JSON library. + """ + return self.loads(fp.read(), **kwargs) + + def _prepare_response_obj( + self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any] + ) -> t.Any: + if args and kwargs: + raise TypeError("app.json.response() takes either args or kwargs, not both") + + if not args and not kwargs: + return None + + if len(args) == 1: + return args[0] + + return args or kwargs + + def response(self, *args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with the ``application/json`` + mimetype. + + The :func:`~flask.json.jsonify` function calls this method for + the current application. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + """ + obj = self._prepare_response_obj(args, kwargs) + return self._app.response_class(self.dumps(obj), mimetype="application/json") + + +def _default(o: t.Any) -> t.Any: + if isinstance(o, date): + return http_date(o) + + if isinstance(o, (decimal.Decimal, uuid.UUID)): + return str(o) + + if dataclasses and dataclasses.is_dataclass(o): + return dataclasses.asdict(o) + + if hasattr(o, "__html__"): + return str(o.__html__()) + + raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable") + + +class DefaultJSONProvider(JSONProvider): + """Provide JSON operations using Python's built-in :mod:`json` + library. Serializes the following additional data types: + + - :class:`datetime.datetime` and :class:`datetime.date` are + serialized to :rfc:`822` strings. This is the same as the HTTP + date format. + - :class:`uuid.UUID` is serialized to a string. + - :class:`dataclasses.dataclass` is passed to + :func:`dataclasses.asdict`. + - :class:`~markupsafe.Markup` (or any object with a ``__html__`` + method) will call the ``__html__`` method to get a string. + """ + + default: t.Callable[[t.Any], t.Any] = staticmethod( + _default + ) # type: ignore[assignment] + """Apply this function to any object that :meth:`json.dumps` does + not know how to serialize. It should return a valid JSON type or + raise a ``TypeError``. + """ + + ensure_ascii = True + """Replace non-ASCII characters with escape sequences. This may be + more compatible with some clients, but can be disabled for better + performance and size. + """ + + sort_keys = True + """Sort the keys in any serialized dicts. This may be useful for + some caching situations, but can be disabled for better performance. + When enabled, keys must all be strings, they are not converted + before sorting. + """ + + compact: bool | None = None + """If ``True``, or ``None`` out of debug mode, the :meth:`response` + output will not add indentation, newlines, or spaces. If ``False``, + or ``None`` in debug mode, it will use a non-compact representation. + """ + + mimetype = "application/json" + """The mimetype set in :meth:`response`.""" + + def dumps(self, obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON to a string. + + Keyword arguments are passed to :func:`json.dumps`. Sets some + parameter defaults from the :attr:`default`, + :attr:`ensure_ascii`, and :attr:`sort_keys` attributes. + + :param obj: The data to serialize. + :param kwargs: Passed to :func:`json.dumps`. + """ + kwargs.setdefault("default", self.default) + kwargs.setdefault("ensure_ascii", self.ensure_ascii) + kwargs.setdefault("sort_keys", self.sort_keys) + return json.dumps(obj, **kwargs) + + def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON from a string or bytes. + + :param s: Text or UTF-8 bytes. + :param kwargs: Passed to :func:`json.loads`. + """ + return json.loads(s, **kwargs) + + def response(self, *args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with it. The response mimetype + will be "application/json" and can be changed with + :attr:`mimetype`. + + If :attr:`compact` is ``False`` or debug mode is enabled, the + output will be formatted to be easier to read. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + """ + obj = self._prepare_response_obj(args, kwargs) + dump_args: dict[str, t.Any] = {} + + if (self.compact is None and self._app.debug) or self.compact is False: + dump_args.setdefault("indent", 2) + else: + dump_args.setdefault("separators", (",", ":")) + + return self._app.response_class( + f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype + ) diff --git a/venv/lib/python3.12/site-packages/flask/json/tag.py b/venv/lib/python3.12/site-packages/flask/json/tag.py new file mode 100644 index 0000000..91cc441 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/json/tag.py @@ -0,0 +1,314 @@ +""" +Tagged JSON +~~~~~~~~~~~ + +A compact representation for lossless serialization of non-standard JSON +types. :class:`~flask.sessions.SecureCookieSessionInterface` uses this +to serialize the session data, but it may be useful in other places. It +can be extended to support other types. + +.. autoclass:: TaggedJSONSerializer + :members: + +.. autoclass:: JSONTag + :members: + +Let's see an example that adds support for +:class:`~collections.OrderedDict`. Dicts don't have an order in JSON, so +to handle this we will dump the items as a list of ``[key, value]`` +pairs. Subclass :class:`JSONTag` and give it the new key ``' od'`` to +identify the type. The session serializer processes dicts first, so +insert the new tag at the front of the order since ``OrderedDict`` must +be processed before ``dict``. + +.. code-block:: python + + from flask.json.tag import JSONTag + + class TagOrderedDict(JSONTag): + __slots__ = ('serializer',) + key = ' od' + + def check(self, value): + return isinstance(value, OrderedDict) + + def to_json(self, value): + return [[k, self.serializer.tag(v)] for k, v in iteritems(value)] + + def to_python(self, value): + return OrderedDict(value) + + app.session_interface.serializer.register(TagOrderedDict, index=0) +""" +from __future__ import annotations + +import typing as t +from base64 import b64decode +from base64 import b64encode +from datetime import datetime +from uuid import UUID + +from markupsafe import Markup +from werkzeug.http import http_date +from werkzeug.http import parse_date + +from ..json import dumps +from ..json import loads + + +class JSONTag: + """Base class for defining type tags for :class:`TaggedJSONSerializer`.""" + + __slots__ = ("serializer",) + + #: The tag to mark the serialized object with. If ``None``, this tag is + #: only used as an intermediate step during tagging. + key: str | None = None + + def __init__(self, serializer: TaggedJSONSerializer) -> None: + """Create a tagger for the given serializer.""" + self.serializer = serializer + + def check(self, value: t.Any) -> bool: + """Check if the given value should be tagged by this tag.""" + raise NotImplementedError + + def to_json(self, value: t.Any) -> t.Any: + """Convert the Python object to an object that is a valid JSON type. + The tag will be added later.""" + raise NotImplementedError + + def to_python(self, value: t.Any) -> t.Any: + """Convert the JSON representation back to the correct type. The tag + will already be removed.""" + raise NotImplementedError + + def tag(self, value: t.Any) -> t.Any: + """Convert the value to a valid JSON type and add the tag structure + around it.""" + return {self.key: self.to_json(value)} + + +class TagDict(JSONTag): + """Tag for 1-item dicts whose only key matches a registered tag. + + Internally, the dict key is suffixed with `__`, and the suffix is removed + when deserializing. + """ + + __slots__ = () + key = " di" + + def check(self, value: t.Any) -> bool: + return ( + isinstance(value, dict) + and len(value) == 1 + and next(iter(value)) in self.serializer.tags + ) + + def to_json(self, value: t.Any) -> t.Any: + key = next(iter(value)) + return {f"{key}__": self.serializer.tag(value[key])} + + def to_python(self, value: t.Any) -> t.Any: + key = next(iter(value)) + return {key[:-2]: value[key]} + + +class PassDict(JSONTag): + __slots__ = () + + def check(self, value: t.Any) -> bool: + return isinstance(value, dict) + + def to_json(self, value: t.Any) -> t.Any: + # JSON objects may only have string keys, so don't bother tagging the + # key here. + return {k: self.serializer.tag(v) for k, v in value.items()} + + tag = to_json + + +class TagTuple(JSONTag): + __slots__ = () + key = " t" + + def check(self, value: t.Any) -> bool: + return isinstance(value, tuple) + + def to_json(self, value: t.Any) -> t.Any: + return [self.serializer.tag(item) for item in value] + + def to_python(self, value: t.Any) -> t.Any: + return tuple(value) + + +class PassList(JSONTag): + __slots__ = () + + def check(self, value: t.Any) -> bool: + return isinstance(value, list) + + def to_json(self, value: t.Any) -> t.Any: + return [self.serializer.tag(item) for item in value] + + tag = to_json + + +class TagBytes(JSONTag): + __slots__ = () + key = " b" + + def check(self, value: t.Any) -> bool: + return isinstance(value, bytes) + + def to_json(self, value: t.Any) -> t.Any: + return b64encode(value).decode("ascii") + + def to_python(self, value: t.Any) -> t.Any: + return b64decode(value) + + +class TagMarkup(JSONTag): + """Serialize anything matching the :class:`~markupsafe.Markup` API by + having a ``__html__`` method to the result of that method. Always + deserializes to an instance of :class:`~markupsafe.Markup`.""" + + __slots__ = () + key = " m" + + def check(self, value: t.Any) -> bool: + return callable(getattr(value, "__html__", None)) + + def to_json(self, value: t.Any) -> t.Any: + return str(value.__html__()) + + def to_python(self, value: t.Any) -> t.Any: + return Markup(value) + + +class TagUUID(JSONTag): + __slots__ = () + key = " u" + + def check(self, value: t.Any) -> bool: + return isinstance(value, UUID) + + def to_json(self, value: t.Any) -> t.Any: + return value.hex + + def to_python(self, value: t.Any) -> t.Any: + return UUID(value) + + +class TagDateTime(JSONTag): + __slots__ = () + key = " d" + + def check(self, value: t.Any) -> bool: + return isinstance(value, datetime) + + def to_json(self, value: t.Any) -> t.Any: + return http_date(value) + + def to_python(self, value: t.Any) -> t.Any: + return parse_date(value) + + +class TaggedJSONSerializer: + """Serializer that uses a tag system to compactly represent objects that + are not JSON types. Passed as the intermediate serializer to + :class:`itsdangerous.Serializer`. + + The following extra types are supported: + + * :class:`dict` + * :class:`tuple` + * :class:`bytes` + * :class:`~markupsafe.Markup` + * :class:`~uuid.UUID` + * :class:`~datetime.datetime` + """ + + __slots__ = ("tags", "order") + + #: Tag classes to bind when creating the serializer. Other tags can be + #: added later using :meth:`~register`. + default_tags = [ + TagDict, + PassDict, + TagTuple, + PassList, + TagBytes, + TagMarkup, + TagUUID, + TagDateTime, + ] + + def __init__(self) -> None: + self.tags: dict[str, JSONTag] = {} + self.order: list[JSONTag] = [] + + for cls in self.default_tags: + self.register(cls) + + def register( + self, + tag_class: type[JSONTag], + force: bool = False, + index: int | None = None, + ) -> None: + """Register a new tag with this serializer. + + :param tag_class: tag class to register. Will be instantiated with this + serializer instance. + :param force: overwrite an existing tag. If false (default), a + :exc:`KeyError` is raised. + :param index: index to insert the new tag in the tag order. Useful when + the new tag is a special case of an existing tag. If ``None`` + (default), the tag is appended to the end of the order. + + :raise KeyError: if the tag key is already registered and ``force`` is + not true. + """ + tag = tag_class(self) + key = tag.key + + if key is not None: + if not force and key in self.tags: + raise KeyError(f"Tag '{key}' is already registered.") + + self.tags[key] = tag + + if index is None: + self.order.append(tag) + else: + self.order.insert(index, tag) + + def tag(self, value: t.Any) -> dict[str, t.Any]: + """Convert a value to a tagged representation if necessary.""" + for tag in self.order: + if tag.check(value): + return tag.tag(value) + + return value + + def untag(self, value: dict[str, t.Any]) -> t.Any: + """Convert a tagged representation back to the original type.""" + if len(value) != 1: + return value + + key = next(iter(value)) + + if key not in self.tags: + return value + + return self.tags[key].to_python(value[key]) + + def dumps(self, value: t.Any) -> str: + """Tag the value and dump it to a compact JSON string.""" + return dumps(self.tag(value), separators=(",", ":")) + + def loads(self, value: str) -> t.Any: + """Load data from a JSON string and deserialized any tagged objects.""" + return loads(value, object_hook=self.untag) diff --git a/venv/lib/python3.12/site-packages/flask/logging.py b/venv/lib/python3.12/site-packages/flask/logging.py new file mode 100644 index 0000000..99f6be8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/logging.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +import logging +import sys +import typing as t + +from werkzeug.local import LocalProxy + +from .globals import request + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + + +@LocalProxy +def wsgi_errors_stream() -> t.TextIO: + """Find the most appropriate error stream for the application. If a request + is active, log to ``wsgi.errors``, otherwise use ``sys.stderr``. + + If you configure your own :class:`logging.StreamHandler`, you may want to + use this for the stream. If you are using file or dict configuration and + can't import this directly, you can refer to it as + ``ext://flask.logging.wsgi_errors_stream``. + """ + return request.environ["wsgi.errors"] if request else sys.stderr + + +def has_level_handler(logger: logging.Logger) -> bool: + """Check if there is a handler in the logging chain that will handle the + given logger's :meth:`effective level <~logging.Logger.getEffectiveLevel>`. + """ + level = logger.getEffectiveLevel() + current = logger + + while current: + if any(handler.level <= level for handler in current.handlers): + return True + + if not current.propagate: + break + + current = current.parent # type: ignore + + return False + + +#: Log messages to :func:`~flask.logging.wsgi_errors_stream` with the format +#: ``[%(asctime)s] %(levelname)s in %(module)s: %(message)s``. +default_handler = logging.StreamHandler(wsgi_errors_stream) # type: ignore +default_handler.setFormatter( + logging.Formatter("[%(asctime)s] %(levelname)s in %(module)s: %(message)s") +) + + +def create_logger(app: Flask) -> logging.Logger: + """Get the Flask app's logger and configure it if needed. + + The logger name will be the same as + :attr:`app.import_name `. + + When :attr:`~flask.Flask.debug` is enabled, set the logger level to + :data:`logging.DEBUG` if it is not set. + + If there is no handler for the logger's effective level, add a + :class:`~logging.StreamHandler` for + :func:`~flask.logging.wsgi_errors_stream` with a basic format. + """ + logger = logging.getLogger(app.name) + + if app.debug and not logger.level: + logger.setLevel(logging.DEBUG) + + if not has_level_handler(logger): + logger.addHandler(default_handler) + + return logger diff --git a/venv/lib/python3.12/site-packages/flask/py.typed b/venv/lib/python3.12/site-packages/flask/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/flask/scaffold.py b/venv/lib/python3.12/site-packages/flask/scaffold.py new file mode 100644 index 0000000..d15b873 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/scaffold.py @@ -0,0 +1,873 @@ +from __future__ import annotations + +import importlib.util +import os +import pathlib +import sys +import typing as t +from collections import defaultdict +from datetime import timedelta +from functools import update_wrapper + +from jinja2 import FileSystemLoader +from werkzeug.exceptions import default_exceptions +from werkzeug.exceptions import HTTPException +from werkzeug.utils import cached_property + +from . import typing as ft +from .cli import AppGroup +from .globals import current_app +from .helpers import get_root_path +from .helpers import send_from_directory +from .templating import _default_template_ctx_processor + +if t.TYPE_CHECKING: # pragma: no cover + from .wrappers import Response + +# a singleton sentinel value for parameter defaults +_sentinel = object() + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable) +T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable) +T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable) +T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) +T_template_context_processor = t.TypeVar( + "T_template_context_processor", bound=ft.TemplateContextProcessorCallable +) +T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable) +T_url_value_preprocessor = t.TypeVar( + "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable +) +T_route = t.TypeVar("T_route", bound=ft.RouteCallable) + + +def setupmethod(f: F) -> F: + f_name = f.__name__ + + def wrapper_func(self, *args: t.Any, **kwargs: t.Any) -> t.Any: + self._check_setup_finished(f_name) + return f(self, *args, **kwargs) + + return t.cast(F, update_wrapper(wrapper_func, f)) + + +class Scaffold: + """Common behavior shared between :class:`~flask.Flask` and + :class:`~flask.blueprints.Blueprint`. + + :param import_name: The import name of the module where this object + is defined. Usually :attr:`__name__` should be used. + :param static_folder: Path to a folder of static files to serve. + If this is set, a static route will be added. + :param static_url_path: URL prefix for the static route. + :param template_folder: Path to a folder containing template files. + for rendering. If this is set, a Jinja loader will be added. + :param root_path: The path that static, template, and resource files + are relative to. Typically not set, it is discovered based on + the ``import_name``. + + .. versionadded:: 2.0 + """ + + name: str + _static_folder: str | None = None + _static_url_path: str | None = None + + def __init__( + self, + import_name: str, + static_folder: str | os.PathLike | None = None, + static_url_path: str | None = None, + template_folder: str | os.PathLike | None = None, + root_path: str | None = None, + ): + #: The name of the package or module that this object belongs + #: to. Do not change this once it is set by the constructor. + self.import_name = import_name + + self.static_folder = static_folder # type: ignore + self.static_url_path = static_url_path + + #: The path to the templates folder, relative to + #: :attr:`root_path`, to add to the template loader. ``None`` if + #: templates should not be added. + self.template_folder = template_folder + + if root_path is None: + root_path = get_root_path(self.import_name) + + #: Absolute path to the package on the filesystem. Used to look + #: up resources contained in the package. + self.root_path = root_path + + #: The Click command group for registering CLI commands for this + #: object. The commands are available from the ``flask`` command + #: once the application has been discovered and blueprints have + #: been registered. + self.cli = AppGroup() + + #: A dictionary mapping endpoint names to view functions. + #: + #: To register a view function, use the :meth:`route` decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.view_functions: dict[str, t.Callable] = {} + + #: A data structure of registered error handlers, in the format + #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is + #: the name of a blueprint the handlers are active for, or + #: ``None`` for all requests. The ``code`` key is the HTTP + #: status code for ``HTTPException``, or ``None`` for + #: other exceptions. The innermost dictionary maps exception + #: classes to handler functions. + #: + #: To register an error handler, use the :meth:`errorhandler` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.error_handler_spec: dict[ + ft.AppOrBlueprintKey, + dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]], + ] = defaultdict(lambda: defaultdict(dict)) + + #: A data structure of functions to call at the beginning of + #: each request, in the format ``{scope: [functions]}``. The + #: ``scope`` key is the name of a blueprint the functions are + #: active for, or ``None`` for all requests. + #: + #: To register a function, use the :meth:`before_request` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.before_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable] + ] = defaultdict(list) + + #: A data structure of functions to call at the end of each + #: request, in the format ``{scope: [functions]}``. The + #: ``scope`` key is the name of a blueprint the functions are + #: active for, or ``None`` for all requests. + #: + #: To register a function, use the :meth:`after_request` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.after_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.AfterRequestCallable] + ] = defaultdict(list) + + #: A data structure of functions to call at the end of each + #: request even if an exception is raised, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the :meth:`teardown_request` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.teardown_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.TeardownCallable] + ] = defaultdict(list) + + #: A data structure of functions to call to pass extra context + #: values when rendering templates, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the :meth:`context_processor` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.template_context_processors: dict[ + ft.AppOrBlueprintKey, list[ft.TemplateContextProcessorCallable] + ] = defaultdict(list, {None: [_default_template_ctx_processor]}) + + #: A data structure of functions to call to modify the keyword + #: arguments passed to the view function, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the + #: :meth:`url_value_preprocessor` decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.url_value_preprocessors: dict[ + ft.AppOrBlueprintKey, + list[ft.URLValuePreprocessorCallable], + ] = defaultdict(list) + + #: A data structure of functions to call to modify the keyword + #: arguments when generating URLs, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the :meth:`url_defaults` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.url_default_functions: dict[ + ft.AppOrBlueprintKey, list[ft.URLDefaultCallable] + ] = defaultdict(list) + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.name!r}>" + + def _check_setup_finished(self, f_name: str) -> None: + raise NotImplementedError + + @property + def static_folder(self) -> str | None: + """The absolute path to the configured static folder. ``None`` + if no static folder is set. + """ + if self._static_folder is not None: + return os.path.join(self.root_path, self._static_folder) + else: + return None + + @static_folder.setter + def static_folder(self, value: str | os.PathLike | None) -> None: + if value is not None: + value = os.fspath(value).rstrip(r"\/") + + self._static_folder = value + + @property + def has_static_folder(self) -> bool: + """``True`` if :attr:`static_folder` is set. + + .. versionadded:: 0.5 + """ + return self.static_folder is not None + + @property + def static_url_path(self) -> str | None: + """The URL prefix that the static route will be accessible from. + + If it was not configured during init, it is derived from + :attr:`static_folder`. + """ + if self._static_url_path is not None: + return self._static_url_path + + if self.static_folder is not None: + basename = os.path.basename(self.static_folder) + return f"/{basename}".rstrip("/") + + return None + + @static_url_path.setter + def static_url_path(self, value: str | None) -> None: + if value is not None: + value = value.rstrip("/") + + self._static_url_path = value + + def get_send_file_max_age(self, filename: str | None) -> int | None: + """Used by :func:`send_file` to determine the ``max_age`` cache + value for a given file path if it wasn't passed. + + By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from + the configuration of :data:`~flask.current_app`. This defaults + to ``None``, which tells the browser to use conditional requests + instead of a timed cache, which is usually preferable. + + .. versionchanged:: 2.0 + The default configuration is ``None`` instead of 12 hours. + + .. versionadded:: 0.9 + """ + value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"] + + if value is None: + return None + + if isinstance(value, timedelta): + return int(value.total_seconds()) + + return value + + def send_static_file(self, filename: str) -> Response: + """The view function used to serve files from + :attr:`static_folder`. A route is automatically registered for + this view at :attr:`static_url_path` if :attr:`static_folder` is + set. + + .. versionadded:: 0.5 + """ + if not self.has_static_folder: + raise RuntimeError("'static_folder' must be set to serve static_files.") + + # send_file only knows to call get_send_file_max_age on the app, + # call it here so it works for blueprints too. + max_age = self.get_send_file_max_age(filename) + return send_from_directory( + t.cast(str, self.static_folder), filename, max_age=max_age + ) + + @cached_property + def jinja_loader(self) -> FileSystemLoader | None: + """The Jinja loader for this object's templates. By default this + is a class :class:`jinja2.loaders.FileSystemLoader` to + :attr:`template_folder` if it is set. + + .. versionadded:: 0.5 + """ + if self.template_folder is not None: + return FileSystemLoader(os.path.join(self.root_path, self.template_folder)) + else: + return None + + def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]: + """Open a resource file relative to :attr:`root_path` for + reading. + + For example, if the file ``schema.sql`` is next to the file + ``app.py`` where the ``Flask`` app is defined, it can be opened + with: + + .. code-block:: python + + with app.open_resource("schema.sql") as f: + conn.executescript(f.read()) + + :param resource: Path to the resource relative to + :attr:`root_path`. + :param mode: Open the file in this mode. Only reading is + supported, valid values are "r" (or "rt") and "rb". + """ + if mode not in {"r", "rt", "rb"}: + raise ValueError("Resources can only be opened for reading.") + + return open(os.path.join(self.root_path, resource), mode) + + def _method_route( + self, + method: str, + rule: str, + options: dict, + ) -> t.Callable[[T_route], T_route]: + if "methods" in options: + raise TypeError("Use the 'route' decorator to use the 'methods' argument.") + + return self.route(rule, methods=[method], **options) + + @setupmethod + def get(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["GET"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("GET", rule, options) + + @setupmethod + def post(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["POST"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("POST", rule, options) + + @setupmethod + def put(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["PUT"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("PUT", rule, options) + + @setupmethod + def delete(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["DELETE"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("DELETE", rule, options) + + @setupmethod + def patch(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["PATCH"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("PATCH", rule, options) + + @setupmethod + def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Decorate a view function to register it with the given URL + rule and options. Calls :meth:`add_url_rule`, which has more + details about the implementation. + + .. code-block:: python + + @app.route("/") + def index(): + return "Hello, World!" + + See :ref:`url-route-registrations`. + + The endpoint name for the route defaults to the name of the view + function if the ``endpoint`` parameter isn't passed. + + The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and + ``OPTIONS`` are added automatically. + + :param rule: The URL rule string. + :param options: Extra options passed to the + :class:`~werkzeug.routing.Rule` object. + """ + + def decorator(f: T_route) -> T_route: + endpoint = options.pop("endpoint", None) + self.add_url_rule(rule, endpoint, f, **options) + return f + + return decorator + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, + **options: t.Any, + ) -> None: + """Register a rule for routing incoming requests and building + URLs. The :meth:`route` decorator is a shortcut to call this + with the ``view_func`` argument. These are equivalent: + + .. code-block:: python + + @app.route("/") + def index(): + ... + + .. code-block:: python + + def index(): + ... + + app.add_url_rule("/", view_func=index) + + See :ref:`url-route-registrations`. + + The endpoint name for the route defaults to the name of the view + function if the ``endpoint`` parameter isn't passed. An error + will be raised if a function has already been registered for the + endpoint. + + The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is + always added automatically, and ``OPTIONS`` is added + automatically by default. + + ``view_func`` does not necessarily need to be passed, but if the + rule should participate in routing an endpoint name must be + associated with a view function at some point with the + :meth:`endpoint` decorator. + + .. code-block:: python + + app.add_url_rule("/", endpoint="index") + + @app.endpoint("index") + def index(): + ... + + If ``view_func`` has a ``required_methods`` attribute, those + methods are added to the passed and automatic methods. If it + has a ``provide_automatic_methods`` attribute, it is used as the + default if the parameter is not passed. + + :param rule: The URL rule string. + :param endpoint: The endpoint name to associate with the rule + and view function. Used when routing and building URLs. + Defaults to ``view_func.__name__``. + :param view_func: The view function to associate with the + endpoint name. + :param provide_automatic_options: Add the ``OPTIONS`` method and + respond to ``OPTIONS`` requests automatically. + :param options: Extra options passed to the + :class:`~werkzeug.routing.Rule` object. + """ + raise NotImplementedError + + @setupmethod + def endpoint(self, endpoint: str) -> t.Callable[[F], F]: + """Decorate a view function to register it for the given + endpoint. Used if a rule is added without a ``view_func`` with + :meth:`add_url_rule`. + + .. code-block:: python + + app.add_url_rule("/ex", endpoint="example") + + @app.endpoint("example") + def example(): + ... + + :param endpoint: The endpoint name to associate with the view + function. + """ + + def decorator(f: F) -> F: + self.view_functions[endpoint] = f + return f + + return decorator + + @setupmethod + def before_request(self, f: T_before_request) -> T_before_request: + """Register a function to run before each request. + + For example, this can be used to open a database connection, or + to load the logged in user from the session. + + .. code-block:: python + + @app.before_request + def load_user(): + if "user_id" in session: + g.user = db.session.get(session["user_id"]) + + The function will be called without any arguments. If it returns + a non-``None`` value, the value is handled as if it was the + return value from the view, and further request handling is + stopped. + + This is available on both app and blueprint objects. When used on an app, this + executes before every request. When used on a blueprint, this executes before + every request that the blueprint handles. To register with a blueprint and + execute before every request, use :meth:`.Blueprint.before_app_request`. + """ + self.before_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def after_request(self, f: T_after_request) -> T_after_request: + """Register a function to run after each request to this object. + + The function is called with the response object, and must return + a response object. This allows the functions to modify or + replace the response before it is sent. + + If a function raises an exception, any remaining + ``after_request`` functions will not be called. Therefore, this + should not be used for actions that must execute, such as to + close resources. Use :meth:`teardown_request` for that. + + This is available on both app and blueprint objects. When used on an app, this + executes after every request. When used on a blueprint, this executes after + every request that the blueprint handles. To register with a blueprint and + execute after every request, use :meth:`.Blueprint.after_app_request`. + """ + self.after_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def teardown_request(self, f: T_teardown) -> T_teardown: + """Register a function to be called when the request context is + popped. Typically this happens at the end of each request, but + contexts may be pushed manually as well during testing. + + .. code-block:: python + + with app.test_request_context(): + ... + + When the ``with`` block exits (or ``ctx.pop()`` is called), the + teardown functions are called just before the request context is + made inactive. + + When a teardown function was called because of an unhandled + exception it will be passed an error object. If an + :meth:`errorhandler` is registered, it will handle the exception + and the teardown will not receive it. + + Teardown functions must avoid raising exceptions. If they + execute code that might fail they must surround that code with a + ``try``/``except`` block and log any errors. + + The return values of teardown functions are ignored. + + This is available on both app and blueprint objects. When used on an app, this + executes after every request. When used on a blueprint, this executes after + every request that the blueprint handles. To register with a blueprint and + execute after every request, use :meth:`.Blueprint.teardown_app_request`. + """ + self.teardown_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def context_processor( + self, + f: T_template_context_processor, + ) -> T_template_context_processor: + """Registers a template context processor function. These functions run before + rendering a template. The keys of the returned dict are added as variables + available in the template. + + This is available on both app and blueprint objects. When used on an app, this + is called for every rendered template. When used on a blueprint, this is called + for templates rendered from the blueprint's views. To register with a blueprint + and affect every template, use :meth:`.Blueprint.app_context_processor`. + """ + self.template_context_processors[None].append(f) + return f + + @setupmethod + def url_value_preprocessor( + self, + f: T_url_value_preprocessor, + ) -> T_url_value_preprocessor: + """Register a URL value preprocessor function for all view + functions in the application. These functions will be called before the + :meth:`before_request` functions. + + The function can modify the values captured from the matched url before + they are passed to the view. For example, this can be used to pop a + common language code value and place it in ``g`` rather than pass it to + every view. + + The function is passed the endpoint name and values dict. The return + value is ignored. + + This is available on both app and blueprint objects. When used on an app, this + is called for every request. When used on a blueprint, this is called for + requests that the blueprint handles. To register with a blueprint and affect + every request, use :meth:`.Blueprint.app_url_value_preprocessor`. + """ + self.url_value_preprocessors[None].append(f) + return f + + @setupmethod + def url_defaults(self, f: T_url_defaults) -> T_url_defaults: + """Callback function for URL defaults for all view functions of the + application. It's called with the endpoint and values and should + update the values passed in place. + + This is available on both app and blueprint objects. When used on an app, this + is called for every request. When used on a blueprint, this is called for + requests that the blueprint handles. To register with a blueprint and affect + every request, use :meth:`.Blueprint.app_url_defaults`. + """ + self.url_default_functions[None].append(f) + return f + + @setupmethod + def errorhandler( + self, code_or_exception: type[Exception] | int + ) -> t.Callable[[T_error_handler], T_error_handler]: + """Register a function to handle errors by code or exception class. + + A decorator that is used to register a function given an + error code. Example:: + + @app.errorhandler(404) + def page_not_found(error): + return 'This page does not exist', 404 + + You can also register handlers for arbitrary exceptions:: + + @app.errorhandler(DatabaseError) + def special_exception_handler(error): + return 'Database connection failed', 500 + + This is available on both app and blueprint objects. When used on an app, this + can handle errors from every request. When used on a blueprint, this can handle + errors from requests that the blueprint handles. To register with a blueprint + and affect every request, use :meth:`.Blueprint.app_errorhandler`. + + .. versionadded:: 0.7 + Use :meth:`register_error_handler` instead of modifying + :attr:`error_handler_spec` directly, for application wide error + handlers. + + .. versionadded:: 0.7 + One can now additionally also register custom exception types + that do not necessarily have to be a subclass of the + :class:`~werkzeug.exceptions.HTTPException` class. + + :param code_or_exception: the code as integer for the handler, or + an arbitrary exception + """ + + def decorator(f: T_error_handler) -> T_error_handler: + self.register_error_handler(code_or_exception, f) + return f + + return decorator + + @setupmethod + def register_error_handler( + self, + code_or_exception: type[Exception] | int, + f: ft.ErrorHandlerCallable, + ) -> None: + """Alternative error attach function to the :meth:`errorhandler` + decorator that is more straightforward to use for non decorator + usage. + + .. versionadded:: 0.7 + """ + exc_class, code = self._get_exc_class_and_code(code_or_exception) + self.error_handler_spec[None][code][exc_class] = f + + @staticmethod + def _get_exc_class_and_code( + exc_class_or_code: type[Exception] | int, + ) -> tuple[type[Exception], int | None]: + """Get the exception class being handled. For HTTP status codes + or ``HTTPException`` subclasses, return both the exception and + status code. + + :param exc_class_or_code: Any exception class, or an HTTP status + code as an integer. + """ + exc_class: type[Exception] + + if isinstance(exc_class_or_code, int): + try: + exc_class = default_exceptions[exc_class_or_code] + except KeyError: + raise ValueError( + f"'{exc_class_or_code}' is not a recognized HTTP" + " error code. Use a subclass of HTTPException with" + " that code instead." + ) from None + else: + exc_class = exc_class_or_code + + if isinstance(exc_class, Exception): + raise TypeError( + f"{exc_class!r} is an instance, not a class. Handlers" + " can only be registered for Exception classes or HTTP" + " error codes." + ) + + if not issubclass(exc_class, Exception): + raise ValueError( + f"'{exc_class.__name__}' is not a subclass of Exception." + " Handlers can only be registered for Exception classes" + " or HTTP error codes." + ) + + if issubclass(exc_class, HTTPException): + return exc_class, exc_class.code + else: + return exc_class, None + + +def _endpoint_from_view_func(view_func: t.Callable) -> str: + """Internal helper that returns the default endpoint for a given + function. This always is the function name. + """ + assert view_func is not None, "expected view func if endpoint is not provided." + return view_func.__name__ + + +def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool: + # Path.is_relative_to doesn't exist until Python 3.9 + try: + path.relative_to(base) + return True + except ValueError: + return False + + +def _find_package_path(import_name): + """Find the path that contains the package or module.""" + root_mod_name, _, _ = import_name.partition(".") + + try: + root_spec = importlib.util.find_spec(root_mod_name) + + if root_spec is None: + raise ValueError("not found") + except (ImportError, ValueError): + # ImportError: the machinery told us it does not exist + # ValueError: + # - the module name was invalid + # - the module name is __main__ + # - we raised `ValueError` due to `root_spec` being `None` + return os.getcwd() + + if root_spec.origin in {"namespace", None}: + # namespace package + package_spec = importlib.util.find_spec(import_name) + + if package_spec is not None and package_spec.submodule_search_locations: + # Pick the path in the namespace that contains the submodule. + package_path = pathlib.Path( + os.path.commonpath(package_spec.submodule_search_locations) + ) + search_location = next( + location + for location in root_spec.submodule_search_locations + if _path_is_relative_to(package_path, location) + ) + else: + # Pick the first path. + search_location = root_spec.submodule_search_locations[0] + + return os.path.dirname(search_location) + elif root_spec.submodule_search_locations: + # package with __init__.py + return os.path.dirname(os.path.dirname(root_spec.origin)) + else: + # module + return os.path.dirname(root_spec.origin) + + +def find_package(import_name: str): + """Find the prefix that a package is installed under, and the path + that it would be imported from. + + The prefix is the directory containing the standard directory + hierarchy (lib, bin, etc.). If the package is not installed to the + system (:attr:`sys.prefix`) or a virtualenv (``site-packages``), + ``None`` is returned. + + The path is the entry in :attr:`sys.path` that contains the package + for import. If the package is not installed, it's assumed that the + package was imported from the current working directory. + """ + package_path = _find_package_path(import_name) + py_prefix = os.path.abspath(sys.prefix) + + # installed to the system + if _path_is_relative_to(pathlib.PurePath(package_path), py_prefix): + return py_prefix, package_path + + site_parent, site_folder = os.path.split(package_path) + + # installed to a virtualenv + if site_folder.lower() == "site-packages": + parent, folder = os.path.split(site_parent) + + # Windows (prefix/lib/site-packages) + if folder.lower() == "lib": + return parent, package_path + + # Unix (prefix/lib/pythonX.Y/site-packages) + if os.path.basename(parent).lower() == "lib": + return os.path.dirname(parent), package_path + + # something else (prefix/site-packages) + return site_parent, package_path + + # not installed + return None, package_path diff --git a/venv/lib/python3.12/site-packages/flask/sessions.py b/venv/lib/python3.12/site-packages/flask/sessions.py new file mode 100644 index 0000000..e5650d6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/sessions.py @@ -0,0 +1,367 @@ +from __future__ import annotations + +import hashlib +import typing as t +from collections.abc import MutableMapping +from datetime import datetime +from datetime import timezone + +from itsdangerous import BadSignature +from itsdangerous import URLSafeTimedSerializer +from werkzeug.datastructures import CallbackDict + +from .json.tag import TaggedJSONSerializer + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + from .wrappers import Request, Response + + +class SessionMixin(MutableMapping): + """Expands a basic dictionary with session attributes.""" + + @property + def permanent(self) -> bool: + """This reflects the ``'_permanent'`` key in the dict.""" + return self.get("_permanent", False) + + @permanent.setter + def permanent(self, value: bool) -> None: + self["_permanent"] = bool(value) + + #: Some implementations can detect whether a session is newly + #: created, but that is not guaranteed. Use with caution. The mixin + # default is hard-coded ``False``. + new = False + + #: Some implementations can detect changes to the session and set + #: this when that happens. The mixin default is hard coded to + #: ``True``. + modified = True + + #: Some implementations can detect when session data is read or + #: written and set this when that happens. The mixin default is hard + #: coded to ``True``. + accessed = True + + +class SecureCookieSession(CallbackDict, SessionMixin): + """Base class for sessions based on signed cookies. + + This session backend will set the :attr:`modified` and + :attr:`accessed` attributes. It cannot reliably track whether a + session is new (vs. empty), so :attr:`new` remains hard coded to + ``False``. + """ + + #: When data is changed, this is set to ``True``. Only the session + #: dictionary itself is tracked; if the session contains mutable + #: data (for example a nested dict) then this must be set to + #: ``True`` manually when modifying that data. The session cookie + #: will only be written to the response if this is ``True``. + modified = False + + #: When data is read or written, this is set to ``True``. Used by + # :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie`` + #: header, which allows caching proxies to cache different pages for + #: different users. + accessed = False + + def __init__(self, initial: t.Any = None) -> None: + def on_update(self) -> None: + self.modified = True + self.accessed = True + + super().__init__(initial, on_update) + + def __getitem__(self, key: str) -> t.Any: + self.accessed = True + return super().__getitem__(key) + + def get(self, key: str, default: t.Any = None) -> t.Any: + self.accessed = True + return super().get(key, default) + + def setdefault(self, key: str, default: t.Any = None) -> t.Any: + self.accessed = True + return super().setdefault(key, default) + + +class NullSession(SecureCookieSession): + """Class used to generate nicer error messages if sessions are not + available. Will still allow read-only access to the empty session + but fail on setting. + """ + + def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: + raise RuntimeError( + "The session is unavailable because no secret " + "key was set. Set the secret_key on the " + "application to something unique and secret." + ) + + __setitem__ = __delitem__ = clear = pop = popitem = update = setdefault = _fail # type: ignore # noqa: B950 + del _fail + + +class SessionInterface: + """The basic interface you have to implement in order to replace the + default session interface which uses werkzeug's securecookie + implementation. The only methods you have to implement are + :meth:`open_session` and :meth:`save_session`, the others have + useful defaults which you don't need to change. + + The session object returned by the :meth:`open_session` method has to + provide a dictionary like interface plus the properties and methods + from the :class:`SessionMixin`. We recommend just subclassing a dict + and adding that mixin:: + + class Session(dict, SessionMixin): + pass + + If :meth:`open_session` returns ``None`` Flask will call into + :meth:`make_null_session` to create a session that acts as replacement + if the session support cannot work because some requirement is not + fulfilled. The default :class:`NullSession` class that is created + will complain that the secret key was not set. + + To replace the session interface on an application all you have to do + is to assign :attr:`flask.Flask.session_interface`:: + + app = Flask(__name__) + app.session_interface = MySessionInterface() + + Multiple requests with the same session may be sent and handled + concurrently. When implementing a new session interface, consider + whether reads or writes to the backing store must be synchronized. + There is no guarantee on the order in which the session for each + request is opened or saved, it will occur in the order that requests + begin and end processing. + + .. versionadded:: 0.8 + """ + + #: :meth:`make_null_session` will look here for the class that should + #: be created when a null session is requested. Likewise the + #: :meth:`is_null_session` method will perform a typecheck against + #: this type. + null_session_class = NullSession + + #: A flag that indicates if the session interface is pickle based. + #: This can be used by Flask extensions to make a decision in regards + #: to how to deal with the session object. + #: + #: .. versionadded:: 0.10 + pickle_based = False + + def make_null_session(self, app: Flask) -> NullSession: + """Creates a null session which acts as a replacement object if the + real session support could not be loaded due to a configuration + error. This mainly aids the user experience because the job of the + null session is to still support lookup without complaining but + modifications are answered with a helpful error message of what + failed. + + This creates an instance of :attr:`null_session_class` by default. + """ + return self.null_session_class() + + def is_null_session(self, obj: object) -> bool: + """Checks if a given object is a null session. Null sessions are + not asked to be saved. + + This checks if the object is an instance of :attr:`null_session_class` + by default. + """ + return isinstance(obj, self.null_session_class) + + def get_cookie_name(self, app: Flask) -> str: + """The name of the session cookie. Uses``app.config["SESSION_COOKIE_NAME"]``.""" + return app.config["SESSION_COOKIE_NAME"] + + def get_cookie_domain(self, app: Flask) -> str | None: + """The value of the ``Domain`` parameter on the session cookie. If not set, + browsers will only send the cookie to the exact domain it was set from. + Otherwise, they will send it to any subdomain of the given value as well. + + Uses the :data:`SESSION_COOKIE_DOMAIN` config. + + .. versionchanged:: 2.3 + Not set by default, does not fall back to ``SERVER_NAME``. + """ + rv = app.config["SESSION_COOKIE_DOMAIN"] + return rv if rv else None + + def get_cookie_path(self, app: Flask) -> str: + """Returns the path for which the cookie should be valid. The + default implementation uses the value from the ``SESSION_COOKIE_PATH`` + config var if it's set, and falls back to ``APPLICATION_ROOT`` or + uses ``/`` if it's ``None``. + """ + return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"] + + def get_cookie_httponly(self, app: Flask) -> bool: + """Returns True if the session cookie should be httponly. This + currently just returns the value of the ``SESSION_COOKIE_HTTPONLY`` + config var. + """ + return app.config["SESSION_COOKIE_HTTPONLY"] + + def get_cookie_secure(self, app: Flask) -> bool: + """Returns True if the cookie should be secure. This currently + just returns the value of the ``SESSION_COOKIE_SECURE`` setting. + """ + return app.config["SESSION_COOKIE_SECURE"] + + def get_cookie_samesite(self, app: Flask) -> str: + """Return ``'Strict'`` or ``'Lax'`` if the cookie should use the + ``SameSite`` attribute. This currently just returns the value of + the :data:`SESSION_COOKIE_SAMESITE` setting. + """ + return app.config["SESSION_COOKIE_SAMESITE"] + + def get_expiration_time(self, app: Flask, session: SessionMixin) -> datetime | None: + """A helper method that returns an expiration date for the session + or ``None`` if the session is linked to the browser session. The + default implementation returns now + the permanent session + lifetime configured on the application. + """ + if session.permanent: + return datetime.now(timezone.utc) + app.permanent_session_lifetime + return None + + def should_set_cookie(self, app: Flask, session: SessionMixin) -> bool: + """Used by session backends to determine if a ``Set-Cookie`` header + should be set for this session cookie for this response. If the session + has been modified, the cookie is set. If the session is permanent and + the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is + always set. + + This check is usually skipped if the session was deleted. + + .. versionadded:: 0.11 + """ + + return session.modified or ( + session.permanent and app.config["SESSION_REFRESH_EACH_REQUEST"] + ) + + def open_session(self, app: Flask, request: Request) -> SessionMixin | None: + """This is called at the beginning of each request, after + pushing the request context, before matching the URL. + + This must return an object which implements a dictionary-like + interface as well as the :class:`SessionMixin` interface. + + This will return ``None`` to indicate that loading failed in + some way that is not immediately an error. The request + context will fall back to using :meth:`make_null_session` + in this case. + """ + raise NotImplementedError() + + def save_session( + self, app: Flask, session: SessionMixin, response: Response + ) -> None: + """This is called at the end of each request, after generating + a response, before removing the request context. It is skipped + if :meth:`is_null_session` returns ``True``. + """ + raise NotImplementedError() + + +session_json_serializer = TaggedJSONSerializer() + + +class SecureCookieSessionInterface(SessionInterface): + """The default session interface that stores sessions in signed cookies + through the :mod:`itsdangerous` module. + """ + + #: the salt that should be applied on top of the secret key for the + #: signing of cookie based sessions. + salt = "cookie-session" + #: the hash function to use for the signature. The default is sha1 + digest_method = staticmethod(hashlib.sha1) + #: the name of the itsdangerous supported key derivation. The default + #: is hmac. + key_derivation = "hmac" + #: A python serializer for the payload. The default is a compact + #: JSON derived serializer with support for some extra Python types + #: such as datetime objects or tuples. + serializer = session_json_serializer + session_class = SecureCookieSession + + def get_signing_serializer(self, app: Flask) -> URLSafeTimedSerializer | None: + if not app.secret_key: + return None + signer_kwargs = dict( + key_derivation=self.key_derivation, digest_method=self.digest_method + ) + return URLSafeTimedSerializer( + app.secret_key, + salt=self.salt, + serializer=self.serializer, + signer_kwargs=signer_kwargs, + ) + + def open_session(self, app: Flask, request: Request) -> SecureCookieSession | None: + s = self.get_signing_serializer(app) + if s is None: + return None + val = request.cookies.get(self.get_cookie_name(app)) + if not val: + return self.session_class() + max_age = int(app.permanent_session_lifetime.total_seconds()) + try: + data = s.loads(val, max_age=max_age) + return self.session_class(data) + except BadSignature: + return self.session_class() + + def save_session( + self, app: Flask, session: SessionMixin, response: Response + ) -> None: + name = self.get_cookie_name(app) + domain = self.get_cookie_domain(app) + path = self.get_cookie_path(app) + secure = self.get_cookie_secure(app) + samesite = self.get_cookie_samesite(app) + httponly = self.get_cookie_httponly(app) + + # Add a "Vary: Cookie" header if the session was accessed at all. + if session.accessed: + response.vary.add("Cookie") + + # If the session is modified to be empty, remove the cookie. + # If the session is empty, return without setting the cookie. + if not session: + if session.modified: + response.delete_cookie( + name, + domain=domain, + path=path, + secure=secure, + samesite=samesite, + httponly=httponly, + ) + response.vary.add("Cookie") + + return + + if not self.should_set_cookie(app, session): + return + + expires = self.get_expiration_time(app, session) + val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore + response.set_cookie( + name, + val, # type: ignore + expires=expires, + httponly=httponly, + domain=domain, + path=path, + secure=secure, + samesite=samesite, + ) + response.vary.add("Cookie") diff --git a/venv/lib/python3.12/site-packages/flask/signals.py b/venv/lib/python3.12/site-packages/flask/signals.py new file mode 100644 index 0000000..d79f21f --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/signals.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import typing as t +import warnings + +from blinker import Namespace + +# This namespace is only for signals provided by Flask itself. +_signals = Namespace() + +template_rendered = _signals.signal("template-rendered") +before_render_template = _signals.signal("before-render-template") +request_started = _signals.signal("request-started") +request_finished = _signals.signal("request-finished") +request_tearing_down = _signals.signal("request-tearing-down") +got_request_exception = _signals.signal("got-request-exception") +appcontext_tearing_down = _signals.signal("appcontext-tearing-down") +appcontext_pushed = _signals.signal("appcontext-pushed") +appcontext_popped = _signals.signal("appcontext-popped") +message_flashed = _signals.signal("message-flashed") + + +def __getattr__(name: str) -> t.Any: + if name == "signals_available": + warnings.warn( + "The 'signals_available' attribute is deprecated and will be removed in" + " Flask 2.4. Signals are always available.", + DeprecationWarning, + stacklevel=2, + ) + return True + + raise AttributeError(name) diff --git a/venv/lib/python3.12/site-packages/flask/templating.py b/venv/lib/python3.12/site-packages/flask/templating.py new file mode 100644 index 0000000..769108f --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/templating.py @@ -0,0 +1,220 @@ +from __future__ import annotations + +import typing as t + +from jinja2 import BaseLoader +from jinja2 import Environment as BaseEnvironment +from jinja2 import Template +from jinja2 import TemplateNotFound + +from .globals import _cv_app +from .globals import _cv_request +from .globals import current_app +from .globals import request +from .helpers import stream_with_context +from .signals import before_render_template +from .signals import template_rendered + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + from .scaffold import Scaffold + + +def _default_template_ctx_processor() -> dict[str, t.Any]: + """Default template context processor. Injects `request`, + `session` and `g`. + """ + appctx = _cv_app.get(None) + reqctx = _cv_request.get(None) + rv: dict[str, t.Any] = {} + if appctx is not None: + rv["g"] = appctx.g + if reqctx is not None: + rv["request"] = reqctx.request + rv["session"] = reqctx.session + return rv + + +class Environment(BaseEnvironment): + """Works like a regular Jinja2 environment but has some additional + knowledge of how Flask's blueprint works so that it can prepend the + name of the blueprint to referenced templates if necessary. + """ + + def __init__(self, app: Flask, **options: t.Any) -> None: + if "loader" not in options: + options["loader"] = app.create_global_jinja_loader() + BaseEnvironment.__init__(self, **options) + self.app = app + + +class DispatchingJinjaLoader(BaseLoader): + """A loader that looks for templates in the application and all + the blueprint folders. + """ + + def __init__(self, app: Flask) -> None: + self.app = app + + def get_source( # type: ignore + self, environment: Environment, template: str + ) -> tuple[str, str | None, t.Callable | None]: + if self.app.config["EXPLAIN_TEMPLATE_LOADING"]: + return self._get_source_explained(environment, template) + return self._get_source_fast(environment, template) + + def _get_source_explained( + self, environment: Environment, template: str + ) -> tuple[str, str | None, t.Callable | None]: + attempts = [] + rv: tuple[str, str | None, t.Callable[[], bool] | None] | None + trv: None | (tuple[str, str | None, t.Callable[[], bool] | None]) = None + + for srcobj, loader in self._iter_loaders(template): + try: + rv = loader.get_source(environment, template) + if trv is None: + trv = rv + except TemplateNotFound: + rv = None + attempts.append((loader, srcobj, rv)) + + from .debughelpers import explain_template_loading_attempts + + explain_template_loading_attempts(self.app, template, attempts) + + if trv is not None: + return trv + raise TemplateNotFound(template) + + def _get_source_fast( + self, environment: Environment, template: str + ) -> tuple[str, str | None, t.Callable | None]: + for _srcobj, loader in self._iter_loaders(template): + try: + return loader.get_source(environment, template) + except TemplateNotFound: + continue + raise TemplateNotFound(template) + + def _iter_loaders( + self, template: str + ) -> t.Generator[tuple[Scaffold, BaseLoader], None, None]: + loader = self.app.jinja_loader + if loader is not None: + yield self.app, loader + + for blueprint in self.app.iter_blueprints(): + loader = blueprint.jinja_loader + if loader is not None: + yield blueprint, loader + + def list_templates(self) -> list[str]: + result = set() + loader = self.app.jinja_loader + if loader is not None: + result.update(loader.list_templates()) + + for blueprint in self.app.iter_blueprints(): + loader = blueprint.jinja_loader + if loader is not None: + for template in loader.list_templates(): + result.add(template) + + return list(result) + + +def _render(app: Flask, template: Template, context: dict[str, t.Any]) -> str: + app.update_template_context(context) + before_render_template.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + rv = template.render(context) + template_rendered.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + return rv + + +def render_template( + template_name_or_list: str | Template | list[str | Template], + **context: t.Any, +) -> str: + """Render a template by name with the given context. + + :param template_name_or_list: The name of the template to render. If + a list is given, the first name to exist will be rendered. + :param context: The variables to make available in the template. + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.get_or_select_template(template_name_or_list) + return _render(app, template, context) + + +def render_template_string(source: str, **context: t.Any) -> str: + """Render a template from the given source string with the given + context. + + :param source: The source code of the template to render. + :param context: The variables to make available in the template. + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.from_string(source) + return _render(app, template, context) + + +def _stream( + app: Flask, template: Template, context: dict[str, t.Any] +) -> t.Iterator[str]: + app.update_template_context(context) + before_render_template.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + + def generate() -> t.Iterator[str]: + yield from template.generate(context) + template_rendered.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + + rv = generate() + + # If a request context is active, keep it while generating. + if request: + rv = stream_with_context(rv) + + return rv + + +def stream_template( + template_name_or_list: str | Template | list[str | Template], + **context: t.Any, +) -> t.Iterator[str]: + """Render a template by name with the given context as a stream. + This returns an iterator of strings, which can be used as a + streaming response from a view. + + :param template_name_or_list: The name of the template to render. If + a list is given, the first name to exist will be rendered. + :param context: The variables to make available in the template. + + .. versionadded:: 2.2 + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.get_or_select_template(template_name_or_list) + return _stream(app, template, context) + + +def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]: + """Render a template from the given source string with the given + context as a stream. This returns an iterator of strings, which can + be used as a streaming response from a view. + + :param source: The source code of the template to render. + :param context: The variables to make available in the template. + + .. versionadded:: 2.2 + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.from_string(source) + return _stream(app, template, context) diff --git a/venv/lib/python3.12/site-packages/flask/testing.py b/venv/lib/python3.12/site-packages/flask/testing.py new file mode 100644 index 0000000..69aa785 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/testing.py @@ -0,0 +1,295 @@ +from __future__ import annotations + +import importlib.metadata +import typing as t +from contextlib import contextmanager +from contextlib import ExitStack +from copy import copy +from types import TracebackType +from urllib.parse import urlsplit + +import werkzeug.test +from click.testing import CliRunner +from werkzeug.test import Client +from werkzeug.wrappers import Request as BaseRequest + +from .cli import ScriptInfo +from .sessions import SessionMixin + +if t.TYPE_CHECKING: # pragma: no cover + from werkzeug.test import TestResponse + + from .app import Flask + + +class EnvironBuilder(werkzeug.test.EnvironBuilder): + """An :class:`~werkzeug.test.EnvironBuilder`, that takes defaults from the + application. + + :param app: The Flask application to configure the environment from. + :param path: URL path being requested. + :param base_url: Base URL where the app is being served, which + ``path`` is relative to. If not given, built from + :data:`PREFERRED_URL_SCHEME`, ``subdomain``, + :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`. + :param subdomain: Subdomain name to append to :data:`SERVER_NAME`. + :param url_scheme: Scheme to use instead of + :data:`PREFERRED_URL_SCHEME`. + :param json: If given, this is serialized as JSON and passed as + ``data``. Also defaults ``content_type`` to + ``application/json``. + :param args: other positional arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + :param kwargs: other keyword arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + """ + + def __init__( + self, + app: Flask, + path: str = "/", + base_url: str | None = None, + subdomain: str | None = None, + url_scheme: str | None = None, + *args: t.Any, + **kwargs: t.Any, + ) -> None: + assert not (base_url or subdomain or url_scheme) or ( + base_url is not None + ) != bool( + subdomain or url_scheme + ), 'Cannot pass "subdomain" or "url_scheme" with "base_url".' + + if base_url is None: + http_host = app.config.get("SERVER_NAME") or "localhost" + app_root = app.config["APPLICATION_ROOT"] + + if subdomain: + http_host = f"{subdomain}.{http_host}" + + if url_scheme is None: + url_scheme = app.config["PREFERRED_URL_SCHEME"] + + url = urlsplit(path) + base_url = ( + f"{url.scheme or url_scheme}://{url.netloc or http_host}" + f"/{app_root.lstrip('/')}" + ) + path = url.path + + if url.query: + sep = b"?" if isinstance(url.query, bytes) else "?" + path += sep + url.query + + self.app = app + super().__init__(path, base_url, *args, **kwargs) + + def json_dumps(self, obj: t.Any, **kwargs: t.Any) -> str: # type: ignore + """Serialize ``obj`` to a JSON-formatted string. + + The serialization will be configured according to the config associated + with this EnvironBuilder's ``app``. + """ + return self.app.json.dumps(obj, **kwargs) + + +_werkzeug_version = "" + + +def _get_werkzeug_version() -> str: + global _werkzeug_version + + if not _werkzeug_version: + _werkzeug_version = importlib.metadata.version("werkzeug") + + return _werkzeug_version + + +class FlaskClient(Client): + """Works like a regular Werkzeug test client but has knowledge about + Flask's contexts to defer the cleanup of the request context until + the end of a ``with`` block. For general information about how to + use this class refer to :class:`werkzeug.test.Client`. + + .. versionchanged:: 0.12 + `app.test_client()` includes preset default environment, which can be + set after instantiation of the `app.test_client()` object in + `client.environ_base`. + + Basic usage is outlined in the :doc:`/testing` chapter. + """ + + application: Flask + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + super().__init__(*args, **kwargs) + self.preserve_context = False + self._new_contexts: list[t.ContextManager[t.Any]] = [] + self._context_stack = ExitStack() + self.environ_base = { + "REMOTE_ADDR": "127.0.0.1", + "HTTP_USER_AGENT": f"Werkzeug/{_get_werkzeug_version()}", + } + + @contextmanager + def session_transaction( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Generator[SessionMixin, None, None]: + """When used in combination with a ``with`` statement this opens a + session transaction. This can be used to modify the session that + the test client uses. Once the ``with`` block is left the session is + stored back. + + :: + + with client.session_transaction() as session: + session['value'] = 42 + + Internally this is implemented by going through a temporary test + request context and since session handling could depend on + request variables this function accepts the same arguments as + :meth:`~flask.Flask.test_request_context` which are directly + passed through. + """ + if self._cookies is None: + raise TypeError( + "Cookies are disabled. Create a client with 'use_cookies=True'." + ) + + app = self.application + ctx = app.test_request_context(*args, **kwargs) + self._add_cookies_to_wsgi(ctx.request.environ) + + with ctx: + sess = app.session_interface.open_session(app, ctx.request) + + if sess is None: + raise RuntimeError("Session backend did not open a session.") + + yield sess + resp = app.response_class() + + if app.session_interface.is_null_session(sess): + return + + with ctx: + app.session_interface.save_session(app, sess, resp) + + self._update_cookies_from_response( + ctx.request.host.partition(":")[0], + ctx.request.path, + resp.headers.getlist("Set-Cookie"), + ) + + def _copy_environ(self, other): + out = {**self.environ_base, **other} + + if self.preserve_context: + out["werkzeug.debug.preserve_context"] = self._new_contexts.append + + return out + + def _request_from_builder_args(self, args, kwargs): + kwargs["environ_base"] = self._copy_environ(kwargs.get("environ_base", {})) + builder = EnvironBuilder(self.application, *args, **kwargs) + + try: + return builder.get_request() + finally: + builder.close() + + def open( + self, + *args: t.Any, + buffered: bool = False, + follow_redirects: bool = False, + **kwargs: t.Any, + ) -> TestResponse: + if args and isinstance( + args[0], (werkzeug.test.EnvironBuilder, dict, BaseRequest) + ): + if isinstance(args[0], werkzeug.test.EnvironBuilder): + builder = copy(args[0]) + builder.environ_base = self._copy_environ(builder.environ_base or {}) + request = builder.get_request() + elif isinstance(args[0], dict): + request = EnvironBuilder.from_environ( + args[0], app=self.application, environ_base=self._copy_environ({}) + ).get_request() + else: + # isinstance(args[0], BaseRequest) + request = copy(args[0]) + request.environ = self._copy_environ(request.environ) + else: + # request is None + request = self._request_from_builder_args(args, kwargs) + + # Pop any previously preserved contexts. This prevents contexts + # from being preserved across redirects or multiple requests + # within a single block. + self._context_stack.close() + + response = super().open( + request, + buffered=buffered, + follow_redirects=follow_redirects, + ) + response.json_module = self.application.json # type: ignore[assignment] + + # Re-push contexts that were preserved during the request. + while self._new_contexts: + cm = self._new_contexts.pop() + self._context_stack.enter_context(cm) + + return response + + def __enter__(self) -> FlaskClient: + if self.preserve_context: + raise RuntimeError("Cannot nest client invocations") + self.preserve_context = True + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.preserve_context = False + self._context_stack.close() + + +class FlaskCliRunner(CliRunner): + """A :class:`~click.testing.CliRunner` for testing a Flask app's + CLI commands. Typically created using + :meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`. + """ + + def __init__(self, app: Flask, **kwargs: t.Any) -> None: + self.app = app + super().__init__(**kwargs) + + def invoke( # type: ignore + self, cli: t.Any = None, args: t.Any = None, **kwargs: t.Any + ) -> t.Any: + """Invokes a CLI command in an isolated environment. See + :meth:`CliRunner.invoke ` for + full method documentation. See :ref:`testing-cli` for examples. + + If the ``obj`` argument is not given, passes an instance of + :class:`~flask.cli.ScriptInfo` that knows how to load the Flask + app being tested. + + :param cli: Command object to invoke. Default is the app's + :attr:`~flask.app.Flask.cli` group. + :param args: List of strings to invoke the command with. + + :return: a :class:`~click.testing.Result` object. + """ + if cli is None: + cli = self.app.cli # type: ignore + + if "obj" not in kwargs: + kwargs["obj"] = ScriptInfo(create_app=lambda: self.app) + + return super().invoke(cli, args, **kwargs) diff --git a/venv/lib/python3.12/site-packages/flask/typing.py b/venv/lib/python3.12/site-packages/flask/typing.py new file mode 100644 index 0000000..50aef7f --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/typing.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +import typing as t + +if t.TYPE_CHECKING: # pragma: no cover + from _typeshed.wsgi import WSGIApplication # noqa: F401 + from werkzeug.datastructures import Headers # noqa: F401 + from werkzeug.wrappers import Response # noqa: F401 + +# The possible types that are directly convertible or are a Response object. +ResponseValue = t.Union[ + "Response", + str, + bytes, + t.List[t.Any], + # Only dict is actually accepted, but Mapping allows for TypedDict. + t.Mapping[str, t.Any], + t.Iterator[str], + t.Iterator[bytes], +] + +# the possible types for an individual HTTP header +# This should be a Union, but mypy doesn't pass unless it's a TypeVar. +HeaderValue = t.Union[str, t.List[str], t.Tuple[str, ...]] + +# the possible types for HTTP headers +HeadersValue = t.Union[ + "Headers", + t.Mapping[str, HeaderValue], + t.Sequence[t.Tuple[str, HeaderValue]], +] + +# The possible types returned by a route function. +ResponseReturnValue = t.Union[ + ResponseValue, + t.Tuple[ResponseValue, HeadersValue], + t.Tuple[ResponseValue, int], + t.Tuple[ResponseValue, int, HeadersValue], + "WSGIApplication", +] + +# Allow any subclass of werkzeug.Response, such as the one from Flask, +# as a callback argument. Using werkzeug.Response directly makes a +# callback annotated with flask.Response fail type checking. +ResponseClass = t.TypeVar("ResponseClass", bound="Response") + +AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named +AfterRequestCallable = t.Union[ + t.Callable[[ResponseClass], ResponseClass], + t.Callable[[ResponseClass], t.Awaitable[ResponseClass]], +] +BeforeFirstRequestCallable = t.Union[ + t.Callable[[], None], t.Callable[[], t.Awaitable[None]] +] +BeforeRequestCallable = t.Union[ + t.Callable[[], t.Optional[ResponseReturnValue]], + t.Callable[[], t.Awaitable[t.Optional[ResponseReturnValue]]], +] +ShellContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]] +TeardownCallable = t.Union[ + t.Callable[[t.Optional[BaseException]], None], + t.Callable[[t.Optional[BaseException]], t.Awaitable[None]], +] +TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]] +TemplateFilterCallable = t.Callable[..., t.Any] +TemplateGlobalCallable = t.Callable[..., t.Any] +TemplateTestCallable = t.Callable[..., bool] +URLDefaultCallable = t.Callable[[str, dict], None] +URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None] + +# This should take Exception, but that either breaks typing the argument +# with a specific exception, or decorating multiple times with different +# exceptions (and using a union type on the argument). +# https://github.com/pallets/flask/issues/4095 +# https://github.com/pallets/flask/issues/4295 +# https://github.com/pallets/flask/issues/4297 +ErrorHandlerCallable = t.Callable[[t.Any], ResponseReturnValue] + +RouteCallable = t.Union[ + t.Callable[..., ResponseReturnValue], + t.Callable[..., t.Awaitable[ResponseReturnValue]], +] diff --git a/venv/lib/python3.12/site-packages/flask/views.py b/venv/lib/python3.12/site-packages/flask/views.py new file mode 100644 index 0000000..c7a2b62 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/views.py @@ -0,0 +1,190 @@ +from __future__ import annotations + +import typing as t + +from . import typing as ft +from .globals import current_app +from .globals import request + + +http_method_funcs = frozenset( + ["get", "post", "head", "options", "delete", "put", "trace", "patch"] +) + + +class View: + """Subclass this class and override :meth:`dispatch_request` to + create a generic class-based view. Call :meth:`as_view` to create a + view function that creates an instance of the class with the given + arguments and calls its ``dispatch_request`` method with any URL + variables. + + See :doc:`views` for a detailed guide. + + .. code-block:: python + + class Hello(View): + init_every_request = False + + def dispatch_request(self, name): + return f"Hello, {name}!" + + app.add_url_rule( + "/hello/", view_func=Hello.as_view("hello") + ) + + Set :attr:`methods` on the class to change what methods the view + accepts. + + Set :attr:`decorators` on the class to apply a list of decorators to + the generated view function. Decorators applied to the class itself + will not be applied to the generated view function! + + Set :attr:`init_every_request` to ``False`` for efficiency, unless + you need to store request-global data on ``self``. + """ + + #: The methods this view is registered for. Uses the same default + #: (``["GET", "HEAD", "OPTIONS"]``) as ``route`` and + #: ``add_url_rule`` by default. + methods: t.ClassVar[t.Collection[str] | None] = None + + #: Control whether the ``OPTIONS`` method is handled automatically. + #: Uses the same default (``True``) as ``route`` and + #: ``add_url_rule`` by default. + provide_automatic_options: t.ClassVar[bool | None] = None + + #: A list of decorators to apply, in order, to the generated view + #: function. Remember that ``@decorator`` syntax is applied bottom + #: to top, so the first decorator in the list would be the bottom + #: decorator. + #: + #: .. versionadded:: 0.8 + decorators: t.ClassVar[list[t.Callable]] = [] + + #: Create a new instance of this view class for every request by + #: default. If a view subclass sets this to ``False``, the same + #: instance is used for every request. + #: + #: A single instance is more efficient, especially if complex setup + #: is done during init. However, storing data on ``self`` is no + #: longer safe across requests, and :data:`~flask.g` should be used + #: instead. + #: + #: .. versionadded:: 2.2 + init_every_request: t.ClassVar[bool] = True + + def dispatch_request(self) -> ft.ResponseReturnValue: + """The actual view function behavior. Subclasses must override + this and return a valid response. Any variables from the URL + rule are passed as keyword arguments. + """ + raise NotImplementedError() + + @classmethod + def as_view( + cls, name: str, *class_args: t.Any, **class_kwargs: t.Any + ) -> ft.RouteCallable: + """Convert the class into a view function that can be registered + for a route. + + By default, the generated view will create a new instance of the + view class for every request and call its + :meth:`dispatch_request` method. If the view class sets + :attr:`init_every_request` to ``False``, the same instance will + be used for every request. + + Except for ``name``, all other arguments passed to this method + are forwarded to the view class ``__init__`` method. + + .. versionchanged:: 2.2 + Added the ``init_every_request`` class attribute. + """ + if cls.init_every_request: + + def view(**kwargs: t.Any) -> ft.ResponseReturnValue: + self = view.view_class( # type: ignore[attr-defined] + *class_args, **class_kwargs + ) + return current_app.ensure_sync(self.dispatch_request)(**kwargs) + + else: + self = cls(*class_args, **class_kwargs) + + def view(**kwargs: t.Any) -> ft.ResponseReturnValue: + return current_app.ensure_sync(self.dispatch_request)(**kwargs) + + if cls.decorators: + view.__name__ = name + view.__module__ = cls.__module__ + for decorator in cls.decorators: + view = decorator(view) + + # We attach the view class to the view function for two reasons: + # first of all it allows us to easily figure out what class-based + # view this thing came from, secondly it's also used for instantiating + # the view class so you can actually replace it with something else + # for testing purposes and debugging. + view.view_class = cls # type: ignore + view.__name__ = name + view.__doc__ = cls.__doc__ + view.__module__ = cls.__module__ + view.methods = cls.methods # type: ignore + view.provide_automatic_options = cls.provide_automatic_options # type: ignore + return view + + +class MethodView(View): + """Dispatches request methods to the corresponding instance methods. + For example, if you implement a ``get`` method, it will be used to + handle ``GET`` requests. + + This can be useful for defining a REST API. + + :attr:`methods` is automatically set based on the methods defined on + the class. + + See :doc:`views` for a detailed guide. + + .. code-block:: python + + class CounterAPI(MethodView): + def get(self): + return str(session.get("counter", 0)) + + def post(self): + session["counter"] = session.get("counter", 0) + 1 + return redirect(url_for("counter")) + + app.add_url_rule( + "/counter", view_func=CounterAPI.as_view("counter") + ) + """ + + def __init_subclass__(cls, **kwargs: t.Any) -> None: + super().__init_subclass__(**kwargs) + + if "methods" not in cls.__dict__: + methods = set() + + for base in cls.__bases__: + if getattr(base, "methods", None): + methods.update(base.methods) # type: ignore[attr-defined] + + for key in http_method_funcs: + if hasattr(cls, key): + methods.add(key.upper()) + + if methods: + cls.methods = methods + + def dispatch_request(self, **kwargs: t.Any) -> ft.ResponseReturnValue: + meth = getattr(self, request.method.lower(), None) + + # If the request method is HEAD and we don't have a handler for it + # retry with GET. + if meth is None and request.method == "HEAD": + meth = getattr(self, "get", None) + + assert meth is not None, f"Unimplemented method {request.method!r}" + return current_app.ensure_sync(meth)(**kwargs) diff --git a/venv/lib/python3.12/site-packages/flask/wrappers.py b/venv/lib/python3.12/site-packages/flask/wrappers.py new file mode 100644 index 0000000..ef7aa38 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask/wrappers.py @@ -0,0 +1,173 @@ +from __future__ import annotations + +import typing as t + +from werkzeug.exceptions import BadRequest +from werkzeug.wrappers import Request as RequestBase +from werkzeug.wrappers import Response as ResponseBase + +from . import json +from .globals import current_app +from .helpers import _split_blueprint_path + +if t.TYPE_CHECKING: # pragma: no cover + from werkzeug.routing import Rule + + +class Request(RequestBase): + """The request object used by default in Flask. Remembers the + matched endpoint and view arguments. + + It is what ends up as :class:`~flask.request`. If you want to replace + the request object used you can subclass this and set + :attr:`~flask.Flask.request_class` to your subclass. + + The request object is a :class:`~werkzeug.wrappers.Request` subclass and + provides all of the attributes Werkzeug defines plus a few Flask + specific ones. + """ + + json_module: t.Any = json + + #: The internal URL rule that matched the request. This can be + #: useful to inspect which methods are allowed for the URL from + #: a before/after handler (``request.url_rule.methods``) etc. + #: Though if the request's method was invalid for the URL rule, + #: the valid list is available in ``routing_exception.valid_methods`` + #: instead (an attribute of the Werkzeug exception + #: :exc:`~werkzeug.exceptions.MethodNotAllowed`) + #: because the request was never internally bound. + #: + #: .. versionadded:: 0.6 + url_rule: Rule | None = None + + #: A dict of view arguments that matched the request. If an exception + #: happened when matching, this will be ``None``. + view_args: dict[str, t.Any] | None = None + + #: If matching the URL failed, this is the exception that will be + #: raised / was raised as part of the request handling. This is + #: usually a :exc:`~werkzeug.exceptions.NotFound` exception or + #: something similar. + routing_exception: Exception | None = None + + @property + def max_content_length(self) -> int | None: # type: ignore + """Read-only view of the ``MAX_CONTENT_LENGTH`` config key.""" + if current_app: + return current_app.config["MAX_CONTENT_LENGTH"] + else: + return None + + @property + def endpoint(self) -> str | None: + """The endpoint that matched the request URL. + + This will be ``None`` if matching failed or has not been + performed yet. + + This in combination with :attr:`view_args` can be used to + reconstruct the same URL or a modified URL. + """ + if self.url_rule is not None: + return self.url_rule.endpoint + + return None + + @property + def blueprint(self) -> str | None: + """The registered name of the current blueprint. + + This will be ``None`` if the endpoint is not part of a + blueprint, or if URL matching failed or has not been performed + yet. + + This does not necessarily match the name the blueprint was + created with. It may have been nested, or registered with a + different name. + """ + endpoint = self.endpoint + + if endpoint is not None and "." in endpoint: + return endpoint.rpartition(".")[0] + + return None + + @property + def blueprints(self) -> list[str]: + """The registered names of the current blueprint upwards through + parent blueprints. + + This will be an empty list if there is no current blueprint, or + if URL matching failed. + + .. versionadded:: 2.0.1 + """ + name = self.blueprint + + if name is None: + return [] + + return _split_blueprint_path(name) + + def _load_form_data(self) -> None: + super()._load_form_data() + + # In debug mode we're replacing the files multidict with an ad-hoc + # subclass that raises a different error for key errors. + if ( + current_app + and current_app.debug + and self.mimetype != "multipart/form-data" + and not self.files + ): + from .debughelpers import attach_enctype_error_multidict + + attach_enctype_error_multidict(self) + + def on_json_loading_failed(self, e: ValueError | None) -> t.Any: + try: + return super().on_json_loading_failed(e) + except BadRequest as e: + if current_app and current_app.debug: + raise + + raise BadRequest() from e + + +class Response(ResponseBase): + """The response object that is used by default in Flask. Works like the + response object from Werkzeug but is set to have an HTML mimetype by + default. Quite often you don't have to create this object yourself because + :meth:`~flask.Flask.make_response` will take care of that for you. + + If you want to replace the response object used you can subclass this and + set :attr:`~flask.Flask.response_class` to your subclass. + + .. versionchanged:: 1.0 + JSON support is added to the response, like the request. This is useful + when testing to get the test client response data as JSON. + + .. versionchanged:: 1.0 + + Added :attr:`max_cookie_size`. + """ + + default_mimetype: str | None = "text/html" + + json_module = json + + autocorrect_location_header = False + + @property + def max_cookie_size(self) -> int: # type: ignore + """Read-only view of the :data:`MAX_COOKIE_SIZE` config key. + + See :attr:`~werkzeug.wrappers.Response.max_cookie_size` in + Werkzeug's docs. + """ + if current_app: + return current_app.config["MAX_COOKIE_SIZE"] + + # return Werkzeug's default when not in an app context + return super().max_cookie_size diff --git a/venv/lib/python3.12/site-packages/flask_socketio/__init__.py b/venv/lib/python3.12/site-packages/flask_socketio/__init__.py new file mode 100644 index 0000000..c065ae3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask_socketio/__init__.py @@ -0,0 +1,1117 @@ +from functools import wraps +import os +import sys + +# make sure gevent-socketio is not installed, as it conflicts with +# python-socketio +gevent_socketio_found = True +try: + from socketio import socketio_manage # noqa: F401 +except ImportError: + gevent_socketio_found = False +if gevent_socketio_found: + print('The gevent-socketio package is incompatible with this version of ' + 'the Flask-SocketIO extension. Please uninstall it, and then ' + 'install the latest version of python-socketio in its place.') + sys.exit(1) + +import flask +from flask import has_request_context, json as flask_json +from flask.sessions import SessionMixin +import socketio +from socketio.exceptions import ConnectionRefusedError # noqa: F401 +from werkzeug.debug import DebuggedApplication +from werkzeug._reloader import run_with_reloader + +from .namespace import Namespace +from .test_client import SocketIOTestClient + + +class _SocketIOMiddleware(socketio.WSGIApp): + """This WSGI middleware simply exposes the Flask application in the WSGI + environment before executing the request. + """ + def __init__(self, socketio_app, flask_app, socketio_path='socket.io'): + self.flask_app = flask_app + super(_SocketIOMiddleware, self).__init__(socketio_app, + flask_app.wsgi_app, + socketio_path=socketio_path) + + def __call__(self, environ, start_response): + environ = environ.copy() + environ['flask.app'] = self.flask_app + return super(_SocketIOMiddleware, self).__call__(environ, + start_response) + + +class _ManagedSession(dict, SessionMixin): + """This class is used for user sessions that are managed by + Flask-SocketIO. It is simple dict, expanded with the Flask session + attributes.""" + pass + + +class SocketIO(object): + """Create a Flask-SocketIO server. + + :param app: The flask application instance. If the application instance + isn't known at the time this class is instantiated, then call + ``socketio.init_app(app)`` once the application instance is + available. + :param manage_session: If set to ``True``, this extension manages the user + session for Socket.IO events. If set to ``False``, + Flask's own session management is used. When using + Flask's cookie based sessions it is recommended that + you leave this set to the default of ``True``. When + using server-side sessions, a ``False`` setting + enables sharing the user session between HTTP routes + and Socket.IO events. + :param message_queue: A connection URL for a message queue service the + server can use for multi-process communication. A + message queue is not required when using a single + server process. + :param channel: The channel name, when using a message queue. If a channel + isn't specified, a default channel will be used. If + multiple clusters of SocketIO processes need to use the + same message queue without interfering with each other, + then each cluster should use a different channel. + :param path: The path where the Socket.IO server is exposed. Defaults to + ``'socket.io'``. Leave this as is unless you know what you are + doing. + :param resource: Alias to ``path``. + :param kwargs: Socket.IO and Engine.IO server options. + + The Socket.IO server options are detailed below: + + :param client_manager: The client manager instance that will manage the + client list. When this is omitted, the client list + is stored in an in-memory structure, so the use of + multiple connected servers is not possible. In most + cases, this argument does not need to be set + explicitly. + :param logger: To enable logging set to ``True`` or pass a logger object to + use. To disable logging set to ``False``. The default is + ``False``. Note that fatal errors will be logged even when + ``logger`` is ``False``. + :param json: An alternative json module to use for encoding and decoding + packets. Custom json modules must have ``dumps`` and ``loads`` + functions that are compatible with the standard library + versions. To use the same json encoder and decoder as a Flask + application, use ``flask.json``. + :param async_handlers: If set to ``True``, event handlers for a client are + executed in separate threads. To run handlers for a + client synchronously, set to ``False``. The default + is ``True``. + :param always_connect: When set to ``False``, new connections are + provisory until the connect handler returns + something other than ``False``, at which point they + are accepted. When set to ``True``, connections are + immediately accepted, and then if the connect + handler returns ``False`` a disconnect is issued. + Set to ``True`` if you need to emit events from the + connect handler and your client is confused when it + receives events before the connection acceptance. + In any other case use the default of ``False``. + + The Engine.IO server configuration supports the following settings: + + :param async_mode: The asynchronous model to use. See the Deployment + section in the documentation for a description of the + available options. Valid async modes are ``threading``, + ``eventlet``, ``gevent`` and ``gevent_uwsgi``. If this + argument is not given, ``eventlet`` is tried first, then + ``gevent_uwsgi``, then ``gevent``, and finally + ``threading``. The first async mode that has all its + dependencies installed is then one that is chosen. + :param ping_interval: The interval in seconds at which the server pings + the client. The default is 25 seconds. For advanced + control, a two element tuple can be given, where + the first number is the ping interval and the second + is a grace period added by the server. + :param ping_timeout: The time in seconds that the client waits for the + server to respond before disconnecting. The default + is 5 seconds. + :param max_http_buffer_size: The maximum size of a message when using the + polling transport. The default is 1,000,000 + bytes. + :param allow_upgrades: Whether to allow transport upgrades or not. The + default is ``True``. + :param http_compression: Whether to compress packages when using the + polling transport. The default is ``True``. + :param compression_threshold: Only compress messages when their byte size + is greater than this value. The default is + 1024 bytes. + :param cookie: If set to a string, it is the name of the HTTP cookie the + server sends back to the client containing the client + session id. If set to a dictionary, the ``'name'`` key + contains the cookie name and other keys define cookie + attributes, where the value of each attribute can be a + string, a callable with no arguments, or a boolean. If set + to ``None`` (the default), a cookie is not sent to the + client. + :param cors_allowed_origins: Origin or list of origins that are allowed to + connect to this server. Only the same origin + is allowed by default. Set this argument to + ``'*'`` to allow all origins, or to ``[]`` to + disable CORS handling. + :param cors_credentials: Whether credentials (cookies, authentication) are + allowed in requests to this server. The default is + ``True``. + :param monitor_clients: If set to ``True``, a background task will ensure + inactive clients are closed. Set to ``False`` to + disable the monitoring task (not recommended). The + default is ``True``. + :param engineio_logger: To enable Engine.IO logging set to ``True`` or pass + a logger object to use. To disable logging set to + ``False``. The default is ``False``. Note that + fatal errors are logged even when + ``engineio_logger`` is ``False``. + """ + + def __init__(self, app=None, **kwargs): + self.server = None + self.server_options = {} + self.wsgi_server = None + self.handlers = [] + self.namespace_handlers = [] + self.exception_handlers = {} + self.default_exception_handler = None + self.manage_session = True + # We can call init_app when: + # - we were given the Flask app instance (standard initialization) + # - we were not given the app, but we were given a message_queue + # (standard initialization for auxiliary process) + # In all other cases we collect the arguments and assume the client + # will call init_app from an app factory function. + if app is not None or 'message_queue' in kwargs: + self.init_app(app, **kwargs) + else: + self.server_options.update(kwargs) + + def init_app(self, app, **kwargs): + if app is not None: + if not hasattr(app, 'extensions'): + app.extensions = {} # pragma: no cover + app.extensions['socketio'] = self + self.server_options.update(kwargs) + self.manage_session = self.server_options.pop('manage_session', + self.manage_session) + + if 'client_manager' not in kwargs: + url = self.server_options.get('message_queue', None) + channel = self.server_options.pop('channel', 'flask-socketio') + write_only = app is None + if url: + if url.startswith(('redis://', "rediss://")): + queue_class = socketio.RedisManager + elif url.startswith(('kafka://')): + queue_class = socketio.KafkaManager + elif url.startswith('zmq'): + queue_class = socketio.ZmqManager + else: + queue_class = socketio.KombuManager + queue = queue_class(url, channel=channel, + write_only=write_only) + self.server_options['client_manager'] = queue + + if 'json' in self.server_options and \ + self.server_options['json'] == flask_json: + # flask's json module is tricky to use because its output + # changes when it is invoked inside or outside the app context + # so here to prevent any ambiguities we replace it with wrappers + # that ensure that the app context is always present + class FlaskSafeJSON(object): + @staticmethod + def dumps(*args, **kwargs): + with app.app_context(): + return flask_json.dumps(*args, **kwargs) + + @staticmethod + def loads(*args, **kwargs): + with app.app_context(): + return flask_json.loads(*args, **kwargs) + + self.server_options['json'] = FlaskSafeJSON + + resource = self.server_options.pop('path', None) or \ + self.server_options.pop('resource', None) or 'socket.io' + if resource.startswith('/'): + resource = resource[1:] + if os.environ.get('FLASK_RUN_FROM_CLI'): + if self.server_options.get('async_mode') is None: + self.server_options['async_mode'] = 'threading' + self.server = socketio.Server(**self.server_options) + self.async_mode = self.server.async_mode + for handler in self.handlers: + self.server.on(handler[0], handler[1], namespace=handler[2]) + for namespace_handler in self.namespace_handlers: + self.server.register_namespace(namespace_handler) + + if app is not None: + # here we attach the SocketIO middleware to the SocketIO object so + # it can be referenced later if debug middleware needs to be + # inserted + self.sockio_mw = _SocketIOMiddleware(self.server, app, + socketio_path=resource) + app.wsgi_app = self.sockio_mw + + def on(self, message, namespace=None): + """Decorator to register a SocketIO event handler. + + This decorator must be applied to SocketIO event handlers. Example:: + + @socketio.on('my event', namespace='/chat') + def handle_my_custom_event(json): + print('received json: ' + str(json)) + + :param message: The name of the event. This is normally a user defined + string, but a few event names are already defined. Use + ``'message'`` to define a handler that takes a string + payload, ``'json'`` to define a handler that takes a + JSON blob payload, ``'connect'`` or ``'disconnect'`` + to create handlers for connection and disconnection + events. + :param namespace: The namespace on which the handler is to be + registered. Defaults to the global namespace. + """ + namespace = namespace or '/' + + def decorator(handler): + @wraps(handler) + def _handler(sid, *args): + return self._handle_event(handler, message, namespace, sid, + *args) + + if self.server: + self.server.on(message, _handler, namespace=namespace) + else: + self.handlers.append((message, _handler, namespace)) + return handler + return decorator + + def on_error(self, namespace=None): + """Decorator to define a custom error handler for SocketIO events. + + This decorator can be applied to a function that acts as an error + handler for a namespace. This handler will be invoked when a SocketIO + event handler raises an exception. The handler function must accept one + argument, which is the exception raised. Example:: + + @socketio.on_error(namespace='/chat') + def chat_error_handler(e): + print('An error has occurred: ' + str(e)) + + :param namespace: The namespace for which to register the error + handler. Defaults to the global namespace. + """ + namespace = namespace or '/' + + def decorator(exception_handler): + if not callable(exception_handler): + raise ValueError('exception_handler must be callable') + self.exception_handlers[namespace] = exception_handler + return exception_handler + return decorator + + def on_error_default(self, exception_handler): + """Decorator to define a default error handler for SocketIO events. + + This decorator can be applied to a function that acts as a default + error handler for any namespaces that do not have a specific handler. + Example:: + + @socketio.on_error_default + def error_handler(e): + print('An error has occurred: ' + str(e)) + """ + if not callable(exception_handler): + raise ValueError('exception_handler must be callable') + self.default_exception_handler = exception_handler + return exception_handler + + def on_event(self, message, handler, namespace=None): + """Register a SocketIO event handler. + + ``on_event`` is the non-decorator version of ``'on'``. + + Example:: + + def on_foo_event(json): + print('received json: ' + str(json)) + + socketio.on_event('my event', on_foo_event, namespace='/chat') + + :param message: The name of the event. This is normally a user defined + string, but a few event names are already defined. Use + ``'message'`` to define a handler that takes a string + payload, ``'json'`` to define a handler that takes a + JSON blob payload, ``'connect'`` or ``'disconnect'`` + to create handlers for connection and disconnection + events. + :param handler: The function that handles the event. + :param namespace: The namespace on which the handler is to be + registered. Defaults to the global namespace. + """ + self.on(message, namespace=namespace)(handler) + + def event(self, *args, **kwargs): + """Decorator to register an event handler. + + This is a simplified version of the ``on()`` method that takes the + event name from the decorated function. + + Example usage:: + + @socketio.event + def my_event(data): + print('Received data: ', data) + + The above example is equivalent to:: + + @socketio.on('my_event') + def my_event(data): + print('Received data: ', data) + + A custom namespace can be given as an argument to the decorator:: + + @socketio.event(namespace='/test') + def my_event(data): + print('Received data: ', data) + """ + if len(args) == 1 and len(kwargs) == 0 and callable(args[0]): + # the decorator was invoked without arguments + # args[0] is the decorated function + return self.on(args[0].__name__)(args[0]) + else: + # the decorator was invoked with arguments + def set_handler(handler): + return self.on(handler.__name__, *args, **kwargs)(handler) + + return set_handler + + def on_namespace(self, namespace_handler): + if not isinstance(namespace_handler, Namespace): + raise ValueError('Not a namespace instance.') + namespace_handler._set_socketio(self) + if self.server: + self.server.register_namespace(namespace_handler) + else: + self.namespace_handlers.append(namespace_handler) + + def emit(self, event, *args, **kwargs): + """Emit a server generated SocketIO event. + + This function emits a SocketIO event to one or more connected clients. + A JSON blob can be attached to the event as payload. This function can + be used outside of a SocketIO event context, so it is appropriate to + use when the server is the originator of an event, outside of any + client context, such as in a regular HTTP request handler or a + background task. Example:: + + @app.route('/ping') + def ping(): + socketio.emit('ping event', {'data': 42}, namespace='/chat') + + :param event: The name of the user event to emit. + :param args: A dictionary with the JSON data to send as payload. + :param namespace: The namespace under which the message is to be sent. + Defaults to the global namespace. + :param to: Send the message to all the users in the given room, or to + the user with the given session ID. If this parameter is not + included, the event is sent to all connected users. + :param include_self: ``True`` to include the sender when broadcasting + or addressing a room, or ``False`` to send to + everyone but the sender. + :param skip_sid: The session id of a client to ignore when broadcasting + or addressing a room. This is typically set to the + originator of the message, so that everyone except + that client receive the message. To skip multiple sids + pass a list. + :param callback: If given, this function will be called to acknowledge + that the client has received the message. The + arguments that will be passed to the function are + those provided by the client. Callback functions can + only be used when addressing an individual client. + """ + namespace = kwargs.pop('namespace', '/') + to = kwargs.pop('to', None) or kwargs.pop('room', None) + include_self = kwargs.pop('include_self', True) + skip_sid = kwargs.pop('skip_sid', None) + if not include_self and not skip_sid: + skip_sid = flask.request.sid + callback = kwargs.pop('callback', None) + if callback: + # wrap the callback so that it sets app app and request contexts + sid = None + original_callback = callback + original_namespace = namespace + if has_request_context(): + sid = getattr(flask.request, 'sid', None) + original_namespace = getattr(flask.request, 'namespace', None) + + def _callback_wrapper(*args): + return self._handle_event(original_callback, None, + original_namespace, sid, *args) + + if sid: + # the callback wrapper above will install a request context + # before invoking the original callback + # we only use it if the emit was issued from a Socket.IO + # populated request context (i.e. request.sid is defined) + callback = _callback_wrapper + self.server.emit(event, *args, namespace=namespace, to=to, + skip_sid=skip_sid, callback=callback, **kwargs) + + def call(self, event, *args, **kwargs): # pragma: no cover + """Emit a SocketIO event and wait for the response. + + This method issues an emit with a callback and waits for the callback + to be invoked by the client before returning. If the callback isn’t + invoked before the timeout, then a TimeoutError exception is raised. If + the Socket.IO connection drops during the wait, this method still waits + until the specified timeout. Example:: + + def get_status(client, data): + status = call('status', {'data': data}, to=client) + + :param event: The name of the user event to emit. + :param args: A dictionary with the JSON data to send as payload. + :param namespace: The namespace under which the message is to be sent. + Defaults to the global namespace. + :param to: The session ID of the recipient client. + :param timeout: The waiting timeout. If the timeout is reached before + the client acknowledges the event, then a + ``TimeoutError`` exception is raised. The default is 60 + seconds. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + client directly, without going through the queue. + This is more efficient, but only works when a + single server process is used, or when there is a + single addressee. It is recommended to always + leave this parameter with its default value of + ``False``. + """ + namespace = kwargs.pop('namespace', '/') + to = kwargs.pop('to', None) or kwargs.pop('room', None) + return self.server.call(event, *args, namespace=namespace, to=to, + **kwargs) + + def send(self, data, json=False, namespace=None, to=None, + callback=None, include_self=True, skip_sid=None, **kwargs): + """Send a server-generated SocketIO message. + + This function sends a simple SocketIO message to one or more connected + clients. The message can be a string or a JSON blob. This is a simpler + version of ``emit()``, which should be preferred. This function can be + used outside of a SocketIO event context, so it is appropriate to use + when the server is the originator of an event. + + :param data: The message to send, either a string or a JSON blob. + :param json: ``True`` if ``message`` is a JSON blob, ``False`` + otherwise. + :param namespace: The namespace under which the message is to be sent. + Defaults to the global namespace. + :param to: Send the message to all the users in the given room, or to + the user with the given session ID. If this parameter is not + included, the event is sent to all connected users. + :param include_self: ``True`` to include the sender when broadcasting + or addressing a room, or ``False`` to send to + everyone but the sender. + :param skip_sid: The session id of a client to ignore when broadcasting + or addressing a room. This is typically set to the + originator of the message, so that everyone except + that client receive the message. To skip multiple sids + pass a list. + :param callback: If given, this function will be called to acknowledge + that the client has received the message. The + arguments that will be passed to the function are + those provided by the client. Callback functions can + only be used when addressing an individual client. + """ + skip_sid = flask.request.sid if not include_self else skip_sid + if json: + self.emit('json', data, namespace=namespace, to=to, + skip_sid=skip_sid, callback=callback, **kwargs) + else: + self.emit('message', data, namespace=namespace, to=to, + skip_sid=skip_sid, callback=callback, **kwargs) + + def close_room(self, room, namespace=None): + """Close a room. + + This function removes any users that are in the given room and then + deletes the room from the server. This function can be used outside + of a SocketIO event context. + + :param room: The name of the room to close. + :param namespace: The namespace under which the room exists. Defaults + to the global namespace. + """ + self.server.close_room(room, namespace) + + def run(self, app, host=None, port=None, **kwargs): # pragma: no cover + """Run the SocketIO web server. + + :param app: The Flask application instance. + :param host: The hostname or IP address for the server to listen on. + Defaults to 127.0.0.1. + :param port: The port number for the server to listen on. Defaults to + 5000. + :param debug: ``True`` to start the server in debug mode, ``False`` to + start in normal mode. + :param use_reloader: ``True`` to enable the Flask reloader, ``False`` + to disable it. + :param reloader_options: A dictionary with options that are passed to + the Flask reloader, such as ``extra_files``, + ``reloader_type``, etc. + :param extra_files: A list of additional files that the Flask + reloader should watch. Defaults to ``None``. + Deprecated, use ``reloader_options`` instead. + :param log_output: If ``True``, the server logs all incoming + connections. If ``False`` logging is disabled. + Defaults to ``True`` in debug mode, ``False`` + in normal mode. Unused when the threading async + mode is used. + :param allow_unsafe_werkzeug: Set to ``True`` to allow the use of the + Werkzeug web server in a production + setting. Default is ``False``. Set to + ``True`` at your own risk. + :param kwargs: Additional web server options. The web server options + are specific to the server used in each of the supported + async modes. Note that options provided here will + not be seen when using an external web server such + as gunicorn, since this method is not called in that + case. + """ + if host is None: + host = '127.0.0.1' + if port is None: + server_name = app.config['SERVER_NAME'] + if server_name and ':' in server_name: + port = int(server_name.rsplit(':', 1)[1]) + else: + port = 5000 + + debug = kwargs.pop('debug', app.debug) + log_output = kwargs.pop('log_output', debug) + use_reloader = kwargs.pop('use_reloader', debug) + extra_files = kwargs.pop('extra_files', None) + reloader_options = kwargs.pop('reloader_options', {}) + if extra_files: + reloader_options['extra_files'] = extra_files + + app.debug = debug + if app.debug and self.server.eio.async_mode != 'threading': + # put the debug middleware between the SocketIO middleware + # and the Flask application instance + # + # mw1 mw2 mw3 Flask app + # o ---- o ---- o ---- o + # / + # o Flask-SocketIO + # \ middleware + # o + # Flask-SocketIO WebSocket handler + # + # BECOMES + # + # dbg-mw mw1 mw2 mw3 Flask app + # o ---- o ---- o ---- o ---- o + # / + # o Flask-SocketIO + # \ middleware + # o + # Flask-SocketIO WebSocket handler + # + self.sockio_mw.wsgi_app = DebuggedApplication( + self.sockio_mw.wsgi_app, evalex=True) + + allow_unsafe_werkzeug = kwargs.pop('allow_unsafe_werkzeug', False) + if self.server.eio.async_mode == 'threading': + try: + import simple_websocket # noqa: F401 + except ImportError: + from werkzeug._internal import _log + _log('warning', 'WebSocket transport not available. Install ' + 'simple-websocket for improved performance.') + if not sys.stdin or not sys.stdin.isatty(): # pragma: no cover + if not allow_unsafe_werkzeug: + raise RuntimeError('The Werkzeug web server is not ' + 'designed to run in production. Pass ' + 'allow_unsafe_werkzeug=True to the ' + 'run() method to disable this error.') + else: + from werkzeug._internal import _log + _log('warning', ('Werkzeug appears to be used in a ' + 'production deployment. Consider ' + 'switching to a production web server ' + 'instead.')) + app.run(host=host, port=port, threaded=True, + use_reloader=use_reloader, **reloader_options, **kwargs) + elif self.server.eio.async_mode == 'eventlet': + def run_server(): + import eventlet + import eventlet.wsgi + import eventlet.green + addresses = eventlet.green.socket.getaddrinfo(host, port) + if not addresses: + raise RuntimeError( + 'Could not resolve host to a valid address') + eventlet_socket = eventlet.listen(addresses[0][4], + addresses[0][0]) + + # If provided an SSL argument, use an SSL socket + ssl_args = ['keyfile', 'certfile', 'server_side', 'cert_reqs', + 'ssl_version', 'ca_certs', + 'do_handshake_on_connect', 'suppress_ragged_eofs', + 'ciphers'] + ssl_params = {k: kwargs[k] for k in kwargs + if k in ssl_args and kwargs[k] is not None} + for k in ssl_args: + kwargs.pop(k, None) + if len(ssl_params) > 0: + ssl_params['server_side'] = True # Listening requires true + eventlet_socket = eventlet.wrap_ssl(eventlet_socket, + **ssl_params) + + eventlet.wsgi.server(eventlet_socket, app, + log_output=log_output, **kwargs) + + if use_reloader: + run_with_reloader(run_server, **reloader_options) + else: + run_server() + elif self.server.eio.async_mode == 'gevent': + from gevent import pywsgi + try: + from geventwebsocket.handler import WebSocketHandler + websocket = True + except ImportError: + app.logger.warning( + 'WebSocket transport not available. Install ' + 'gevent-websocket for improved performance.') + websocket = False + + log = 'default' + if not log_output: + log = None + if websocket: + self.wsgi_server = pywsgi.WSGIServer( + (host, port), app, handler_class=WebSocketHandler, + log=log, **kwargs) + else: + self.wsgi_server = pywsgi.WSGIServer((host, port), app, + log=log, **kwargs) + + if use_reloader: + # monkey patching is required by the reloader + from gevent import monkey + monkey.patch_thread() + monkey.patch_time() + + def run_server(): + self.wsgi_server.serve_forever() + + run_with_reloader(run_server, **reloader_options) + else: + self.wsgi_server.serve_forever() + + def stop(self): + """Stop a running SocketIO web server. + + This method must be called from a HTTP or SocketIO handler function. + """ + if self.server.eio.async_mode == 'threading': + func = flask.request.environ.get('werkzeug.server.shutdown') + if func: + func() + else: + raise RuntimeError('Cannot stop unknown web server') + elif self.server.eio.async_mode == 'eventlet': + raise SystemExit + elif self.server.eio.async_mode == 'gevent': + self.wsgi_server.stop() + + def start_background_task(self, target, *args, **kwargs): + """Start a background task using the appropriate async model. + + This is a utility function that applications can use to start a + background task using the method that is compatible with the + selected async mode. + + :param target: the target function to execute. + :param args: arguments to pass to the function. + :param kwargs: keyword arguments to pass to the function. + + This function returns an object that represents the background task, + on which the ``join()`` method can be invoked to wait for the task to + complete. + """ + return self.server.start_background_task(target, *args, **kwargs) + + def sleep(self, seconds=0): + """Sleep for the requested amount of time using the appropriate async + model. + + This is a utility function that applications can use to put a task to + sleep without having to worry about using the correct call for the + selected async mode. + """ + return self.server.sleep(seconds) + + def test_client(self, app, namespace=None, query_string=None, + headers=None, auth=None, flask_test_client=None): + """The Socket.IO test client is useful for testing a Flask-SocketIO + server. It works in a similar way to the Flask Test Client, but + adapted to the Socket.IO server. + + :param app: The Flask application instance. + :param namespace: The namespace for the client. If not provided, the + client connects to the server on the global + namespace. + :param query_string: A string with custom query string arguments. + :param headers: A dictionary with custom HTTP headers. + :param auth: Optional authentication data, given as a dictionary. + :param flask_test_client: The instance of the Flask test client + currently in use. Passing the Flask test + client is optional, but is necessary if you + want the Flask user session and any other + cookies set in HTTP routes accessible from + Socket.IO events. + """ + return SocketIOTestClient(app, self, namespace=namespace, + query_string=query_string, headers=headers, + auth=auth, + flask_test_client=flask_test_client) + + def _handle_event(self, handler, message, namespace, sid, *args): + environ = self.server.get_environ(sid, namespace=namespace) + if not environ: + # we don't have record of this client, ignore this event + return '', 400 + app = environ['flask.app'] + with app.request_context(environ): + if self.manage_session: + # manage a separate session for this client's Socket.IO events + # created as a copy of the regular user session + if 'saved_session' not in environ: + environ['saved_session'] = _ManagedSession(flask.session) + session_obj = environ['saved_session'] + if hasattr(flask, 'globals') and \ + hasattr(flask.globals, 'request_ctx'): + # update session for Flask >= 2.2 + ctx = flask.globals.request_ctx._get_current_object() + else: # pragma: no cover + # update session for Flask < 2.2 + ctx = flask._request_ctx_stack.top + ctx.session = session_obj + else: + # let Flask handle the user session + # for cookie based sessions, this effectively freezes the + # session to its state at connection time + # for server-side sessions, this allows HTTP and Socket.IO to + # share the session, with both having read/write access to it + session_obj = flask.session._get_current_object() + flask.request.sid = sid + flask.request.namespace = namespace + flask.request.event = {'message': message, 'args': args} + try: + if message == 'connect': + auth = args[1] if len(args) > 1 else None + try: + ret = handler(auth) + except TypeError: + ret = handler() + else: + ret = handler(*args) + except ConnectionRefusedError: + raise # let this error bubble up to python-socketio + except: + err_handler = self.exception_handlers.get( + namespace, self.default_exception_handler) + if err_handler is None: + raise + type, value, traceback = sys.exc_info() + return err_handler(value) + if not self.manage_session: + # when Flask is managing the user session, it needs to save it + if not hasattr(session_obj, 'modified') or \ + session_obj.modified: + resp = app.response_class() + app.session_interface.save_session(app, session_obj, resp) + return ret + + +def emit(event, *args, **kwargs): + """Emit a SocketIO event. + + This function emits a SocketIO event to one or more connected clients. A + JSON blob can be attached to the event as payload. This is a function that + can only be called from a SocketIO event handler, as in obtains some + information from the current client context. Example:: + + @socketio.on('my event') + def handle_my_custom_event(json): + emit('my response', {'data': 42}) + + :param event: The name of the user event to emit. + :param args: A dictionary with the JSON data to send as payload. + :param namespace: The namespace under which the message is to be sent. + Defaults to the namespace used by the originating event. + A ``'/'`` can be used to explicitly specify the global + namespace. + :param callback: Callback function to invoke with the client's + acknowledgement. + :param broadcast: ``True`` to send the message to all clients, or ``False`` + to only reply to the sender of the originating event. + :param to: Send the message to all the users in the given room, or to the + user with the given session ID. If this argument is not set and + ``broadcast`` is ``False``, then the message is sent only to the + originating user. + :param include_self: ``True`` to include the sender when broadcasting or + addressing a room, or ``False`` to send to everyone + but the sender. + :param skip_sid: The session id of a client to ignore when broadcasting + or addressing a room. This is typically set to the + originator of the message, so that everyone except + that client receive the message. To skip multiple sids + pass a list. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + clients directly, without going through the queue. + This is more efficient, but only works when a + single server process is used, or when there is a + single addressee. It is recommended to always leave + this parameter with its default value of ``False``. + """ + if 'namespace' in kwargs: + namespace = kwargs['namespace'] + else: + namespace = flask.request.namespace + callback = kwargs.get('callback') + broadcast = kwargs.get('broadcast') + to = kwargs.pop('to', None) or kwargs.pop('room', None) + if to is None and not broadcast: + to = flask.request.sid + include_self = kwargs.get('include_self', True) + skip_sid = kwargs.get('skip_sid') + ignore_queue = kwargs.get('ignore_queue', False) + + socketio = flask.current_app.extensions['socketio'] + return socketio.emit(event, *args, namespace=namespace, to=to, + include_self=include_self, skip_sid=skip_sid, + callback=callback, ignore_queue=ignore_queue) + + +def call(event, *args, **kwargs): # pragma: no cover + """Emit a SocketIO event and wait for the response. + + This function issues an emit with a callback and waits for the callback to + be invoked by the client before returning. If the callback isn’t invoked + before the timeout, then a TimeoutError exception is raised. If the + Socket.IO connection drops during the wait, this method still waits until + the specified timeout. Example:: + + def get_status(client, data): + status = call('status', {'data': data}, to=client) + + :param event: The name of the user event to emit. + :param args: A dictionary with the JSON data to send as payload. + :param namespace: The namespace under which the message is to be sent. + Defaults to the namespace used by the originating event. + A ``'/'`` can be used to explicitly specify the global + namespace. + :param to: The session ID of the recipient client. If this argument is not + given, the event is sent to the originating client. + :param timeout: The waiting timeout. If the timeout is reached before the + client acknowledges the event, then a ``TimeoutError`` + exception is raised. The default is 60 seconds. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + client directly, without going through the queue. + This is more efficient, but only works when a + single server process is used, or when there is a + single addressee. It is recommended to always leave + this parameter with its default value of ``False``. + """ + if 'namespace' in kwargs: + namespace = kwargs['namespace'] + else: + namespace = flask.request.namespace + to = kwargs.pop('to', None) or kwargs.pop('room', None) + if to is None: + to = flask.request.sid + timeout = kwargs.get('timeout', 60) + ignore_queue = kwargs.get('ignore_queue', False) + + socketio = flask.current_app.extensions['socketio'] + return socketio.call(event, *args, namespace=namespace, to=to, + ignore_queue=ignore_queue, timeout=timeout) + + +def send(message, **kwargs): + """Send a SocketIO message. + + This function sends a simple SocketIO message to one or more connected + clients. The message can be a string or a JSON blob. This is a simpler + version of ``emit()``, which should be preferred. This is a function that + can only be called from a SocketIO event handler. + + :param message: The message to send, either a string or a JSON blob. + :param json: ``True`` if ``message`` is a JSON blob, ``False`` + otherwise. + :param namespace: The namespace under which the message is to be sent. + Defaults to the namespace used by the originating event. + An empty string can be used to use the global namespace. + :param callback: Callback function to invoke with the client's + acknowledgement. + :param broadcast: ``True`` to send the message to all connected clients, or + ``False`` to only reply to the sender of the originating + event. + :param to: Send the message to all the users in the given room, or to the + user with the given session ID. If this argument is not set and + ``broadcast`` is ``False``, then the message is sent only to the + originating user. + :param include_self: ``True`` to include the sender when broadcasting or + addressing a room, or ``False`` to send to everyone + but the sender. + :param skip_sid: The session id of a client to ignore when broadcasting + or addressing a room. This is typically set to the + originator of the message, so that everyone except + that client receive the message. To skip multiple sids + pass a list. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + clients directly, without going through the queue. + This is more efficient, but only works when a + single server process is used, or when there is a + single addressee. It is recommended to always leave + this parameter with its default value of ``False``. + """ + json = kwargs.get('json', False) + if 'namespace' in kwargs: + namespace = kwargs['namespace'] + else: + namespace = flask.request.namespace + callback = kwargs.get('callback') + broadcast = kwargs.get('broadcast') + to = kwargs.pop('to', None) or kwargs.pop('room', None) + if to is None and not broadcast: + to = flask.request.sid + include_self = kwargs.get('include_self', True) + skip_sid = kwargs.get('skip_sid') + ignore_queue = kwargs.get('ignore_queue', False) + + socketio = flask.current_app.extensions['socketio'] + return socketio.send(message, json=json, namespace=namespace, to=to, + include_self=include_self, skip_sid=skip_sid, + callback=callback, ignore_queue=ignore_queue) + + +def join_room(room, sid=None, namespace=None): + """Join a room. + + This function puts the user in a room, under the current namespace. The + user and the namespace are obtained from the event context. This is a + function that can only be called from a SocketIO event handler. Example:: + + @socketio.on('join') + def on_join(data): + username = session['username'] + room = data['room'] + join_room(room) + send(username + ' has entered the room.', to=room) + + :param room: The name of the room to join. + :param sid: The session id of the client. If not provided, the client is + obtained from the request context. + :param namespace: The namespace for the room. If not provided, the + namespace is obtained from the request context. + """ + socketio = flask.current_app.extensions['socketio'] + sid = sid or flask.request.sid + namespace = namespace or flask.request.namespace + socketio.server.enter_room(sid, room, namespace=namespace) + + +def leave_room(room, sid=None, namespace=None): + """Leave a room. + + This function removes the user from a room, under the current namespace. + The user and the namespace are obtained from the event context. Example:: + + @socketio.on('leave') + def on_leave(data): + username = session['username'] + room = data['room'] + leave_room(room) + send(username + ' has left the room.', to=room) + + :param room: The name of the room to leave. + :param sid: The session id of the client. If not provided, the client is + obtained from the request context. + :param namespace: The namespace for the room. If not provided, the + namespace is obtained from the request context. + """ + socketio = flask.current_app.extensions['socketio'] + sid = sid or flask.request.sid + namespace = namespace or flask.request.namespace + socketio.server.leave_room(sid, room, namespace=namespace) + + +def close_room(room, namespace=None): + """Close a room. + + This function removes any users that are in the given room and then deletes + the room from the server. + + :param room: The name of the room to close. + :param namespace: The namespace for the room. If not provided, the + namespace is obtained from the request context. + """ + socketio = flask.current_app.extensions['socketio'] + namespace = namespace or flask.request.namespace + socketio.server.close_room(room, namespace=namespace) + + +def rooms(sid=None, namespace=None): + """Return a list of the rooms the client is in. + + This function returns all the rooms the client has entered, including its + own room, assigned by the Socket.IO server. + + :param sid: The session id of the client. If not provided, the client is + obtained from the request context. + :param namespace: The namespace for the room. If not provided, the + namespace is obtained from the request context. + """ + socketio = flask.current_app.extensions['socketio'] + sid = sid or flask.request.sid + namespace = namespace or flask.request.namespace + return socketio.server.rooms(sid, namespace=namespace) + + +def disconnect(sid=None, namespace=None, silent=False): + """Disconnect the client. + + This function terminates the connection with the client. As a result of + this call the client will receive a disconnect event. Example:: + + @socketio.on('message') + def receive_message(msg): + if is_banned(session['username']): + disconnect() + else: + # ... + + :param sid: The session id of the client. If not provided, the client is + obtained from the request context. + :param namespace: The namespace for the room. If not provided, the + namespace is obtained from the request context. + :param silent: this option is deprecated. + """ + socketio = flask.current_app.extensions['socketio'] + sid = sid or flask.request.sid + namespace = namespace or flask.request.namespace + return socketio.server.disconnect(sid, namespace=namespace) diff --git a/venv/lib/python3.12/site-packages/flask_socketio/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask_socketio/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b40d3ac70b9c926ce8f45b0d4aca4dbf372b9cf2 GIT binary patch literal 57908 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 C)+-7C literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask_socketio/__pycache__/namespace.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask_socketio/__pycache__/namespace.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08a39b76e4d4313fd6d8be7d9b4e7ba806338c97 GIT binary patch literal 2716 zcmbVOO>7%Q6rS1j`VS|LLR8wSEfeSuPF=gCL?DVHR9XrWq@ebIEX1pAo=rDbspW`x<|f{2T${bZZjaIPL<;L%T{W zpHRqTNWy@GRZ3=A0DK`;%Xd7eYZP+pl(Jb8mp6SmtQPQ)*g>rp_@vGu+eP^8@_0M} z%@0Bn%u(hAmO~vf*&w_=DS1VCv1`ua#0?3oI2EuOm!?@z4_$wn1XId0_#4D_d5OFc zd<_0FwOrSXa+BB)%_Nn}GS_jaPuwZOXQ}O49&zH3g>6>%C?^)9#HSS8E0Tc0*si^* zH7AByghS=gPFNu@9~J@J#B9&&I0)f7l)H=qQXQZoJ@Czmf)S;v7KaVPqSQ1Eg(ArZ zgi{!s5`G4o85Imk&d89zqJ+h!d6bJ&VT$b|jLF1sK{B~iOy7{X)6X8@L@M z94oZgZcy1CAw(|O(;&FOBPN8*6C?0rzJK#@5e9dOkuQOOq$JV6kOF*oJwJST-|En{ z^}hI|6u=EztZ z0tZf4&~v8wQQh)XkG$~N04&OO3PFSr`ICOaX{g>DHuXHygv(l_K)zxU zpve3_SVH$T&Deb}jg8@Z$$n#WgP`$axV3+zttX8!IN*-q*Kupx&bDqCV}JJRMkfYg zY$E2LdKzWTE>KsH3RFA`9;RPW;f%8)G=)E0Z21ixWBfBJ{*fHPr|`w>eI#l*?=O%e BaQgrN literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask_socketio/__pycache__/test_client.cpython-312.pyc b/venv/lib/python3.12/site-packages/flask_socketio/__pycache__/test_client.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e31219c988d0bc9efd9665deb9798f3c88d208f8 GIT binary patch literal 13537 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%0AWIyDLL|ZvL$Q{ z2U>&PUTcI?J8Vw1_4B*fb{rBNb{7L&A;2weUk&UBV`|~``&FIy{GH~ZDK@kh8rqk) zuU7Pa%R}Q2>ZhQ7{}NO1w{1QJ9_Sq<_WGxQP~a5Uy14if6AEHKlpQF~IdALd2Tlr} zE%NYYj01vt_OzW4_(RJDo@p2F08sA|n{w1u+77()ZZsESjtI^;d4L-mkU#CVK)zk5 zS2kU4cIPWckI^#Diq?a}7#SKZ0D0YaJVxLUAg?E3#vb$r@G?%=G%K6uFUQJeaMu{I z*qvh`UiUPN9bu%+8MK4%2@|qgJwj_6_?{~>dV*-*K_`_u@E7TYwR!>i4Hj2M?x$V$ z->1Di$i0X;FS2PCta`0JVx3L+b|1!IN^2R$dI<{B0j%Nq>Vd057sZT#*Mzi7DH`bKL;=vU z1n>$lq3$JXgS!0?O@PWKIMWb9NeWazlyz_xJk(F^A6;Oxg!m(!PsOLA*62KU-^ znG`rx^x+`>)(8O3Jq_V4xPU}1N^MQ6q;tgZ2yS&Qjhj~1O)R~a#}N}?UgV6J0%$%0 zx6;s4Gnz?}yHA_8iHVapx{35_No0q8548tZqQ0}Q2wg%0;>(b#tTCJYB03;If1wjn zbN)I2$C7X&%HVJif^g}{62165RzzV-u(F8ZaS`1Nnl?mvWveT9;SXJq=q^^(MTSm# zQr(xX;BnHNtr4vGDkcOY`^*3-1OqVG3hrkJCi);(auhU0>lEOiPu1C|Sp4AzG?5O0 zP6Fk+6C+b+=FTkqc&YDxs4HKl1gh7oPF=?UDwi;--MR=Uq4mo>{VJi@1FPyfN6K0 zd04xBJ~5YgCz?-2UmT2m2JBr$X9-jo^>k0(0ut^c`>-B5ZqG?b{B%(%b`yi zK5qC_{w#PF0$orGkav0I^>Z`lihi!(=ZgM=1^>aj7k~BgM=yVRWYs?iIR5(KnZx&d zoKjUgAD#>6dzEbs`M$SLKWN^&RK4`WTlM#wf3RG+(%duq%n?*JTqAe#wTgg>Hq=b=g6gwEn-6;Hx zEu@QTjI@`CX`@l}5pGIkvq@It+g@tuhT|sLV4T5{4*#YX{-zhmPXBA_1;kn+fM|>z z5aEl80vt7?_;PEO4N(uIqFF;_CZsV5Wq@{wbPB5C00aB^2VGpi~e&D5h>`R;N6VponA0YGnt#s3p#gK$;(4ddH z?ua?Yz_xe=0}>Dz*Mb%~8gZvYzwOUC<{WPaoN%*_*y>JTXv0%N1Kd1FBgb5(p)FkY zgtk5>P{+0r-QL05cth}wCf~1=Sp&<>75Gq@tI0Y?CTaElm2q$SNc)SUn#b@*)gF;OCG2pMR>Mg2+eTf((* z5gm{h;WxZb19monqUgQUK_67sy^wO+6qxA|IU3h4tsCPaq~T&aLbfaD!!*aIIZ&~C zxcd!V0A|~)6ShQNAk@K^sBsR+4cvF7_{-$T&vkvG&d0_-}zsh9a`GGbg8hfXE}a<->J8T?)hKL(~v+2`sVPB;osF9TZr9#W;y!H zqd$+W)Epz>=0Gsd{`pH6ReqZOvs!aZ!tm2a<;V9sKXF3x+v>982b{m%;e<5o8o(z^ z4}_g0!}RwLg#9YS(Q)!45!D-kOCEeCiFtn{@*4Qz^&RM=Izji-O3n6lrp491exCNa+P~bk+tsvwh7P&T(qFX&U6ooFNFBVwN3fF@ zFhS6y)@dH{c)DXUGa_Y1B9GivD2FY7X-de9b>K15eBkc%2)eX2ZeK$}9hkI1@(p`b vo0sRs3>4AvA~iw+@QtSbgYx}1RsVac>-W^&uiQLb7h98z3?D literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/flask_socketio/namespace.py b/venv/lib/python3.12/site-packages/flask_socketio/namespace.py new file mode 100644 index 0000000..43833a9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask_socketio/namespace.py @@ -0,0 +1,46 @@ +from socketio import Namespace as _Namespace + + +class Namespace(_Namespace): + def __init__(self, namespace=None): + super(Namespace, self).__init__(namespace) + self.socketio = None + + def _set_socketio(self, socketio): + self.socketio = socketio + + def trigger_event(self, event, *args): + """Dispatch an event to the proper handler method. + + In the most common usage, this method is not overloaded by subclasses, + as it performs the routing of events to methods. However, this + method can be overridden if special dispatching rules are needed, or if + having a single method that catches all events is desired. + """ + handler_name = 'on_' + event + if not hasattr(self, handler_name): + # there is no handler for this event, so we ignore it + return + handler = getattr(self, handler_name) + return self.socketio._handle_event(handler, event, self.namespace, + *args) + + def emit(self, event, data=None, room=None, include_self=True, + namespace=None, callback=None): + """Emit a custom event to one or more connected clients.""" + return self.socketio.emit(event, data, room=room, + include_self=include_self, + namespace=namespace or self.namespace, + callback=callback) + + def send(self, data, room=None, include_self=True, namespace=None, + callback=None): + """Send a message to one or more connected clients.""" + return self.socketio.send(data, room=room, include_self=include_self, + namespace=namespace or self.namespace, + callback=callback) + + def close_room(self, room, namespace=None): + """Close a room.""" + return self.socketio.close_room(room=room, + namespace=namespace or self.namespace) diff --git a/venv/lib/python3.12/site-packages/flask_socketio/test_client.py b/venv/lib/python3.12/site-packages/flask_socketio/test_client.py new file mode 100644 index 0000000..0603d77 --- /dev/null +++ b/venv/lib/python3.12/site-packages/flask_socketio/test_client.py @@ -0,0 +1,236 @@ +import uuid + +from socketio import packet +from socketio.pubsub_manager import PubSubManager +from werkzeug.test import EnvironBuilder + + +class SocketIOTestClient(object): + """ + This class is useful for testing a Flask-SocketIO server. It works in a + similar way to the Flask Test Client, but adapted to the Socket.IO server. + + :param app: The Flask application instance. + :param socketio: The application's ``SocketIO`` instance. + :param namespace: The namespace for the client. If not provided, the client + connects to the server on the global namespace. + :param query_string: A string with custom query string arguments. + :param headers: A dictionary with custom HTTP headers. + :param auth: Optional authentication data, given as a dictionary. + :param flask_test_client: The instance of the Flask test client + currently in use. Passing the Flask test + client is optional, but is necessary if you + want the Flask user session and any other + cookies set in HTTP routes accessible from + Socket.IO events. + """ + clients = {} + + def __init__(self, app, socketio, namespace=None, query_string=None, + headers=None, auth=None, flask_test_client=None): + def _mock_send_packet(eio_sid, pkt): + # make sure the packet can be encoded and decoded + epkt = pkt.encode() + if not isinstance(epkt, list): + pkt = packet.Packet(encoded_packet=epkt) + else: + pkt = packet.Packet(encoded_packet=epkt[0]) + for att in epkt[1:]: + pkt.add_attachment(att) + client = self.clients.get(eio_sid) + if not client: + return + if pkt.packet_type == packet.EVENT or \ + pkt.packet_type == packet.BINARY_EVENT: + if pkt.data[0] == 'message' or pkt.data[0] == 'json': + client.queue.append({ + 'name': pkt.data[0], + 'args': pkt.data[1], + 'namespace': pkt.namespace or '/'}) + else: + client.queue.append({ + 'name': pkt.data[0], + 'args': pkt.data[1:], + 'namespace': pkt.namespace or '/'}) + elif pkt.packet_type == packet.ACK or \ + pkt.packet_type == packet.BINARY_ACK: + client.acks = {'args': pkt.data, + 'namespace': pkt.namespace or '/'} + elif pkt.packet_type in [packet.DISCONNECT, packet.CONNECT_ERROR]: + client.connected[pkt.namespace or '/'] = False + + _current_packet = None + + def _mock_send_eio_packet(eio_sid, eio_pkt): + nonlocal _current_packet + if _current_packet is not None: + _current_packet.add_attachment(eio_pkt.data) + if _current_packet.attachment_count == \ + len(_current_packet.attachments): + _mock_send_packet(eio_sid, _current_packet) + _current_packet = None + else: + pkt = packet.Packet(encoded_packet=eio_pkt.data) + if pkt.attachment_count == 0: + _mock_send_packet(eio_sid, pkt) + else: + _current_packet = pkt + + self.app = app + self.flask_test_client = flask_test_client + self.eio_sid = uuid.uuid4().hex + self.clients[self.eio_sid] = self + self.callback_counter = 0 + self.socketio = socketio + self.connected = {} + self.queue = [] + self.acks = None + socketio.server._send_packet = _mock_send_packet + socketio.server._send_eio_packet = _mock_send_eio_packet + socketio.server.environ[self.eio_sid] = {} + socketio.server.async_handlers = False # easier to test when + socketio.server.eio.async_handlers = False # events are sync + if isinstance(socketio.server.manager, PubSubManager): + raise RuntimeError('Test client cannot be used with a message ' + 'queue. Disable the queue on your test ' + 'configuration.') + socketio.server.manager.initialize() + self.connect(namespace=namespace, query_string=query_string, + headers=headers, auth=auth) + + def is_connected(self, namespace=None): + """Check if a namespace is connected. + + :param namespace: The namespace to check. The global namespace is + assumed if this argument is not provided. + """ + return self.connected.get(namespace or '/', False) + + def connect(self, namespace=None, query_string=None, headers=None, + auth=None): + """Connect the client. + + :param namespace: The namespace for the client. If not provided, the + client connects to the server on the global + namespace. + :param query_string: A string with custom query string arguments. + :param headers: A dictionary with custom HTTP headers. + :param auth: Optional authentication data, given as a dictionary. + + Note that it is usually not necessary to explicitly call this method, + since a connection is automatically established when an instance of + this class is created. An example where it this method would be useful + is when the application accepts multiple namespace connections. + """ + url = '/socket.io' + namespace = namespace or '/' + if query_string: + if query_string[0] != '?': + query_string = '?' + query_string + url += query_string + environ = EnvironBuilder(url, headers=headers).get_environ() + environ['flask.app'] = self.app + if self.flask_test_client: + # inject cookies from Flask + if hasattr(self.flask_test_client, '_add_cookies_to_wsgi'): + # flask >= 2.3 + self.flask_test_client._add_cookies_to_wsgi(environ) + else: # pragma: no cover + # flask < 2.3 + self.flask_test_client.cookie_jar.inject_wsgi(environ) + self.socketio.server._handle_eio_connect(self.eio_sid, environ) + pkt = packet.Packet(packet.CONNECT, auth, namespace=namespace) + self.socketio.server._handle_eio_message(self.eio_sid, pkt.encode()) + sid = self.socketio.server.manager.sid_from_eio_sid(self.eio_sid, + namespace) + if sid: + self.connected[namespace] = True + + def disconnect(self, namespace=None): + """Disconnect the client. + + :param namespace: The namespace to disconnect. The global namespace is + assumed if this argument is not provided. + """ + if not self.is_connected(namespace): + raise RuntimeError('not connected') + pkt = packet.Packet(packet.DISCONNECT, namespace=namespace) + self.socketio.server._handle_eio_message(self.eio_sid, pkt.encode()) + del self.connected[namespace or '/'] + + def emit(self, event, *args, **kwargs): + """Emit an event to the server. + + :param event: The event name. + :param *args: The event arguments. + :param callback: ``True`` if the client requests a callback, ``False`` + if not. Note that client-side callbacks are not + implemented, a callback request will just tell the + server to provide the arguments to invoke the + callback, but no callback is invoked. Instead, the + arguments that the server provided for the callback + are returned by this function. + :param namespace: The namespace of the event. The global namespace is + assumed if this argument is not provided. + """ + namespace = kwargs.pop('namespace', None) + if not self.is_connected(namespace): + raise RuntimeError('not connected') + callback = kwargs.pop('callback', False) + id = None + if callback: + self.callback_counter += 1 + id = self.callback_counter + pkt = packet.Packet(packet.EVENT, data=[event] + list(args), + namespace=namespace, id=id) + encoded_pkt = pkt.encode() + if isinstance(encoded_pkt, list): + for epkt in encoded_pkt: + self.socketio.server._handle_eio_message(self.eio_sid, epkt) + else: + self.socketio.server._handle_eio_message(self.eio_sid, encoded_pkt) + if self.acks is not None: + ack = self.acks + self.acks = None + return ack['args'][0] if len(ack['args']) == 1 \ + else ack['args'] + + def send(self, data, json=False, callback=False, namespace=None): + """Send a text or JSON message to the server. + + :param data: A string, dictionary or list to send to the server. + :param json: ``True`` to send a JSON message, ``False`` to send a text + message. + :param callback: ``True`` if the client requests a callback, ``False`` + if not. Note that client-side callbacks are not + implemented, a callback request will just tell the + server to provide the arguments to invoke the + callback, but no callback is invoked. Instead, the + arguments that the server provided for the callback + are returned by this function. + :param namespace: The namespace of the event. The global namespace is + assumed if this argument is not provided. + """ + if json: + msg = 'json' + else: + msg = 'message' + return self.emit(msg, data, callback=callback, namespace=namespace) + + def get_received(self, namespace=None): + """Return the list of messages received from the server. + + Since this is not a real client, any time the server emits an event, + the event is simply stored. The test code can invoke this method to + obtain the list of events that were received since the last call. + + :param namespace: The namespace to get events from. The global + namespace is assumed if this argument is not + provided. + """ + if not self.is_connected(namespace): + raise RuntimeError('not connected') + namespace = namespace or '/' + r = [pkt for pkt in self.queue if pkt['namespace'] == namespace] + self.queue = [pkt for pkt in self.queue if pkt not in r] + return r diff --git a/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/METADATA b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/METADATA new file mode 100644 index 0000000..0e3a649 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/METADATA @@ -0,0 +1,117 @@ +Metadata-Version: 2.4 +Name: greenlet +Version: 3.2.4 +Summary: Lightweight in-process concurrent programming +Home-page: https://greenlet.readthedocs.io/ +Author: Alexey Borzenkov +Author-email: snaury@gmail.com +Maintainer: Jason Madden +Maintainer-email: jason@seecoresoftware.com +License: MIT AND Python-2.0 +Project-URL: Bug Tracker, https://github.com/python-greenlet/greenlet/issues +Project-URL: Source Code, https://github.com/python-greenlet/greenlet/ +Project-URL: Documentation, https://greenlet.readthedocs.io/ +Project-URL: Changes, https://greenlet.readthedocs.io/en/latest/changes.html +Keywords: greenlet coroutine concurrency threads cooperative +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: Programming Language :: C +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.9 +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: Operating System :: OS Independent +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.9 +Description-Content-Type: text/x-rst +License-File: LICENSE +License-File: LICENSE.PSF +Provides-Extra: docs +Requires-Dist: Sphinx; extra == "docs" +Requires-Dist: furo; extra == "docs" +Provides-Extra: test +Requires-Dist: objgraph; extra == "test" +Requires-Dist: psutil; extra == "test" +Requires-Dist: setuptools; extra == "test" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: description-content-type +Dynamic: home-page +Dynamic: keywords +Dynamic: license +Dynamic: license-file +Dynamic: maintainer +Dynamic: maintainer-email +Dynamic: platform +Dynamic: project-url +Dynamic: provides-extra +Dynamic: requires-python +Dynamic: summary + +.. This file is included into docs/history.rst + + +Greenlets are lightweight coroutines for in-process concurrent +programming. + +The "greenlet" package is a spin-off of `Stackless`_, a version of +CPython that supports micro-threads called "tasklets". Tasklets run +pseudo-concurrently (typically in a single or a few OS-level threads) +and are synchronized with data exchanges on "channels". + +A "greenlet", on the other hand, is a still more primitive notion of +micro-thread with no implicit scheduling; coroutines, in other words. +This is useful when you want to control exactly when your code runs. +You can build custom scheduled micro-threads on top of greenlet; +however, it seems that greenlets are useful on their own as a way to +make advanced control flow structures. For example, we can recreate +generators; the difference with Python's own generators is that our +generators can call nested functions and the nested functions can +yield values too. (Additionally, you don't need a "yield" keyword. See +the example in `test_generator.py +`_). + +Greenlets are provided as a C extension module for the regular unmodified +interpreter. + +.. _`Stackless`: http://www.stackless.com + + +Who is using Greenlet? +====================== + +There are several libraries that use Greenlet as a more flexible +alternative to Python's built in coroutine support: + + - `Concurrence`_ + - `Eventlet`_ + - `Gevent`_ + +.. _Concurrence: http://opensource.hyves.org/concurrence/ +.. _Eventlet: http://eventlet.net/ +.. _Gevent: http://www.gevent.org/ + +Getting Greenlet +================ + +The easiest way to get Greenlet is to install it with pip:: + + pip install greenlet + + +Source code archives and binary distributions are available on the +python package index at https://pypi.org/project/greenlet + +The source code repository is hosted on github: +https://github.com/python-greenlet/greenlet + +Documentation is available on readthedocs.org: +https://greenlet.readthedocs.io diff --git a/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/RECORD b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/RECORD new file mode 100644 index 0000000..4c457e6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/RECORD @@ -0,0 +1,121 @@ +../../../include/site/python3.12/greenlet/greenlet.h,sha256=sz5pYRSQqedgOt2AMgxLZdTjO-qcr_JMvgiEJR9IAJ8,4755 +greenlet-3.2.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +greenlet-3.2.4.dist-info/METADATA,sha256=ZwuiD2PER_KIrBSuuQdUPtK-VCLKtfY5RueYGQheX6o,4120 +greenlet-3.2.4.dist-info/RECORD,, +greenlet-3.2.4.dist-info/WHEEL,sha256=LpQoElFRmdjMbJKp2FHd8t88QuEtEHUH_crLCk0WHuI,152 +greenlet-3.2.4.dist-info/licenses/LICENSE,sha256=dpgx1uXfrywggC-sz_H6-0wgJd2PYlPfpH_K1Z1NCXk,1434 +greenlet-3.2.4.dist-info/licenses/LICENSE.PSF,sha256=5f88I8EQ5JTNfXNsEP2W1GJFe6_soxCEDbZScpjH1Gs,2424 +greenlet-3.2.4.dist-info/top_level.txt,sha256=YSnRsCRoO61JGlP57o8iKL6rdLWDWuiyKD8ekpWUsDc,9 +greenlet/CObjects.cpp,sha256=OPej1bWBgc4sRrTRQ2aFFML9pzDYKlKhlJSjsI0X_eU,3508 +greenlet/PyGreenlet.cpp,sha256=dGal9uux_E0d6yMaZfVYpdD9x1XFVOrp4s_or_D_UEM,24199 +greenlet/PyGreenlet.hpp,sha256=2ZQlOxYNoy7QwD7mppFoOXe_At56NIsJ0eNsE_hoSsw,1463 +greenlet/PyGreenletUnswitchable.cpp,sha256=PQE0fSZa_IOyUM44IESHkJoD2KtGW3dkhkmZSYY3WHs,4375 +greenlet/PyModule.cpp,sha256=J2TH06dGcNEarioS6NbWXkdME8hJY05XVbdqLrfO5w4,8587 +greenlet/TBrokenGreenlet.cpp,sha256=smN26uC7ahAbNYiS10rtWPjCeTG4jevM8siA2sjJiXg,1021 +greenlet/TExceptionState.cpp,sha256=U7Ctw9fBdNraS0d174MoQW7bN-ae209Ta0JuiKpcpVI,1359 +greenlet/TGreenlet.cpp,sha256=IM4cHsv1drEl35d7n8YOA_wR-R7oRvx5XhOJOK2PBB8,25732 +greenlet/TGreenlet.hpp,sha256=DoN795i3vofgll-20GA-ylg3qCNw-nKprLA6r7CK5HY,28522 +greenlet/TGreenletGlobals.cpp,sha256=YyEmDjKf1g32bsL-unIUScFLnnA1fzLWf2gOMd-D0Zw,3264 +greenlet/TMainGreenlet.cpp,sha256=fvgb8HHB-FVTPEKjR1s_ifCZSpp5D5YQByik0CnIABg,3276 +greenlet/TPythonState.cpp,sha256=b12U09sNjQvKG0_agROFHuJkDDa7HDccWaFW55XViQA,15975 +greenlet/TStackState.cpp,sha256=V444I8Jj9DhQz-9leVW_9dtiSRjaE1NMlgDG02Xxq-Y,7381 +greenlet/TThreadState.hpp,sha256=2Jgg7DtGggMYR_x3CLAvAFf1mIdIDtQvSSItcdmX4ZQ,19131 +greenlet/TThreadStateCreator.hpp,sha256=uYTexDWooXSSgUc5uh-Mhm5BQi3-kR6CqpizvNynBFQ,2610 +greenlet/TThreadStateDestroy.cpp,sha256=36yBCAMq3beXTZd-XnFA7DwaHVSOx2vc28-nf0spysU,8169 +greenlet/TUserGreenlet.cpp,sha256=uemg0lwKXtYB0yzmvyYdIIAsKnNkifXM1OJ2OlrFP1A,23553 +greenlet/__init__.py,sha256=vSR8EU6Bi32-0MkAlx--fzCL-Eheh6EqJWa-7B9LTOk,1723 +greenlet/__pycache__/__init__.cpython-312.pyc,, +greenlet/_greenlet.cpython-312-x86_64-linux-gnu.so,sha256=5JNZW-sTm4GrmgS5ACVmTnr03ssOpDX0OVtVrUleW8U,1446176 +greenlet/greenlet.cpp,sha256=WdItb1yWL9WNsTqJNf0Iw8ZwDHD49pkDP0rIRGBg2pw,10996 +greenlet/greenlet.h,sha256=sz5pYRSQqedgOt2AMgxLZdTjO-qcr_JMvgiEJR9IAJ8,4755 +greenlet/greenlet_allocator.hpp,sha256=eC0S1AQuep1vnVRsag-r83xgfAtbpn0qQZ-oXzQXaso,2607 +greenlet/greenlet_compiler_compat.hpp,sha256=nRxpLN9iNbnLVyFDeVmOwyeeNm6scQrOed1l7JQYMCM,4346 +greenlet/greenlet_cpython_compat.hpp,sha256=kJG6d_yDwwl3bSZOOFqM3ks1UzVIGcwbsTM2s8C6VYE,4149 +greenlet/greenlet_exceptions.hpp,sha256=06Bx81DtVaJTa6RtiMcV141b-XHv4ppEgVItkblcLWY,4503 +greenlet/greenlet_internal.hpp,sha256=Ajc-_09W4xWzm9XfyXHAeQAFUgKGKsnJwYsTCoNy3ns,2709 +greenlet/greenlet_msvc_compat.hpp,sha256=0MyaiyoCE_A6UROXZlMQRxRS17gfyh0d7NUppU3EVFc,2978 +greenlet/greenlet_refs.hpp,sha256=OnbA91yZf3QHH6-eJccvoNDAaN-pQBMMrclFU1Ot3J4,34436 +greenlet/greenlet_slp_switch.hpp,sha256=kM1QHA2iV-gH4cFyN6lfIagHQxvJZjWOVJdIxRE3TlQ,3198 +greenlet/greenlet_thread_support.hpp,sha256=XUJ6ljWjf9OYyuOILiz8e_yHvT3fbaUiHdhiPNQUV4s,867 +greenlet/platform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +greenlet/platform/__pycache__/__init__.cpython-312.pyc,, +greenlet/platform/setup_switch_x64_masm.cmd,sha256=ZpClUJeU0ujEPSTWNSepP0W2f9XiYQKA8QKSoVou8EU,143 +greenlet/platform/switch_aarch64_gcc.h,sha256=GKC0yWNXnbK2X--X6aguRCMj2Tg7hDU1Zkl3RljDvC8,4307 +greenlet/platform/switch_alpha_unix.h,sha256=Z-SvF8JQV3oxWT8JRbL9RFu4gRFxPdJ7cviM8YayMmw,671 +greenlet/platform/switch_amd64_unix.h,sha256=EcSFCBlodEBhqhKjcJqY_5Dn_jn7pKpkJlOvp7gFXLI,2748 +greenlet/platform/switch_arm32_gcc.h,sha256=Z3KkHszdgq6uU4YN3BxvKMG2AdDnovwCCNrqGWZ1Lyo,2479 +greenlet/platform/switch_arm32_ios.h,sha256=mm5_R9aXB92hyxzFRwB71M60H6AlvHjrpTrc72Pz3l8,1892 +greenlet/platform/switch_arm64_masm.asm,sha256=4kpTtfy7rfcr8j1CpJLAK21EtZpGDAJXWRU68HEy5A8,1245 +greenlet/platform/switch_arm64_masm.obj,sha256=DmLnIB_icoEHAz1naue_pJPTZgR9ElM7-Nmztr-o9_U,746 +greenlet/platform/switch_arm64_msvc.h,sha256=RqK5MHLmXI3Q-FQ7tm32KWnbDNZKnkJdq8CR89cz640,398 +greenlet/platform/switch_csky_gcc.h,sha256=kDikyiPpewP71KoBZQO_MukDTXTXBiC7x-hF0_2DL0w,1331 +greenlet/platform/switch_loongarch64_linux.h,sha256=7M-Dhc4Q8tRbJCJhalDLwU6S9Mx8MjmN1RbTDgIvQTM,779 +greenlet/platform/switch_m68k_gcc.h,sha256=VSa6NpZhvyyvF-Q58CTIWSpEDo4FKygOyTz00whctlw,928 +greenlet/platform/switch_mips_unix.h,sha256=E0tYsqc5anDY1BhenU1l8DW-nVHC_BElzLgJw3TGtPk,1426 +greenlet/platform/switch_ppc64_aix.h,sha256=_BL0iyRr3ZA5iPlr3uk9SJ5sNRWGYLrXcZ5z-CE9anE,3860 +greenlet/platform/switch_ppc64_linux.h,sha256=0rriT5XyxPb0GqsSSn_bP9iQsnjsPbBmu0yqo5goSyQ,3815 +greenlet/platform/switch_ppc_aix.h,sha256=pHA4slEjUFP3J3SYm1TAlNPhgb2G_PAtax5cO8BEe1A,2941 +greenlet/platform/switch_ppc_linux.h,sha256=YwrlKUzxlXuiKMQqr6MFAV1bPzWnmvk6X1AqJZEpOWU,2759 +greenlet/platform/switch_ppc_macosx.h,sha256=Z6KN_ud0n6nC3ltJrNz2qtvER6vnRAVRNH9mdIDpMxY,2624 +greenlet/platform/switch_ppc_unix.h,sha256=-ZG7MSSPEA5N4qO9PQChtyEJ-Fm6qInhyZm_ZBHTtMg,2652 +greenlet/platform/switch_riscv_unix.h,sha256=606V6ACDf79Fz_WGItnkgbjIJ0pGg_sHmPyDxQYKK58,949 +greenlet/platform/switch_s390_unix.h,sha256=RRlGu957ybmq95qNNY4Qw1mcaoT3eBnW5KbVwu48KX8,2763 +greenlet/platform/switch_sh_gcc.h,sha256=mcRJBTu-2UBf4kZtX601qofwuDuy-Y-hnxJtrcaB7do,901 +greenlet/platform/switch_sparc_sun_gcc.h,sha256=xZish9GsMHBienUbUMsX1-ZZ-as7hs36sVhYIE3ew8Y,2797 +greenlet/platform/switch_x32_unix.h,sha256=nM98PKtzTWc1lcM7TRMUZJzskVdR1C69U1UqZRWX0GE,1509 +greenlet/platform/switch_x64_masm.asm,sha256=nu6n2sWyXuXfpPx40d9YmLfHXUc1sHgeTvX1kUzuvEM,1841 +greenlet/platform/switch_x64_masm.obj,sha256=GNtTNxYdo7idFUYsQv-mrXWgyT5EJ93-9q90lN6svtQ,1078 +greenlet/platform/switch_x64_msvc.h,sha256=LIeasyKo_vHzspdMzMHbosRhrBfKI4BkQOh4qcTHyJw,1805 +greenlet/platform/switch_x86_msvc.h,sha256=TtGOwinbFfnn6clxMNkCz8i6OmgB6kVRrShoF5iT9to,12838 +greenlet/platform/switch_x86_unix.h,sha256=VplW9H0FF0cZHw1DhJdIUs5q6YLS4cwb2nYwjF83R1s,3059 +greenlet/slp_platformselect.h,sha256=hTb3GFdcPUYJTuu1MY93js7MZEax1_e5E-gflpi0RzI,3959 +greenlet/tests/__init__.py,sha256=EtTtQfpRDde0MhsdAM5Cm7LYIfS_HKUIFwquiH4Q7ac,9736 +greenlet/tests/__pycache__/__init__.cpython-312.pyc,, +greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-312.pyc,, +greenlet/tests/__pycache__/fail_cpp_exception.cpython-312.pyc,, +greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-312.pyc,, +greenlet/tests/__pycache__/fail_slp_switch.cpython-312.pyc,, +greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-312.pyc,, +greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-312.pyc,, +greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-312.pyc,, +greenlet/tests/__pycache__/leakcheck.cpython-312.pyc,, +greenlet/tests/__pycache__/test_contextvars.cpython-312.pyc,, +greenlet/tests/__pycache__/test_cpp.cpython-312.pyc,, +greenlet/tests/__pycache__/test_extension_interface.cpython-312.pyc,, +greenlet/tests/__pycache__/test_gc.cpython-312.pyc,, +greenlet/tests/__pycache__/test_generator.cpython-312.pyc,, +greenlet/tests/__pycache__/test_generator_nested.cpython-312.pyc,, +greenlet/tests/__pycache__/test_greenlet.cpython-312.pyc,, +greenlet/tests/__pycache__/test_greenlet_trash.cpython-312.pyc,, +greenlet/tests/__pycache__/test_leaks.cpython-312.pyc,, +greenlet/tests/__pycache__/test_stack_saved.cpython-312.pyc,, +greenlet/tests/__pycache__/test_throw.cpython-312.pyc,, +greenlet/tests/__pycache__/test_tracing.cpython-312.pyc,, +greenlet/tests/__pycache__/test_version.cpython-312.pyc,, +greenlet/tests/__pycache__/test_weakref.cpython-312.pyc,, +greenlet/tests/_test_extension.c,sha256=vkeGA-6oeJcGILsD7oIrT1qZop2GaTOHXiNT7mcSl-0,5773 +greenlet/tests/_test_extension.cpython-312-x86_64-linux-gnu.so,sha256=J6t_AfqGHhCVKKwL3MMeVLA3-0fX4CIb85P8aLPbbBY,17256 +greenlet/tests/_test_extension_cpp.cpp,sha256=e0kVnaB8CCaEhE9yHtNyfqTjevsPDKKx-zgxk7PPK48,6565 +greenlet/tests/_test_extension_cpp.cpython-312-x86_64-linux-gnu.so,sha256=bxLQ3ukXYXuFVMFVNEdMaeYQk8zgfeMgYPgw621VQZc,58384 +greenlet/tests/fail_clearing_run_switches.py,sha256=o433oA_nUCtOPaMEGc8VEhZIKa71imVHXFw7TsXaP8M,1263 +greenlet/tests/fail_cpp_exception.py,sha256=o_ZbipWikok8Bjc-vjiQvcb5FHh2nVW-McGKMLcMzh0,985 +greenlet/tests/fail_initialstub_already_started.py,sha256=txENn5IyzGx2p-XR1XB7qXmC8JX_4mKDEA8kYBXUQKc,1961 +greenlet/tests/fail_slp_switch.py,sha256=rJBZcZfTWR3e2ERQtPAud6YKShiDsP84PmwOJbp4ey0,524 +greenlet/tests/fail_switch_three_greenlets.py,sha256=zSitV7rkNnaoHYVzAGGLnxz-yPtohXJJzaE8ehFDQ0M,956 +greenlet/tests/fail_switch_three_greenlets2.py,sha256=FPJensn2EJxoropl03JSTVP3kgP33k04h6aDWWozrOk,1285 +greenlet/tests/fail_switch_two_greenlets.py,sha256=1CaI8s3504VbbF1vj1uBYuy-zxBHVzHPIAd1LIc8ONg,817 +greenlet/tests/leakcheck.py,sha256=JHgc45bnTyVtn9MiprIlz2ygSXMFtcaCSp2eB9XIhQE,12612 +greenlet/tests/test_contextvars.py,sha256=xutO-qZgKTwKsA9lAqTjIcTBEiQV4RpNKM-vO2_YCVU,10541 +greenlet/tests/test_cpp.py,sha256=hpxhFAdKJTpAVZP8CBGs1ZcrKdscI9BaDZk4btkI5d4,2736 +greenlet/tests/test_extension_interface.py,sha256=eJ3cwLacdK2WbsrC-4DgeyHdwLRcG4zx7rrkRtqSzC4,3829 +greenlet/tests/test_gc.py,sha256=PCOaRpIyjNnNlDogGL3FZU_lrdXuM-pv1rxeE5TP5mc,2923 +greenlet/tests/test_generator.py,sha256=tONXiTf98VGm347o1b-810daPiwdla5cbpFg6QI1R1g,1240 +greenlet/tests/test_generator_nested.py,sha256=7v4HOYrf1XZP39dk5IUMubdZ8yc3ynwZcqj9GUJyMSA,3718 +greenlet/tests/test_greenlet.py,sha256=gSG6hOjKYyRRe5ZzNUpskrUcMnBT3WU4yITTzaZfLH4,47995 +greenlet/tests/test_greenlet_trash.py,sha256=n2dBlQfOoEO1ODatFi8QdhboH3fB86YtqzcYMYOXxbw,7947 +greenlet/tests/test_leaks.py,sha256=OFSE870Zyql85HukfC_XYa2c4gDQBU889RV1AlLum74,18076 +greenlet/tests/test_stack_saved.py,sha256=eyzqNY2VCGuGlxhT_In6TvZ6Okb0AXFZVyBEnK1jDwA,446 +greenlet/tests/test_throw.py,sha256=u2TQ_WvvCd6N6JdXWIxVEcXkKu5fepDlz9dktYdmtng,3712 +greenlet/tests/test_tracing.py,sha256=NFD6Vcww8grBnFQFhCNdswwGetjLeLQ7vL2Qqw3LWBM,8591 +greenlet/tests/test_version.py,sha256=O9DpAITsOFgiRcjd4odQ7ejmwx_N9Q1zQENVcbtFHIc,1339 +greenlet/tests/test_weakref.py,sha256=F8M23btEF87bIbpptLNBORosbQqNZGiYeKMqYjWrsak,883 diff --git a/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/WHEEL b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/WHEEL new file mode 100644 index 0000000..d9747de --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: setuptools (80.9.0) +Root-Is-Purelib: false +Tag: cp312-cp312-manylinux_2_24_x86_64 +Tag: cp312-cp312-manylinux_2_28_x86_64 + diff --git a/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/licenses/LICENSE b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/licenses/LICENSE new file mode 100644 index 0000000..b73a4a1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/licenses/LICENSE @@ -0,0 +1,30 @@ +The following files are derived from Stackless Python and are subject to the +same license as Stackless Python: + + src/greenlet/slp_platformselect.h + files in src/greenlet/platform/ directory + +See LICENSE.PSF and http://www.stackless.com/ for details. + +Unless otherwise noted, the files in greenlet have been released under the +following MIT license: + +Copyright (c) Armin Rigo, Christian Tismer 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/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/licenses/LICENSE.PSF b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/licenses/LICENSE.PSF new file mode 100644 index 0000000..d3b509a --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/licenses/LICENSE.PSF @@ -0,0 +1,47 @@ +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, +2011 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. diff --git a/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/top_level.txt b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/top_level.txt new file mode 100644 index 0000000..46725be --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet-3.2.4.dist-info/top_level.txt @@ -0,0 +1 @@ +greenlet diff --git a/venv/lib/python3.12/site-packages/greenlet/CObjects.cpp b/venv/lib/python3.12/site-packages/greenlet/CObjects.cpp new file mode 100644 index 0000000..c135995 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/CObjects.cpp @@ -0,0 +1,157 @@ +#ifndef COBJECTS_CPP +#define COBJECTS_CPP +/***************************************************************************** + * C interface + * + * These are exported using the CObject API + */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-function" +#endif + +#include "greenlet_exceptions.hpp" + +#include "greenlet_internal.hpp" +#include "greenlet_refs.hpp" + + +#include "TThreadStateDestroy.cpp" + +#include "PyGreenlet.hpp" + +using greenlet::PyErrOccurred; +using greenlet::Require; + + + +extern "C" { +static PyGreenlet* +PyGreenlet_GetCurrent(void) +{ + return GET_THREAD_STATE().state().get_current().relinquish_ownership(); +} + +static int +PyGreenlet_SetParent(PyGreenlet* g, PyGreenlet* nparent) +{ + return green_setparent((PyGreenlet*)g, (PyObject*)nparent, NULL); +} + +static PyGreenlet* +PyGreenlet_New(PyObject* run, PyGreenlet* parent) +{ + using greenlet::refs::NewDictReference; + // In the past, we didn't use green_new and green_init, but that + // was a maintenance issue because we duplicated code. This way is + // much safer, but slightly slower. If that's a problem, we could + // refactor green_init to separate argument parsing from initialization. + OwnedGreenlet g = OwnedGreenlet::consuming(green_new(&PyGreenlet_Type, nullptr, nullptr)); + if (!g) { + return NULL; + } + + try { + NewDictReference kwargs; + if (run) { + kwargs.SetItem(mod_globs->str_run, run); + } + if (parent) { + kwargs.SetItem("parent", (PyObject*)parent); + } + + Require(green_init(g.borrow(), mod_globs->empty_tuple, kwargs.borrow())); + } + catch (const PyErrOccurred&) { + return nullptr; + } + + return g.relinquish_ownership(); +} + +static PyObject* +PyGreenlet_Switch(PyGreenlet* self, PyObject* args, PyObject* kwargs) +{ + if (!PyGreenlet_Check(self)) { + PyErr_BadArgument(); + return NULL; + } + + if (args == NULL) { + args = mod_globs->empty_tuple; + } + + if (kwargs == NULL || !PyDict_Check(kwargs)) { + kwargs = NULL; + } + + return green_switch(self, args, kwargs); +} + +static PyObject* +PyGreenlet_Throw(PyGreenlet* self, PyObject* typ, PyObject* val, PyObject* tb) +{ + if (!PyGreenlet_Check(self)) { + PyErr_BadArgument(); + return nullptr; + } + try { + PyErrPieces err_pieces(typ, val, tb); + return internal_green_throw(self, err_pieces).relinquish_ownership(); + } + catch (const PyErrOccurred&) { + return nullptr; + } +} + + + +static int +Extern_PyGreenlet_MAIN(PyGreenlet* self) +{ + if (!PyGreenlet_Check(self)) { + PyErr_BadArgument(); + return -1; + } + return self->pimpl->main(); +} + +static int +Extern_PyGreenlet_ACTIVE(PyGreenlet* self) +{ + if (!PyGreenlet_Check(self)) { + PyErr_BadArgument(); + return -1; + } + return self->pimpl->active(); +} + +static int +Extern_PyGreenlet_STARTED(PyGreenlet* self) +{ + if (!PyGreenlet_Check(self)) { + PyErr_BadArgument(); + return -1; + } + return self->pimpl->started(); +} + +static PyGreenlet* +Extern_PyGreenlet_GET_PARENT(PyGreenlet* self) +{ + if (!PyGreenlet_Check(self)) { + PyErr_BadArgument(); + return NULL; + } + // This can return NULL even if there is no exception + return self->pimpl->parent().acquire(); +} +} // extern C. + +/** End C API ****************************************************************/ +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/PyGreenlet.cpp b/venv/lib/python3.12/site-packages/greenlet/PyGreenlet.cpp new file mode 100644 index 0000000..6b118a5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/PyGreenlet.cpp @@ -0,0 +1,751 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +#ifndef PYGREENLET_CPP +#define PYGREENLET_CPP +/***************** +The Python slot functions for TGreenlet. + */ + + +#define PY_SSIZE_T_CLEAN +#include +#include "structmember.h" // PyMemberDef + +#include "greenlet_internal.hpp" +#include "TThreadStateDestroy.cpp" +#include "TGreenlet.hpp" +// #include "TUserGreenlet.cpp" +// #include "TMainGreenlet.cpp" +// #include "TBrokenGreenlet.cpp" + + +#include "greenlet_refs.hpp" +#include "greenlet_slp_switch.hpp" + +#include "greenlet_thread_support.hpp" +#include "TGreenlet.hpp" + +#include "TGreenletGlobals.cpp" +#include "TThreadStateDestroy.cpp" +#include "PyGreenlet.hpp" +// #include "TGreenlet.cpp" + +// #include "TExceptionState.cpp" +// #include "TPythonState.cpp" +// #include "TStackState.cpp" + +using greenlet::LockGuard; +using greenlet::LockInitError; +using greenlet::PyErrOccurred; +using greenlet::Require; + +using greenlet::g_handle_exit; +using greenlet::single_result; + +using greenlet::Greenlet; +using greenlet::UserGreenlet; +using greenlet::MainGreenlet; +using greenlet::BrokenGreenlet; +using greenlet::ThreadState; +using greenlet::PythonState; + + + +static PyGreenlet* +green_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds)) +{ + PyGreenlet* o = + (PyGreenlet*)PyBaseObject_Type.tp_new(type, mod_globs->empty_tuple, mod_globs->empty_dict); + if (o) { + // Recall: borrowing or getting the current greenlet + // causes the "deleteme list" to get cleared. So constructing a greenlet + // can do things like cause other greenlets to get finalized. + UserGreenlet* c = new UserGreenlet(o, GET_THREAD_STATE().state().borrow_current()); + assert(Py_REFCNT(o) == 1); + // Also: This looks like a memory leak, but isn't. Constructing the + // C++ object assigns it to the pimpl pointer of the Python object (o); + // we'll need that later. + assert(c == o->pimpl); + } + return o; +} + + +// green_init is used in the tp_init slot. So it's important that +// it can be called directly from CPython. Thus, we don't use +// BorrowedGreenlet and BorrowedObject --- although in theory +// these should be binary layout compatible, that may not be +// guaranteed to be the case (32-bit linux ppc possibly). +static int +green_init(PyGreenlet* self, PyObject* args, PyObject* kwargs) +{ + PyArgParseParam run; + PyArgParseParam nparent; + static const char* kwlist[] = { + "run", + "parent", + NULL + }; + + // recall: The O specifier does NOT increase the reference count. + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "|OO:green", (char**)kwlist, &run, &nparent)) { + return -1; + } + + if (run) { + if (green_setrun(self, run, NULL)) { + return -1; + } + } + if (nparent && !nparent.is_None()) { + return green_setparent(self, nparent, NULL); + } + return 0; +} + + + +static int +green_traverse(PyGreenlet* self, visitproc visit, void* arg) +{ + // We must only visit referenced objects, i.e. only objects + // Py_INCREF'ed by this greenlet (directly or indirectly): + // + // - stack_prev is not visited: holds previous stack pointer, but it's not + // referenced + // - frames are not visited as we don't strongly reference them; + // alive greenlets are not garbage collected + // anyway. This can be a problem, however, if this greenlet is + // never allowed to finish, and is referenced from the frame: we + // have an uncollectible cycle in that case. Note that the + // frame object itself is also frequently not even tracked by the GC + // starting with Python 3.7 (frames are allocated by the + // interpreter untracked, and only become tracked when their + // evaluation is finished if they have a refcount > 1). All of + // this is to say that we should probably strongly reference + // the frame object. Doing so, while always allowing GC on a + // greenlet, solves several leaks for us. + + Py_VISIT(self->dict); + if (!self->pimpl) { + // Hmm. I have seen this at interpreter shutdown time, + // I think. That's very odd because this doesn't go away until + // we're ``green_dealloc()``, at which point we shouldn't be + // traversed anymore. + return 0; + } + + return self->pimpl->tp_traverse(visit, arg); +} + +static int +green_is_gc(PyObject* _self) +{ + BorrowedGreenlet self(_self); + int result = 0; + /* Main greenlet can be garbage collected since it can only + become unreachable if the underlying thread exited. + Active greenlets --- including those that are suspended --- + cannot be garbage collected, however. + */ + if (self->main() || !self->active()) { + result = 1; + } + // The main greenlet pointer will eventually go away after the thread dies. + if (self->was_running_in_dead_thread()) { + // Our thread is dead! We can never run again. Might as well + // GC us. Note that if a tuple containing only us and other + // immutable objects had been scanned before this, when we + // would have returned 0, the tuple will take itself out of GC + // tracking and never be investigated again. So that could + // result in both us and the tuple leaking due to an + // unreachable/uncollectible reference. The same goes for + // dictionaries. + // + // It's not a great idea to be changing our GC state on the + // fly. + result = 1; + } + return result; +} + + +static int +green_clear(PyGreenlet* self) +{ + /* Greenlet is only cleared if it is about to be collected. + Since active greenlets are not garbage collectable, we can + be sure that, even if they are deallocated during clear, + nothing they reference is in unreachable or finalizers, + so even if it switches we are relatively safe. */ + // XXX: Are we responsible for clearing weakrefs here? + Py_CLEAR(self->dict); + return self->pimpl->tp_clear(); +} + +/** + * Returns 0 on failure (the object was resurrected) or 1 on success. + **/ +static int +_green_dealloc_kill_started_non_main_greenlet(BorrowedGreenlet self) +{ + /* Hacks hacks hacks copied from instance_dealloc() */ + /* Temporarily resurrect the greenlet. */ + assert(self.REFCNT() == 0); + Py_SET_REFCNT(self.borrow(), 1); + /* Save the current exception, if any. */ + PyErrPieces saved_err; + try { + // BY THE TIME WE GET HERE, the state may actually be going + // away + // if we're shutting down the interpreter and freeing thread + // entries, + // this could result in freeing greenlets that were leaked. So + // we can't try to read the state. + self->deallocing_greenlet_in_thread( + self->thread_state() + ? static_cast(GET_THREAD_STATE()) + : nullptr); + } + catch (const PyErrOccurred&) { + PyErr_WriteUnraisable(self.borrow_o()); + /* XXX what else should we do? */ + } + /* Check for no resurrection must be done while we keep + * our internal reference, otherwise PyFile_WriteObject + * causes recursion if using Py_INCREF/Py_DECREF + */ + if (self.REFCNT() == 1 && self->active()) { + /* Not resurrected, but still not dead! + XXX what else should we do? we complain. */ + PyObject* f = PySys_GetObject("stderr"); + Py_INCREF(self.borrow_o()); /* leak! */ + if (f != NULL) { + PyFile_WriteString("GreenletExit did not kill ", f); + PyFile_WriteObject(self.borrow_o(), f, 0); + PyFile_WriteString("\n", f); + } + } + /* Restore the saved exception. */ + saved_err.PyErrRestore(); + /* Undo the temporary resurrection; can't use DECREF here, + * it would cause a recursive call. + */ + assert(self.REFCNT() > 0); + + Py_ssize_t refcnt = self.REFCNT() - 1; + Py_SET_REFCNT(self.borrow_o(), refcnt); + if (refcnt != 0) { + /* Resurrected! */ + _Py_NewReference(self.borrow_o()); + Py_SET_REFCNT(self.borrow_o(), refcnt); + /* Better to use tp_finalizer slot (PEP 442) + * and call ``PyObject_CallFinalizerFromDealloc``, + * but that's only supported in Python 3.4+; see + * Modules/_io/iobase.c for an example. + * TODO: We no longer run on anything that old, switch to finalizers. + * + * The following approach is copied from iobase.c in CPython 2.7. + * (along with much of this function in general). Here's their + * comment: + * + * When called from a heap type's dealloc, the type will be + * decref'ed on return (see e.g. subtype_dealloc in typeobject.c). + * + * On free-threaded builds of CPython, the type is meant to be immortal + * so we probably shouldn't mess with this? See + * test_issue_245_reference_counting_subclass_no_threads + */ + if (PyType_HasFeature(self.TYPE(), Py_TPFLAGS_HEAPTYPE)) { + Py_INCREF(self.TYPE()); + } + + PyObject_GC_Track((PyObject*)self); + + GREENLET_Py_DEC_REFTOTAL; +#ifdef COUNT_ALLOCS + --Py_TYPE(self)->tp_frees; + --Py_TYPE(self)->tp_allocs; +#endif /* COUNT_ALLOCS */ + return 0; + } + return 1; +} + + +static void +green_dealloc(PyGreenlet* self) +{ + PyObject_GC_UnTrack(self); + BorrowedGreenlet me(self); + if (me->active() + && me->started() + && !me->main()) { + if (!_green_dealloc_kill_started_non_main_greenlet(me)) { + return; + } + } + + if (self->weakreflist != NULL) { + PyObject_ClearWeakRefs((PyObject*)self); + } + Py_CLEAR(self->dict); + + if (self->pimpl) { + // In case deleting this, which frees some memory, + // somehow winds up calling back into us. That's usually a + //bug in our code. + Greenlet* p = self->pimpl; + self->pimpl = nullptr; + delete p; + } + // and finally we're done. self is now invalid. + Py_TYPE(self)->tp_free((PyObject*)self); +} + + + +static OwnedObject +internal_green_throw(BorrowedGreenlet self, PyErrPieces& err_pieces) +{ + PyObject* result = nullptr; + err_pieces.PyErrRestore(); + assert(PyErr_Occurred()); + if (self->started() && !self->active()) { + /* dead greenlet: turn GreenletExit into a regular return */ + result = g_handle_exit(OwnedObject()).relinquish_ownership(); + } + self->args() <<= result; + + return single_result(self->g_switch()); +} + + + +PyDoc_STRVAR( + green_switch_doc, + "switch(*args, **kwargs)\n" + "\n" + "Switch execution to this greenlet.\n" + "\n" + "If this greenlet has never been run, then this greenlet\n" + "will be switched to using the body of ``self.run(*args, **kwargs)``.\n" + "\n" + "If the greenlet is active (has been run, but was switch()'ed\n" + "out before leaving its run function), then this greenlet will\n" + "be resumed and the return value to its switch call will be\n" + "None if no arguments are given, the given argument if one\n" + "argument is given, or the args tuple and keyword args dict if\n" + "multiple arguments are given.\n" + "\n" + "If the greenlet is dead, or is the current greenlet then this\n" + "function will simply return the arguments using the same rules as\n" + "above.\n"); + +static PyObject* +green_switch(PyGreenlet* self, PyObject* args, PyObject* kwargs) +{ + using greenlet::SwitchingArgs; + SwitchingArgs switch_args(OwnedObject::owning(args), OwnedObject::owning(kwargs)); + self->pimpl->may_switch_away(); + self->pimpl->args() <<= switch_args; + + // If we're switching out of a greenlet, and that switch is the + // last thing the greenlet does, the greenlet ought to be able to + // go ahead and die at that point. Currently, someone else must + // manually switch back to the greenlet so that we "fall off the + // end" and can perform cleanup. You'd think we'd be able to + // figure out that this is happening using the frame's ``f_lasti`` + // member, which is supposed to be an index into + // ``frame->f_code->co_code``, the bytecode string. However, in + // recent interpreters, ``f_lasti`` tends not to be updated thanks + // to things like the PREDICT() macros in ceval.c. So it doesn't + // really work to do that in many cases. For example, the Python + // code: + // def run(): + // greenlet.getcurrent().parent.switch() + // produces bytecode of len 16, with the actual call to switch() + // being at index 10 (in Python 3.10). However, the reported + // ``f_lasti`` we actually see is...5! (Which happens to be the + // second byte of the CALL_METHOD op for ``getcurrent()``). + + try { + //OwnedObject result = single_result(self->pimpl->g_switch()); + OwnedObject result(single_result(self->pimpl->g_switch())); +#ifndef NDEBUG + // Note that the current greenlet isn't necessarily self. If self + // finished, we went to one of its parents. + assert(!self->pimpl->args()); + + const BorrowedGreenlet& current = GET_THREAD_STATE().state().borrow_current(); + // It's possible it's never been switched to. + assert(!current->args()); +#endif + PyObject* p = result.relinquish_ownership(); + + if (!p && !PyErr_Occurred()) { + // This shouldn't be happening anymore, so the asserts + // are there for debug builds. Non-debug builds + // crash "gracefully" in this case, although there is an + // argument to be made for killing the process in all + // cases --- for this to be the case, our switches + // probably nested in an incorrect way, so the state is + // suspicious. Nothing should be corrupt though, just + // confused at the Python level. Letting this propagate is + // probably good enough. + assert(p || PyErr_Occurred()); + throw PyErrOccurred( + mod_globs->PyExc_GreenletError, + "Greenlet.switch() returned NULL without an exception set." + ); + } + return p; + } + catch(const PyErrOccurred&) { + return nullptr; + } +} + +PyDoc_STRVAR( + green_throw_doc, + "Switches execution to this greenlet, but immediately raises the\n" + "given exception in this greenlet. If no argument is provided, the " + "exception\n" + "defaults to `greenlet.GreenletExit`. The normal exception\n" + "propagation rules apply, as described for `switch`. Note that calling " + "this\n" + "method is almost equivalent to the following::\n" + "\n" + " def raiser():\n" + " raise typ, val, tb\n" + " g_raiser = greenlet(raiser, parent=g)\n" + " g_raiser.switch()\n" + "\n" + "except that this trick does not work for the\n" + "`greenlet.GreenletExit` exception, which would not propagate\n" + "from ``g_raiser`` to ``g``.\n"); + +static PyObject* +green_throw(PyGreenlet* self, PyObject* args) +{ + PyArgParseParam typ(mod_globs->PyExc_GreenletExit); + PyArgParseParam val; + PyArgParseParam tb; + + if (!PyArg_ParseTuple(args, "|OOO:throw", &typ, &val, &tb)) { + return nullptr; + } + + assert(typ.borrow() || val.borrow()); + + self->pimpl->may_switch_away(); + try { + // Both normalizing the error and the actual throw_greenlet + // could throw PyErrOccurred. + PyErrPieces err_pieces(typ.borrow(), val.borrow(), tb.borrow()); + + return internal_green_throw(self, err_pieces).relinquish_ownership(); + } + catch (const PyErrOccurred&) { + return nullptr; + } +} + +static int +green_bool(PyGreenlet* self) +{ + return self->pimpl->active(); +} + +/** + * CAUTION: Allocates memory, may run GC and arbitrary Python code. + */ +static PyObject* +green_getdict(PyGreenlet* self, void* UNUSED(context)) +{ + if (self->dict == NULL) { + self->dict = PyDict_New(); + if (self->dict == NULL) { + return NULL; + } + } + Py_INCREF(self->dict); + return self->dict; +} + +static int +green_setdict(PyGreenlet* self, PyObject* val, void* UNUSED(context)) +{ + PyObject* tmp; + + if (val == NULL) { + PyErr_SetString(PyExc_TypeError, "__dict__ may not be deleted"); + return -1; + } + if (!PyDict_Check(val)) { + PyErr_SetString(PyExc_TypeError, "__dict__ must be a dictionary"); + return -1; + } + tmp = self->dict; + Py_INCREF(val); + self->dict = val; + Py_XDECREF(tmp); + return 0; +} + +static bool +_green_not_dead(BorrowedGreenlet self) +{ + // XXX: Where else should we do this? + // Probably on entry to most Python-facing functions? + if (self->was_running_in_dead_thread()) { + self->deactivate_and_free(); + return false; + } + return self->active() || !self->started(); +} + + +static PyObject* +green_getdead(PyGreenlet* self, void* UNUSED(context)) +{ + if (_green_not_dead(self)) { + Py_RETURN_FALSE; + } + else { + Py_RETURN_TRUE; + } +} + +static PyObject* +green_get_stack_saved(PyGreenlet* self, void* UNUSED(context)) +{ + return PyLong_FromSsize_t(self->pimpl->stack_saved()); +} + + +static PyObject* +green_getrun(PyGreenlet* self, void* UNUSED(context)) +{ + try { + OwnedObject result(BorrowedGreenlet(self)->run()); + return result.relinquish_ownership(); + } + catch(const PyErrOccurred&) { + return nullptr; + } +} + + +static int +green_setrun(PyGreenlet* self, PyObject* nrun, void* UNUSED(context)) +{ + try { + BorrowedGreenlet(self)->run(nrun); + return 0; + } + catch(const PyErrOccurred&) { + return -1; + } +} + +static PyObject* +green_getparent(PyGreenlet* self, void* UNUSED(context)) +{ + return BorrowedGreenlet(self)->parent().acquire_or_None(); +} + + +static int +green_setparent(PyGreenlet* self, PyObject* nparent, void* UNUSED(context)) +{ + try { + BorrowedGreenlet(self)->parent(nparent); + } + catch(const PyErrOccurred&) { + return -1; + } + return 0; +} + + +static PyObject* +green_getcontext(const PyGreenlet* self, void* UNUSED(context)) +{ + const Greenlet *const g = self->pimpl; + try { + OwnedObject result(g->context()); + return result.relinquish_ownership(); + } + catch(const PyErrOccurred&) { + return nullptr; + } +} + +static int +green_setcontext(PyGreenlet* self, PyObject* nctx, void* UNUSED(context)) +{ + try { + BorrowedGreenlet(self)->context(nctx); + return 0; + } + catch(const PyErrOccurred&) { + return -1; + } +} + + +static PyObject* +green_getframe(PyGreenlet* self, void* UNUSED(context)) +{ + const PythonState::OwnedFrame& top_frame = BorrowedGreenlet(self)->top_frame(); + return top_frame.acquire_or_None(); +} + + +static PyObject* +green_getstate(PyGreenlet* self) +{ + PyErr_Format(PyExc_TypeError, + "cannot serialize '%s' object", + Py_TYPE(self)->tp_name); + return nullptr; +} + +static PyObject* +green_repr(PyGreenlet* _self) +{ + BorrowedGreenlet self(_self); + /* + Return a string like + + + The handling of greenlets across threads is not super good. + We mostly use the internal definitions of these terms, but they + generally should make sense to users as well. + */ + PyObject* result; + int never_started = !self->started() && !self->active(); + + const char* const tp_name = Py_TYPE(self)->tp_name; + + if (_green_not_dead(self)) { + /* XXX: The otid= is almost useless because you can't correlate it to + any thread identifier exposed to Python. We could use + PyThreadState_GET()->thread_id, but we'd need to save that in the + greenlet, or save the whole PyThreadState object itself. + + As it stands, its only useful for identifying greenlets from the same thread. + */ + const char* state_in_thread; + if (self->was_running_in_dead_thread()) { + // The thread it was running in is dead! + // This can happen, especially at interpreter shut down. + // It complicates debugging output because it may be + // impossible to access the current thread state at that + // time. Thus, don't access the current thread state. + state_in_thread = " (thread exited)"; + } + else { + state_in_thread = GET_THREAD_STATE().state().is_current(self) + ? " current" + : (self->started() ? " suspended" : ""); + } + result = PyUnicode_FromFormat( + "<%s object at %p (otid=%p)%s%s%s%s>", + tp_name, + self.borrow_o(), + self->thread_state(), + state_in_thread, + self->active() ? " active" : "", + never_started ? " pending" : " started", + self->main() ? " main" : "" + ); + } + else { + result = PyUnicode_FromFormat( + "<%s object at %p (otid=%p) %sdead>", + tp_name, + self.borrow_o(), + self->thread_state(), + self->was_running_in_dead_thread() + ? "(thread exited) " + : "" + ); + } + + return result; +} + + +static PyMethodDef green_methods[] = { + { + .ml_name="switch", + .ml_meth=reinterpret_cast(green_switch), + .ml_flags=METH_VARARGS | METH_KEYWORDS, + .ml_doc=green_switch_doc + }, + {.ml_name="throw", .ml_meth=(PyCFunction)green_throw, .ml_flags=METH_VARARGS, .ml_doc=green_throw_doc}, + {.ml_name="__getstate__", .ml_meth=(PyCFunction)green_getstate, .ml_flags=METH_NOARGS, .ml_doc=NULL}, + {.ml_name=NULL, .ml_meth=NULL} /* sentinel */ +}; + +static PyGetSetDef green_getsets[] = { + /* name, getter, setter, doc, context pointer */ + {.name="__dict__", .get=(getter)green_getdict, .set=(setter)green_setdict}, + {.name="run", .get=(getter)green_getrun, .set=(setter)green_setrun}, + {.name="parent", .get=(getter)green_getparent, .set=(setter)green_setparent}, + {.name="gr_frame", .get=(getter)green_getframe }, + { + .name="gr_context", + .get=(getter)green_getcontext, + .set=(setter)green_setcontext + }, + {.name="dead", .get=(getter)green_getdead}, + {.name="_stack_saved", .get=(getter)green_get_stack_saved}, + {.name=NULL} +}; + +static PyMemberDef green_members[] = { + {.name=NULL} +}; + +static PyNumberMethods green_as_number = { + .nb_bool=(inquiry)green_bool, +}; + + +PyTypeObject PyGreenlet_Type = { + .ob_base=PyVarObject_HEAD_INIT(NULL, 0) + .tp_name="greenlet.greenlet", /* tp_name */ + .tp_basicsize=sizeof(PyGreenlet), /* tp_basicsize */ + /* methods */ + .tp_dealloc=(destructor)green_dealloc, /* tp_dealloc */ + .tp_repr=(reprfunc)green_repr, /* tp_repr */ + .tp_as_number=&green_as_number, /* tp_as _number*/ + .tp_flags=G_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + .tp_doc="greenlet(run=None, parent=None) -> greenlet\n\n" + "Creates a new greenlet object (without running it).\n\n" + " - *run* -- The callable to invoke.\n" + " - *parent* -- The parent greenlet. The default is the current " + "greenlet.", /* tp_doc */ + .tp_traverse=(traverseproc)green_traverse, /* tp_traverse */ + .tp_clear=(inquiry)green_clear, /* tp_clear */ + .tp_weaklistoffset=offsetof(PyGreenlet, weakreflist), /* tp_weaklistoffset */ + + .tp_methods=green_methods, /* tp_methods */ + .tp_members=green_members, /* tp_members */ + .tp_getset=green_getsets, /* tp_getset */ + .tp_dictoffset=offsetof(PyGreenlet, dict), /* tp_dictoffset */ + .tp_init=(initproc)green_init, /* tp_init */ + .tp_alloc=PyType_GenericAlloc, /* tp_alloc */ + .tp_new=(newfunc)green_new, /* tp_new */ + .tp_free=PyObject_GC_Del, /* tp_free */ + .tp_is_gc=(inquiry)green_is_gc, /* tp_is_gc */ +}; + +#endif + +// Local Variables: +// flycheck-clang-include-path: ("/opt/local/Library/Frameworks/Python.framework/Versions/3.8/include/python3.8") +// End: diff --git a/venv/lib/python3.12/site-packages/greenlet/PyGreenlet.hpp b/venv/lib/python3.12/site-packages/greenlet/PyGreenlet.hpp new file mode 100644 index 0000000..df6cd80 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/PyGreenlet.hpp @@ -0,0 +1,35 @@ +#ifndef PYGREENLET_HPP +#define PYGREENLET_HPP + + +#include "greenlet.h" +#include "greenlet_compiler_compat.hpp" +#include "greenlet_refs.hpp" + + +using greenlet::refs::OwnedGreenlet; +using greenlet::refs::BorrowedGreenlet; +using greenlet::refs::BorrowedObject;; +using greenlet::refs::OwnedObject; +using greenlet::refs::PyErrPieces; + + +// XXX: These doesn't really belong here, it's not a Python slot. +static OwnedObject internal_green_throw(BorrowedGreenlet self, PyErrPieces& err_pieces); + +static PyGreenlet* green_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds)); +static int green_clear(PyGreenlet* self); +static int green_init(PyGreenlet* self, PyObject* args, PyObject* kwargs); +static int green_setparent(PyGreenlet* self, PyObject* nparent, void* UNUSED(context)); +static int green_setrun(PyGreenlet* self, PyObject* nrun, void* UNUSED(context)); +static int green_traverse(PyGreenlet* self, visitproc visit, void* arg); +static void green_dealloc(PyGreenlet* self); +static PyObject* green_getparent(PyGreenlet* self, void* UNUSED(context)); + +static int green_is_gc(PyObject* self); +static PyObject* green_getdead(PyGreenlet* self, void* UNUSED(context)); +static PyObject* green_getrun(PyGreenlet* self, void* UNUSED(context)); +static int green_setcontext(PyGreenlet* self, PyObject* nctx, void* UNUSED(context)); +static PyObject* green_getframe(PyGreenlet* self, void* UNUSED(context)); +static PyObject* green_repr(PyGreenlet* self); +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/PyGreenletUnswitchable.cpp b/venv/lib/python3.12/site-packages/greenlet/PyGreenletUnswitchable.cpp new file mode 100644 index 0000000..1b768ee --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/PyGreenletUnswitchable.cpp @@ -0,0 +1,147 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + Implementation of the Python slots for PyGreenletUnswitchable_Type +*/ +#ifndef PY_GREENLET_UNSWITCHABLE_CPP +#define PY_GREENLET_UNSWITCHABLE_CPP + + + +#define PY_SSIZE_T_CLEAN +#include +#include "structmember.h" // PyMemberDef + +#include "greenlet_internal.hpp" +// Code after this point can assume access to things declared in stdint.h, +// including the fixed-width types. This goes for the platform-specific switch functions +// as well. +#include "greenlet_refs.hpp" +#include "greenlet_slp_switch.hpp" + +#include "greenlet_thread_support.hpp" +#include "TGreenlet.hpp" + +#include "TGreenlet.cpp" +#include "TGreenletGlobals.cpp" +#include "TThreadStateDestroy.cpp" + + +using greenlet::LockGuard; +using greenlet::LockInitError; +using greenlet::PyErrOccurred; +using greenlet::Require; + +using greenlet::g_handle_exit; +using greenlet::single_result; + +using greenlet::Greenlet; +using greenlet::UserGreenlet; +using greenlet::MainGreenlet; +using greenlet::BrokenGreenlet; +using greenlet::ThreadState; +using greenlet::PythonState; + + +#include "PyGreenlet.hpp" + +static PyGreenlet* +green_unswitchable_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds)) +{ + PyGreenlet* o = + (PyGreenlet*)PyBaseObject_Type.tp_new(type, mod_globs->empty_tuple, mod_globs->empty_dict); + if (o) { + new BrokenGreenlet(o, GET_THREAD_STATE().state().borrow_current()); + assert(Py_REFCNT(o) == 1); + } + return o; +} + +static PyObject* +green_unswitchable_getforce(PyGreenlet* self, void* UNUSED(context)) +{ + BrokenGreenlet* broken = dynamic_cast(self->pimpl); + return PyBool_FromLong(broken->_force_switch_error); +} + +static int +green_unswitchable_setforce(PyGreenlet* self, PyObject* nforce, void* UNUSED(context)) +{ + if (!nforce) { + PyErr_SetString( + PyExc_AttributeError, + "Cannot delete force_switch_error" + ); + return -1; + } + BrokenGreenlet* broken = dynamic_cast(self->pimpl); + int is_true = PyObject_IsTrue(nforce); + if (is_true == -1) { + return -1; + } + broken->_force_switch_error = is_true; + return 0; +} + +static PyObject* +green_unswitchable_getforceslp(PyGreenlet* self, void* UNUSED(context)) +{ + BrokenGreenlet* broken = dynamic_cast(self->pimpl); + return PyBool_FromLong(broken->_force_slp_switch_error); +} + +static int +green_unswitchable_setforceslp(PyGreenlet* self, PyObject* nforce, void* UNUSED(context)) +{ + if (!nforce) { + PyErr_SetString( + PyExc_AttributeError, + "Cannot delete force_slp_switch_error" + ); + return -1; + } + BrokenGreenlet* broken = dynamic_cast(self->pimpl); + int is_true = PyObject_IsTrue(nforce); + if (is_true == -1) { + return -1; + } + broken->_force_slp_switch_error = is_true; + return 0; +} + +static PyGetSetDef green_unswitchable_getsets[] = { + /* name, getter, setter, doc, closure (context pointer) */ + { + .name="force_switch_error", + .get=(getter)green_unswitchable_getforce, + .set=(setter)green_unswitchable_setforce, + .doc=NULL + }, + { + .name="force_slp_switch_error", + .get=(getter)green_unswitchable_getforceslp, + .set=(setter)green_unswitchable_setforceslp, + .doc=nullptr + }, + {.name=nullptr} +}; + +PyTypeObject PyGreenletUnswitchable_Type = { + .ob_base=PyVarObject_HEAD_INIT(NULL, 0) + .tp_name="greenlet._greenlet.UnswitchableGreenlet", + .tp_dealloc= (destructor)green_dealloc, /* tp_dealloc */ + .tp_flags=G_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + .tp_doc="Undocumented internal class", /* tp_doc */ + .tp_traverse=(traverseproc)green_traverse, /* tp_traverse */ + .tp_clear=(inquiry)green_clear, /* tp_clear */ + + .tp_getset=green_unswitchable_getsets, /* tp_getset */ + .tp_base=&PyGreenlet_Type, /* tp_base */ + .tp_init=(initproc)green_init, /* tp_init */ + .tp_alloc=PyType_GenericAlloc, /* tp_alloc */ + .tp_new=(newfunc)green_unswitchable_new, /* tp_new */ + .tp_free=PyObject_GC_Del, /* tp_free */ + .tp_is_gc=(inquiry)green_is_gc, /* tp_is_gc */ +}; + + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/PyModule.cpp b/venv/lib/python3.12/site-packages/greenlet/PyModule.cpp new file mode 100644 index 0000000..6adcb5c --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/PyModule.cpp @@ -0,0 +1,292 @@ +#ifndef PY_MODULE_CPP +#define PY_MODULE_CPP + +#include "greenlet_internal.hpp" + + +#include "TGreenletGlobals.cpp" +#include "TMainGreenlet.cpp" +#include "TThreadStateDestroy.cpp" + +using greenlet::LockGuard; +using greenlet::ThreadState; + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-function" +# pragma clang diagnostic ignored "-Wunused-variable" +#endif + +PyDoc_STRVAR(mod_getcurrent_doc, + "getcurrent() -> greenlet\n" + "\n" + "Returns the current greenlet (i.e. the one which called this " + "function).\n"); + +static PyObject* +mod_getcurrent(PyObject* UNUSED(module)) +{ + return GET_THREAD_STATE().state().get_current().relinquish_ownership_o(); +} + +PyDoc_STRVAR(mod_settrace_doc, + "settrace(callback) -> object\n" + "\n" + "Sets a new tracing function and returns the previous one.\n"); +static PyObject* +mod_settrace(PyObject* UNUSED(module), PyObject* args) +{ + PyArgParseParam tracefunc; + if (!PyArg_ParseTuple(args, "O", &tracefunc)) { + return NULL; + } + ThreadState& state = GET_THREAD_STATE(); + OwnedObject previous = state.get_tracefunc(); + if (!previous) { + previous = Py_None; + } + + state.set_tracefunc(tracefunc); + + return previous.relinquish_ownership(); +} + +PyDoc_STRVAR(mod_gettrace_doc, + "gettrace() -> object\n" + "\n" + "Returns the currently set tracing function, or None.\n"); + +static PyObject* +mod_gettrace(PyObject* UNUSED(module)) +{ + OwnedObject tracefunc = GET_THREAD_STATE().state().get_tracefunc(); + if (!tracefunc) { + tracefunc = Py_None; + } + return tracefunc.relinquish_ownership(); +} + + + +PyDoc_STRVAR(mod_set_thread_local_doc, + "set_thread_local(key, value) -> None\n" + "\n" + "Set a value in the current thread-local dictionary. Debugging only.\n"); + +static PyObject* +mod_set_thread_local(PyObject* UNUSED(module), PyObject* args) +{ + PyArgParseParam key; + PyArgParseParam value; + PyObject* result = NULL; + + if (PyArg_UnpackTuple(args, "set_thread_local", 2, 2, &key, &value)) { + if(PyDict_SetItem( + PyThreadState_GetDict(), // borrow + key, + value) == 0 ) { + // success + Py_INCREF(Py_None); + result = Py_None; + } + } + return result; +} + +PyDoc_STRVAR(mod_get_pending_cleanup_count_doc, + "get_pending_cleanup_count() -> Integer\n" + "\n" + "Get the number of greenlet cleanup operations pending. Testing only.\n"); + + +static PyObject* +mod_get_pending_cleanup_count(PyObject* UNUSED(module)) +{ + LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock); + return PyLong_FromSize_t(mod_globs->thread_states_to_destroy.size()); +} + +PyDoc_STRVAR(mod_get_total_main_greenlets_doc, + "get_total_main_greenlets() -> Integer\n" + "\n" + "Quickly return the number of main greenlets that exist. Testing only.\n"); + +static PyObject* +mod_get_total_main_greenlets(PyObject* UNUSED(module)) +{ + return PyLong_FromSize_t(G_TOTAL_MAIN_GREENLETS); +} + + + +PyDoc_STRVAR(mod_get_clocks_used_doing_optional_cleanup_doc, + "get_clocks_used_doing_optional_cleanup() -> Integer\n" + "\n" + "Get the number of clock ticks the program has used doing optional " + "greenlet cleanup.\n" + "Beginning in greenlet 2.0, greenlet tries to find and dispose of greenlets\n" + "that leaked after a thread exited. This requires invoking Python's garbage collector,\n" + "which may have a performance cost proportional to the number of live objects.\n" + "This function returns the amount of processor time\n" + "greenlet has used to do this. In programs that run with very large amounts of live\n" + "objects, this metric can be used to decide whether the cost of doing this cleanup\n" + "is worth the memory leak being corrected. If not, you can disable the cleanup\n" + "using ``enable_optional_cleanup(False)``.\n" + "The units are arbitrary and can only be compared to themselves (similarly to ``time.clock()``);\n" + "for example, to see how it scales with your heap. You can attempt to convert them into seconds\n" + "by dividing by the value of CLOCKS_PER_SEC." + "If cleanup has been disabled, returns None." + "\n" + "This is an implementation specific, provisional API. It may be changed or removed\n" + "in the future.\n" + ".. versionadded:: 2.0" + ); +static PyObject* +mod_get_clocks_used_doing_optional_cleanup(PyObject* UNUSED(module)) +{ + std::clock_t& clocks = ThreadState::clocks_used_doing_gc(); + + if (clocks == std::clock_t(-1)) { + Py_RETURN_NONE; + } + // This might not actually work on some implementations; clock_t + // is an opaque type. + return PyLong_FromSsize_t(clocks); +} + +PyDoc_STRVAR(mod_enable_optional_cleanup_doc, + "mod_enable_optional_cleanup(bool) -> None\n" + "\n" + "Enable or disable optional cleanup operations.\n" + "See ``get_clocks_used_doing_optional_cleanup()`` for details.\n" + ); +static PyObject* +mod_enable_optional_cleanup(PyObject* UNUSED(module), PyObject* flag) +{ + int is_true = PyObject_IsTrue(flag); + if (is_true == -1) { + return nullptr; + } + + std::clock_t& clocks = ThreadState::clocks_used_doing_gc(); + if (is_true) { + // If we already have a value, we don't want to lose it. + if (clocks == std::clock_t(-1)) { + clocks = 0; + } + } + else { + clocks = std::clock_t(-1); + } + Py_RETURN_NONE; +} + + + + +#if !GREENLET_PY313 +PyDoc_STRVAR(mod_get_tstate_trash_delete_nesting_doc, + "get_tstate_trash_delete_nesting() -> Integer\n" + "\n" + "Return the 'trash can' nesting level. Testing only.\n"); +static PyObject* +mod_get_tstate_trash_delete_nesting(PyObject* UNUSED(module)) +{ + PyThreadState* tstate = PyThreadState_GET(); + +#if GREENLET_PY312 + return PyLong_FromLong(tstate->trash.delete_nesting); +#else + return PyLong_FromLong(tstate->trash_delete_nesting); +#endif +} +#endif + + + + +static PyMethodDef GreenMethods[] = { + { + .ml_name="getcurrent", + .ml_meth=(PyCFunction)mod_getcurrent, + .ml_flags=METH_NOARGS, + .ml_doc=mod_getcurrent_doc + }, + { + .ml_name="settrace", + .ml_meth=(PyCFunction)mod_settrace, + .ml_flags=METH_VARARGS, + .ml_doc=mod_settrace_doc + }, + { + .ml_name="gettrace", + .ml_meth=(PyCFunction)mod_gettrace, + .ml_flags=METH_NOARGS, + .ml_doc=mod_gettrace_doc + }, + { + .ml_name="set_thread_local", + .ml_meth=(PyCFunction)mod_set_thread_local, + .ml_flags=METH_VARARGS, + .ml_doc=mod_set_thread_local_doc + }, + { + .ml_name="get_pending_cleanup_count", + .ml_meth=(PyCFunction)mod_get_pending_cleanup_count, + .ml_flags=METH_NOARGS, + .ml_doc=mod_get_pending_cleanup_count_doc + }, + { + .ml_name="get_total_main_greenlets", + .ml_meth=(PyCFunction)mod_get_total_main_greenlets, + .ml_flags=METH_NOARGS, + .ml_doc=mod_get_total_main_greenlets_doc + }, + { + .ml_name="get_clocks_used_doing_optional_cleanup", + .ml_meth=(PyCFunction)mod_get_clocks_used_doing_optional_cleanup, + .ml_flags=METH_NOARGS, + .ml_doc=mod_get_clocks_used_doing_optional_cleanup_doc + }, + { + .ml_name="enable_optional_cleanup", + .ml_meth=(PyCFunction)mod_enable_optional_cleanup, + .ml_flags=METH_O, + .ml_doc=mod_enable_optional_cleanup_doc + }, +#if !GREENLET_PY313 + { + .ml_name="get_tstate_trash_delete_nesting", + .ml_meth=(PyCFunction)mod_get_tstate_trash_delete_nesting, + .ml_flags=METH_NOARGS, + .ml_doc=mod_get_tstate_trash_delete_nesting_doc + }, +#endif + {.ml_name=NULL, .ml_meth=NULL} /* Sentinel */ +}; + +static const char* const copy_on_greentype[] = { + "getcurrent", + "error", + "GreenletExit", + "settrace", + "gettrace", + NULL +}; + +static struct PyModuleDef greenlet_module_def = { + .m_base=PyModuleDef_HEAD_INIT, + .m_name="greenlet._greenlet", + .m_doc=NULL, + .m_size=-1, + .m_methods=GreenMethods, +}; + + +#endif + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/TBrokenGreenlet.cpp b/venv/lib/python3.12/site-packages/greenlet/TBrokenGreenlet.cpp new file mode 100644 index 0000000..7e9ab5b --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/TBrokenGreenlet.cpp @@ -0,0 +1,45 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + * Implementation of greenlet::UserGreenlet. + * + * Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ + +#include "TGreenlet.hpp" + +namespace greenlet { + +void* BrokenGreenlet::operator new(size_t UNUSED(count)) +{ + return allocator.allocate(1); +} + + +void BrokenGreenlet::operator delete(void* ptr) +{ + return allocator.deallocate(static_cast(ptr), + 1); +} + +greenlet::PythonAllocator greenlet::BrokenGreenlet::allocator; + +bool +BrokenGreenlet::force_slp_switch_error() const noexcept +{ + return this->_force_slp_switch_error; +} + +UserGreenlet::switchstack_result_t BrokenGreenlet::g_switchstack(void) +{ + if (this->_force_switch_error) { + return switchstack_result_t(-1); + } + return UserGreenlet::g_switchstack(); +} + +}; //namespace greenlet diff --git a/venv/lib/python3.12/site-packages/greenlet/TExceptionState.cpp b/venv/lib/python3.12/site-packages/greenlet/TExceptionState.cpp new file mode 100644 index 0000000..08a94ae --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/TExceptionState.cpp @@ -0,0 +1,62 @@ +#ifndef GREENLET_EXCEPTION_STATE_CPP +#define GREENLET_EXCEPTION_STATE_CPP + +#include +#include "TGreenlet.hpp" + +namespace greenlet { + + +ExceptionState::ExceptionState() +{ + this->clear(); +} + +void ExceptionState::operator<<(const PyThreadState *const tstate) noexcept +{ + this->exc_info = tstate->exc_info; + this->exc_state = tstate->exc_state; +} + +void ExceptionState::operator>>(PyThreadState *const tstate) noexcept +{ + tstate->exc_state = this->exc_state; + tstate->exc_info = + this->exc_info ? this->exc_info : &tstate->exc_state; + this->clear(); +} + +void ExceptionState::clear() noexcept +{ + this->exc_info = nullptr; + this->exc_state.exc_value = nullptr; +#if !GREENLET_PY311 + this->exc_state.exc_type = nullptr; + this->exc_state.exc_traceback = nullptr; +#endif + this->exc_state.previous_item = nullptr; +} + +int ExceptionState::tp_traverse(visitproc visit, void* arg) noexcept +{ + Py_VISIT(this->exc_state.exc_value); +#if !GREENLET_PY311 + Py_VISIT(this->exc_state.exc_type); + Py_VISIT(this->exc_state.exc_traceback); +#endif + return 0; +} + +void ExceptionState::tp_clear() noexcept +{ + Py_CLEAR(this->exc_state.exc_value); +#if !GREENLET_PY311 + Py_CLEAR(this->exc_state.exc_type); + Py_CLEAR(this->exc_state.exc_traceback); +#endif +} + + +}; // namespace greenlet + +#endif // GREENLET_EXCEPTION_STATE_CPP diff --git a/venv/lib/python3.12/site-packages/greenlet/TGreenlet.cpp b/venv/lib/python3.12/site-packages/greenlet/TGreenlet.cpp new file mode 100644 index 0000000..d12722b --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/TGreenlet.cpp @@ -0,0 +1,719 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + * Implementation of greenlet::Greenlet. + * + * Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ +#ifndef TGREENLET_CPP +#define TGREENLET_CPP +#include "greenlet_internal.hpp" +#include "TGreenlet.hpp" + + +#include "TGreenletGlobals.cpp" +#include "TThreadStateDestroy.cpp" + +namespace greenlet { + +Greenlet::Greenlet(PyGreenlet* p) + : Greenlet(p, StackState()) +{ +} + +Greenlet::Greenlet(PyGreenlet* p, const StackState& initial_stack) + : _self(p), stack_state(initial_stack) +{ + assert(p->pimpl == nullptr); + p->pimpl = this; +} + +Greenlet::~Greenlet() +{ + // XXX: Can't do this. tp_clear is a virtual function, and by the + // time we're here, we've sliced off our child classes. + //this->tp_clear(); + this->_self->pimpl = nullptr; +} + +bool +Greenlet::force_slp_switch_error() const noexcept +{ + return false; +} + +void +Greenlet::release_args() +{ + this->switch_args.CLEAR(); +} + +/** + * CAUTION: This will allocate memory and may trigger garbage + * collection and arbitrary Python code. + */ +OwnedObject +Greenlet::throw_GreenletExit_during_dealloc(const ThreadState& UNUSED(current_thread_state)) +{ + // If we're killed because we lost all references in the + // middle of a switch, that's ok. Don't reset the args/kwargs, + // we still want to pass them to the parent. + PyErr_SetString(mod_globs->PyExc_GreenletExit, + "Killing the greenlet because all references have vanished."); + // To get here it had to have run before + return this->g_switch(); +} + +inline void +Greenlet::slp_restore_state() noexcept +{ +#ifdef SLP_BEFORE_RESTORE_STATE + SLP_BEFORE_RESTORE_STATE(); +#endif + this->stack_state.copy_heap_to_stack( + this->thread_state()->borrow_current()->stack_state); +} + + +inline int +Greenlet::slp_save_state(char *const stackref) noexcept +{ + // XXX: This used to happen in the middle, before saving, but + // after finding the next owner. Does that matter? This is + // only defined for Sparc/GCC where it flushes register + // windows to the stack (I think) +#ifdef SLP_BEFORE_SAVE_STATE + SLP_BEFORE_SAVE_STATE(); +#endif + return this->stack_state.copy_stack_to_heap(stackref, + this->thread_state()->borrow_current()->stack_state); +} + +/** + * CAUTION: This will allocate memory and may trigger garbage + * collection and arbitrary Python code. + */ +OwnedObject +Greenlet::on_switchstack_or_initialstub_failure( + Greenlet* target, + const Greenlet::switchstack_result_t& err, + const bool target_was_me, + const bool was_initial_stub) +{ + // If we get here, either g_initialstub() + // failed, or g_switchstack() failed. Either one of those + // cases SHOULD leave us in the original greenlet with a valid stack. + if (!PyErr_Occurred()) { + PyErr_SetString( + PyExc_SystemError, + was_initial_stub + ? "Failed to switch stacks into a greenlet for the first time." + : "Failed to switch stacks into a running greenlet."); + } + this->release_args(); + + if (target && !target_was_me) { + target->murder_in_place(); + } + + assert(!err.the_new_current_greenlet); + assert(!err.origin_greenlet); + return OwnedObject(); + +} + +OwnedGreenlet +Greenlet::g_switchstack_success() noexcept +{ + PyThreadState* tstate = PyThreadState_GET(); + // restore the saved state + this->python_state >> tstate; + this->exception_state >> tstate; + + // The thread state hasn't been changed yet. + ThreadState* thread_state = this->thread_state(); + OwnedGreenlet result(thread_state->get_current()); + thread_state->set_current(this->self()); + //assert(thread_state->borrow_current().borrow() == this->_self); + return result; +} + +Greenlet::switchstack_result_t +Greenlet::g_switchstack(void) +{ + // if any of these assertions fail, it's likely because we + // switched away and tried to switch back to us. Early stages of + // switching are not reentrant because we re-use ``this->args()``. + // Switching away would happen if we trigger a garbage collection + // (by just using some Python APIs that happen to allocate Python + // objects) and some garbage had weakref callbacks or __del__ that + // switches (people don't write code like that by hand, but with + // gevent it's possible without realizing it) + assert(this->args() || PyErr_Occurred()); + { /* save state */ + if (this->thread_state()->is_current(this->self())) { + // Hmm, nothing to do. + // TODO: Does this bypass trace events that are + // important? + return switchstack_result_t(0, + this, this->thread_state()->borrow_current()); + } + BorrowedGreenlet current = this->thread_state()->borrow_current(); + PyThreadState* tstate = PyThreadState_GET(); + + current->python_state << tstate; + current->exception_state << tstate; + this->python_state.will_switch_from(tstate); + switching_thread_state = this; + current->expose_frames(); + } + assert(this->args() || PyErr_Occurred()); + // If this is the first switch into a greenlet, this will + // return twice, once with 1 in the new greenlet, once with 0 + // in the origin. + int err; + if (this->force_slp_switch_error()) { + err = -1; + } + else { + err = slp_switch(); + } + + if (err < 0) { /* error */ + // Tested by + // test_greenlet.TestBrokenGreenlets.test_failed_to_slp_switch_into_running + // + // It's not clear if it's worth trying to clean up and + // continue here. Failing to switch stacks is a big deal which + // may not be recoverable (who knows what state the stack is in). + // Also, we've stolen references in preparation for calling + // ``g_switchstack_success()`` and we don't have a clean + // mechanism for backing that all out. + Py_FatalError("greenlet: Failed low-level slp_switch(). The stack is probably corrupt."); + } + + // No stack-based variables are valid anymore. + + // But the global is volatile so we can reload it without the + // compiler caching it from earlier. + Greenlet* greenlet_that_switched_in = switching_thread_state; // aka this + switching_thread_state = nullptr; + // except that no stack variables are valid, we would: + // assert(this == greenlet_that_switched_in); + + // switchstack success is where we restore the exception state, + // etc. It returns the origin greenlet because its convenient. + + OwnedGreenlet origin = greenlet_that_switched_in->g_switchstack_success(); + assert(greenlet_that_switched_in->args() || PyErr_Occurred()); + return switchstack_result_t(err, greenlet_that_switched_in, origin); +} + + +inline void +Greenlet::check_switch_allowed() const +{ + // TODO: Make this take a parameter of the current greenlet, + // or current main greenlet, to make the check for + // cross-thread switching cheaper. Surely somewhere up the + // call stack we've already accessed the thread local variable. + + // We expect to always have a main greenlet now; accessing the thread state + // created it. However, if we get here and cleanup has already + // begun because we're a greenlet that was running in a + // (now dead) thread, these invariants will not hold true. In + // fact, accessing `this->thread_state` may not even be possible. + + // If the thread this greenlet was running in is dead, + // we'll still have a reference to a main greenlet, but the + // thread state pointer we have is bogus. + // TODO: Give the objects an API to determine if they belong + // to a dead thread. + + const BorrowedMainGreenlet main_greenlet = this->find_main_greenlet_in_lineage(); + + if (!main_greenlet) { + throw PyErrOccurred(mod_globs->PyExc_GreenletError, + "cannot switch to a garbage collected greenlet"); + } + + if (!main_greenlet->thread_state()) { + throw PyErrOccurred(mod_globs->PyExc_GreenletError, + "cannot switch to a different thread (which happens to have exited)"); + } + + // The main greenlet we found was from the .parent lineage. + // That may or may not have any relationship to the main + // greenlet of the running thread. We can't actually access + // our this->thread_state members to try to check that, + // because it could be in the process of getting destroyed, + // but setting the main_greenlet->thread_state member to NULL + // may not be visible yet. So we need to check against the + // current thread state (once the cheaper checks are out of + // the way) + const BorrowedMainGreenlet current_main_greenlet = GET_THREAD_STATE().state().borrow_main_greenlet(); + if ( + // lineage main greenlet is not this thread's greenlet + current_main_greenlet != main_greenlet + || ( + // atteched to some thread + this->main_greenlet() + // XXX: Same condition as above. Was this supposed to be + // this->main_greenlet()? + && current_main_greenlet != main_greenlet) + // switching into a known dead thread (XXX: which, if we get here, + // is bad, because we just accessed the thread state, which is + // gone!) + || (!current_main_greenlet->thread_state())) { + // CAUTION: This may trigger memory allocations, gc, and + // arbitrary Python code. + throw PyErrOccurred( + mod_globs->PyExc_GreenletError, + "Cannot switch to a different thread\n\tCurrent: %R\n\tExpected: %R", + current_main_greenlet, main_greenlet); + } +} + +const OwnedObject +Greenlet::context() const +{ + using greenlet::PythonStateContext; + OwnedObject result; + + if (this->is_currently_running_in_some_thread()) { + /* Currently running greenlet: context is stored in the thread state, + not the greenlet object. */ + if (GET_THREAD_STATE().state().is_current(this->self())) { + result = PythonStateContext::context(PyThreadState_GET()); + } + else { + throw ValueError( + "cannot get context of a " + "greenlet that is running in a different thread"); + } + } + else { + /* Greenlet is not running: just return context. */ + result = this->python_state.context(); + } + if (!result) { + result = OwnedObject::None(); + } + return result; +} + + +void +Greenlet::context(BorrowedObject given) +{ + using greenlet::PythonStateContext; + if (!given) { + throw AttributeError("can't delete context attribute"); + } + if (given.is_None()) { + /* "Empty context" is stored as NULL, not None. */ + given = nullptr; + } + + //checks type, incrs refcnt + greenlet::refs::OwnedContext context(given); + PyThreadState* tstate = PyThreadState_GET(); + + if (this->is_currently_running_in_some_thread()) { + if (!GET_THREAD_STATE().state().is_current(this->self())) { + throw ValueError("cannot set context of a greenlet" + " that is running in a different thread"); + } + + /* Currently running greenlet: context is stored in the thread state, + not the greenlet object. */ + OwnedObject octx = OwnedObject::consuming(PythonStateContext::context(tstate)); + PythonStateContext::context(tstate, context.relinquish_ownership()); + } + else { + /* Greenlet is not running: just set context. Note that the + greenlet may be dead.*/ + this->python_state.context() = context; + } +} + +/** + * CAUTION: May invoke arbitrary Python code. + * + * Figure out what the result of ``greenlet.switch(arg, kwargs)`` + * should be and transfers ownership of it to the left-hand-side. + * + * If switch() was just passed an arg tuple, then we'll just return that. + * If only keyword arguments were passed, then we'll pass the keyword + * argument dict. Otherwise, we'll create a tuple of (args, kwargs) and + * return both. + * + * CAUTION: This may allocate a new tuple object, which may + * cause the Python garbage collector to run, which in turn may + * run arbitrary Python code that switches. + */ +OwnedObject& operator<<=(OwnedObject& lhs, greenlet::SwitchingArgs& rhs) noexcept +{ + // Because this may invoke arbitrary Python code, which could + // result in switching back to us, we need to get the + // arguments locally on the stack. + assert(rhs); + OwnedObject args = rhs.args(); + OwnedObject kwargs = rhs.kwargs(); + rhs.CLEAR(); + // We shouldn't be called twice for the same switch. + assert(args || kwargs); + assert(!rhs); + + if (!kwargs) { + lhs = args; + } + else if (!PyDict_Size(kwargs.borrow())) { + lhs = args; + } + else if (!PySequence_Length(args.borrow())) { + lhs = kwargs; + } + else { + // PyTuple_Pack allocates memory, may GC, may run arbitrary + // Python code. + lhs = OwnedObject::consuming(PyTuple_Pack(2, args.borrow(), kwargs.borrow())); + } + return lhs; +} + +static OwnedObject +g_handle_exit(const OwnedObject& greenlet_result) +{ + if (!greenlet_result && mod_globs->PyExc_GreenletExit.PyExceptionMatches()) { + /* catch and ignore GreenletExit */ + PyErrFetchParam val; + PyErr_Fetch(PyErrFetchParam(), val, PyErrFetchParam()); + if (!val) { + return OwnedObject::None(); + } + return OwnedObject(val); + } + + if (greenlet_result) { + // package the result into a 1-tuple + // PyTuple_Pack increments the reference of its arguments, + // so we always need to decref the greenlet result; + // the owner will do that. + return OwnedObject::consuming(PyTuple_Pack(1, greenlet_result.borrow())); + } + + return OwnedObject(); +} + + + +/** + * May run arbitrary Python code. + */ +OwnedObject +Greenlet::g_switch_finish(const switchstack_result_t& err) +{ + assert(err.the_new_current_greenlet == this); + + ThreadState& state = *this->thread_state(); + // Because calling the trace function could do arbitrary things, + // including switching away from this greenlet and then maybe + // switching back, we need to capture the arguments now so that + // they don't change. + OwnedObject result; + if (this->args()) { + result <<= this->args(); + } + else { + assert(PyErr_Occurred()); + } + assert(!this->args()); + try { + // Our only caller handles the bad error case + assert(err.status >= 0); + assert(state.borrow_current() == this->self()); + if (OwnedObject tracefunc = state.get_tracefunc()) { + assert(result || PyErr_Occurred()); + g_calltrace(tracefunc, + result ? mod_globs->event_switch : mod_globs->event_throw, + err.origin_greenlet, + this->self()); + } + // The above could have invoked arbitrary Python code, but + // it couldn't switch back to this object and *also* + // throw an exception, so the args won't have changed. + + if (PyErr_Occurred()) { + // We get here if we fell of the end of the run() function + // raising an exception. The switch itself was + // successful, but the function raised. + // valgrind reports that memory allocated here can still + // be reached after a test run. + throw PyErrOccurred::from_current(); + } + return result; + } + catch (const PyErrOccurred&) { + /* Turn switch errors into switch throws */ + /* Turn trace errors into switch throws */ + this->release_args(); + throw; + } +} + +void +Greenlet::g_calltrace(const OwnedObject& tracefunc, + const greenlet::refs::ImmortalEventName& event, + const BorrowedGreenlet& origin, + const BorrowedGreenlet& target) +{ + PyErrPieces saved_exc; + try { + TracingGuard tracing_guard; + // TODO: We have saved the active exception (if any) that's + // about to be raised. In the 'throw' case, we could provide + // the exception to the tracefunction, which seems very helpful. + tracing_guard.CallTraceFunction(tracefunc, event, origin, target); + } + catch (const PyErrOccurred&) { + // In case of exceptions trace function is removed, + // and any existing exception is replaced with the tracing + // exception. + GET_THREAD_STATE().state().set_tracefunc(Py_None); + throw; + } + + saved_exc.PyErrRestore(); + assert( + (event == mod_globs->event_throw && PyErr_Occurred()) + || (event == mod_globs->event_switch && !PyErr_Occurred()) + ); +} + +void +Greenlet::murder_in_place() +{ + if (this->active()) { + assert(!this->is_currently_running_in_some_thread()); + this->deactivate_and_free(); + } +} + +inline void +Greenlet::deactivate_and_free() +{ + if (!this->active()) { + return; + } + // Throw away any saved stack. + this->stack_state = StackState(); + assert(!this->stack_state.active()); + // Throw away any Python references. + // We're holding a borrowed reference to the last + // frame we executed. Since we borrowed it, the + // normal traversal, clear, and dealloc functions + // ignore it, meaning it leaks. (The thread state + // object can't find it to clear it when that's + // deallocated either, because by definition if we + // got an object on this list, it wasn't + // running and the thread state doesn't have + // this frame.) + // So here, we *do* clear it. + this->python_state.tp_clear(true); +} + +bool +Greenlet::belongs_to_thread(const ThreadState* thread_state) const +{ + if (!this->thread_state() // not running anywhere, or thread + // exited + || !thread_state) { // same, or there is no thread state. + return false; + } + return true; +} + + +void +Greenlet::deallocing_greenlet_in_thread(const ThreadState* current_thread_state) +{ + /* Cannot raise an exception to kill the greenlet if + it is not running in the same thread! */ + if (this->belongs_to_thread(current_thread_state)) { + assert(current_thread_state); + // To get here it had to have run before + /* Send the greenlet a GreenletExit exception. */ + + // We don't care about the return value, only whether an + // exception happened. + this->throw_GreenletExit_during_dealloc(*current_thread_state); + return; + } + + // Not the same thread! Temporarily save the greenlet + // into its thread's deleteme list, *if* it exists. + // If that thread has already exited, and processed its pending + // cleanup, we'll never be able to clean everything up: we won't + // be able to raise an exception. + // That's mostly OK! Since we can't add it to a list, our refcount + // won't increase, and we'll go ahead with the DECREFs later. + + ThreadState *const thread_state = this->thread_state(); + if (thread_state) { + thread_state->delete_when_thread_running(this->self()); + } + else { + // The thread is dead, we can't raise an exception. + // We need to make it look non-active, though, so that dealloc + // finishes killing it. + this->deactivate_and_free(); + } + return; +} + + +int +Greenlet::tp_traverse(visitproc visit, void* arg) +{ + + int result; + if ((result = this->exception_state.tp_traverse(visit, arg)) != 0) { + return result; + } + //XXX: This is ugly. But so is handling everything having to do + //with the top frame. + bool visit_top_frame = this->was_running_in_dead_thread(); + // When true, the thread is dead. Our implicit weak reference to the + // frame is now all that's left; we consider ourselves to + // strongly own it now. + if ((result = this->python_state.tp_traverse(visit, arg, visit_top_frame)) != 0) { + return result; + } + return 0; +} + +int +Greenlet::tp_clear() +{ + bool own_top_frame = this->was_running_in_dead_thread(); + this->exception_state.tp_clear(); + this->python_state.tp_clear(own_top_frame); + return 0; +} + +bool Greenlet::is_currently_running_in_some_thread() const +{ + return this->stack_state.active() && !this->python_state.top_frame(); +} + +#if GREENLET_PY312 +void GREENLET_NOINLINE(Greenlet::expose_frames)() +{ + if (!this->python_state.top_frame()) { + return; + } + + _PyInterpreterFrame* last_complete_iframe = nullptr; + _PyInterpreterFrame* iframe = this->python_state.top_frame()->f_frame; + while (iframe) { + // We must make a copy before looking at the iframe contents, + // since iframe might point to a portion of the greenlet's C stack + // that was spilled when switching greenlets. + _PyInterpreterFrame iframe_copy; + this->stack_state.copy_from_stack(&iframe_copy, iframe, sizeof(*iframe)); + if (!_PyFrame_IsIncomplete(&iframe_copy)) { + // If the iframe were OWNED_BY_CSTACK then it would always be + // incomplete. Since it's not incomplete, it's not on the C stack + // and we can access it through the original `iframe` pointer + // directly. This is important since GetFrameObject might + // lazily _create_ the frame object and we don't want the + // interpreter to lose track of it. + assert(iframe_copy.owner != FRAME_OWNED_BY_CSTACK); + + // We really want to just write: + // PyFrameObject* frame = _PyFrame_GetFrameObject(iframe); + // but _PyFrame_GetFrameObject calls _PyFrame_MakeAndSetFrameObject + // which is not a visible symbol in libpython. The easiest + // way to get a public function to call it is using + // PyFrame_GetBack, which is defined as follows: + // assert(frame != NULL); + // assert(!_PyFrame_IsIncomplete(frame->f_frame)); + // PyFrameObject *back = frame->f_back; + // if (back == NULL) { + // _PyInterpreterFrame *prev = frame->f_frame->previous; + // prev = _PyFrame_GetFirstComplete(prev); + // if (prev) { + // back = _PyFrame_GetFrameObject(prev); + // } + // } + // return (PyFrameObject*)Py_XNewRef(back); + if (!iframe->frame_obj) { + PyFrameObject dummy_frame; + _PyInterpreterFrame dummy_iframe; + dummy_frame.f_back = nullptr; + dummy_frame.f_frame = &dummy_iframe; + // force the iframe to be considered complete without + // needing to check its code object: + dummy_iframe.owner = FRAME_OWNED_BY_GENERATOR; + dummy_iframe.previous = iframe; + assert(!_PyFrame_IsIncomplete(&dummy_iframe)); + // Drop the returned reference immediately; the iframe + // continues to hold a strong reference + Py_XDECREF(PyFrame_GetBack(&dummy_frame)); + assert(iframe->frame_obj); + } + + // This is a complete frame, so make the last one of those we saw + // point at it, bypassing any incomplete frames (which may have + // been on the C stack) in between the two. We're overwriting + // last_complete_iframe->previous and need that to be reversible, + // so we store the original previous ptr in the frame object + // (which we must have created on a previous iteration through + // this loop). The frame object has a bunch of storage that is + // only used when its iframe is OWNED_BY_FRAME_OBJECT, which only + // occurs when the frame object outlives the frame's execution, + // which can't have happened yet because the frame is currently + // executing as far as the interpreter is concerned. So, we can + // reuse it for our own purposes. + assert(iframe->owner == FRAME_OWNED_BY_THREAD + || iframe->owner == FRAME_OWNED_BY_GENERATOR); + if (last_complete_iframe) { + assert(last_complete_iframe->frame_obj); + memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0], + &last_complete_iframe->previous, sizeof(void *)); + last_complete_iframe->previous = iframe; + } + last_complete_iframe = iframe; + } + // Frames that are OWNED_BY_FRAME_OBJECT are linked via the + // frame's f_back while all others are linked via the iframe's + // previous ptr. Since all the frames we traverse are running + // as far as the interpreter is concerned, we don't have to + // worry about the OWNED_BY_FRAME_OBJECT case. + iframe = iframe_copy.previous; + } + + // Give the outermost complete iframe a null previous pointer to + // account for any potential incomplete/C-stack iframes between it + // and the actual top-of-stack + if (last_complete_iframe) { + assert(last_complete_iframe->frame_obj); + memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0], + &last_complete_iframe->previous, sizeof(void *)); + last_complete_iframe->previous = nullptr; + } +} +#else +void Greenlet::expose_frames() +{ + +} +#endif + +}; // namespace greenlet +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/TGreenlet.hpp b/venv/lib/python3.12/site-packages/greenlet/TGreenlet.hpp new file mode 100644 index 0000000..e152353 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/TGreenlet.hpp @@ -0,0 +1,830 @@ +#ifndef GREENLET_GREENLET_HPP +#define GREENLET_GREENLET_HPP +/* + * Declarations of the core data structures. +*/ + +#define PY_SSIZE_T_CLEAN +#include + +#include "greenlet_compiler_compat.hpp" +#include "greenlet_refs.hpp" +#include "greenlet_cpython_compat.hpp" +#include "greenlet_allocator.hpp" + +using greenlet::refs::OwnedObject; +using greenlet::refs::OwnedGreenlet; +using greenlet::refs::OwnedMainGreenlet; +using greenlet::refs::BorrowedGreenlet; + +#if PY_VERSION_HEX < 0x30B00A6 +# define _PyCFrame CFrame +# define _PyInterpreterFrame _interpreter_frame +#endif + +#if GREENLET_PY312 +# define Py_BUILD_CORE +# include "internal/pycore_frame.h" +#endif + +#if GREENLET_PY314 +# include "internal/pycore_interpframe_structs.h" +#if defined(_MSC_VER) || defined(__MINGW64__) +# include "greenlet_msvc_compat.hpp" +#else +# include "internal/pycore_interpframe.h" +#endif +#endif + +// XXX: TODO: Work to remove all virtual functions +// for speed of calling and size of objects (no vtable). +// One pattern is the Curiously Recurring Template +namespace greenlet +{ + class ExceptionState + { + private: + G_NO_COPIES_OF_CLS(ExceptionState); + + // Even though these are borrowed objects, we actually own + // them, when they're not null. + // XXX: Express that in the API. + private: + _PyErr_StackItem* exc_info; + _PyErr_StackItem exc_state; + public: + ExceptionState(); + void operator<<(const PyThreadState *const tstate) noexcept; + void operator>>(PyThreadState* tstate) noexcept; + void clear() noexcept; + + int tp_traverse(visitproc visit, void* arg) noexcept; + void tp_clear() noexcept; + }; + + template + void operator<<(const PyThreadState *const tstate, T& exc); + + class PythonStateContext + { + protected: + greenlet::refs::OwnedContext _context; + public: + inline const greenlet::refs::OwnedContext& context() const + { + return this->_context; + } + inline greenlet::refs::OwnedContext& context() + { + return this->_context; + } + + inline void tp_clear() + { + this->_context.CLEAR(); + } + + template + inline static PyObject* context(T* tstate) + { + return tstate->context; + } + + template + inline static void context(T* tstate, PyObject* new_context) + { + tstate->context = new_context; + tstate->context_ver++; + } + }; + class SwitchingArgs; + class PythonState : public PythonStateContext + { + public: + typedef greenlet::refs::OwnedReference OwnedFrame; + private: + G_NO_COPIES_OF_CLS(PythonState); + // We own this if we're suspended (although currently we don't + // tp_traverse into it; that's a TODO). If we're running, it's + // empty. If we get deallocated and *still* have a frame, it + // won't be reachable from the place that normally decref's + // it, so we need to do it (hence owning it). + OwnedFrame _top_frame; +#if GREENLET_USE_CFRAME + _PyCFrame* cframe; + int use_tracing; +#endif +#if GREENLET_PY314 + int py_recursion_depth; + // I think this is only used by the JIT. At least, + // we only got errors not switching it when the JIT was enabled. + // Python/generated_cases.c.h:12469: _PyEval_EvalFrameDefault: + // Assertion `tstate->current_executor == NULL' failed. + // see https://github.com/python-greenlet/greenlet/issues/460 + PyObject* current_executor; +#elif GREENLET_PY312 + int py_recursion_depth; + int c_recursion_depth; +#else + int recursion_depth; +#endif +#if GREENLET_PY313 + PyObject *delete_later; +#else + int trash_delete_nesting; +#endif +#if GREENLET_PY311 + _PyInterpreterFrame* current_frame; + _PyStackChunk* datastack_chunk; + PyObject** datastack_top; + PyObject** datastack_limit; +#endif + // The PyInterpreterFrame list on 3.12+ contains some entries that are + // on the C stack, which can't be directly accessed while a greenlet is + // suspended. In order to keep greenlet gr_frame introspection working, + // we adjust stack switching to rewrite the interpreter frame list + // to skip these C-stack frames; we call this "exposing" the greenlet's + // frames because it makes them valid to work with in Python. Then when + // the greenlet is resumed we need to remember to reverse the operation + // we did. The C-stack frames are "entry frames" which are a low-level + // interpreter detail; they're not needed for introspection, but do + // need to be present for the eval loop to work. + void unexpose_frames(); + + public: + + PythonState(); + // You can use this for testing whether we have a frame + // or not. It returns const so they can't modify it. + const OwnedFrame& top_frame() const noexcept; + + inline void operator<<(const PyThreadState *const tstate) noexcept; + inline void operator>>(PyThreadState* tstate) noexcept; + void clear() noexcept; + + int tp_traverse(visitproc visit, void* arg, bool visit_top_frame) noexcept; + void tp_clear(bool own_top_frame) noexcept; + void set_initial_state(const PyThreadState* const tstate) noexcept; +#if GREENLET_USE_CFRAME + void set_new_cframe(_PyCFrame& frame) noexcept; +#endif + + void may_switch_away() noexcept; + inline void will_switch_from(PyThreadState *const origin_tstate) noexcept; + void did_finish(PyThreadState* tstate) noexcept; + }; + + class StackState + { + // By having only plain C (POD) members, no virtual functions + // or bases, we get a trivial assignment operator generated + // for us. However, that's not safe since we do manage memory. + // So we declare an assignment operator that only works if we + // don't have any memory allocated. (We don't use + // std::shared_ptr for reference counting just to keep this + // object small) + private: + char* _stack_start; + char* stack_stop; + char* stack_copy; + intptr_t _stack_saved; + StackState* stack_prev; + inline int copy_stack_to_heap_up_to(const char* const stop) noexcept; + inline void free_stack_copy() noexcept; + + public: + /** + * Creates a started, but inactive, state, using *current* + * as the previous. + */ + StackState(void* mark, StackState& current); + /** + * Creates an inactive, unstarted, state. + */ + StackState(); + ~StackState(); + StackState(const StackState& other); + StackState& operator=(const StackState& other); + inline void copy_heap_to_stack(const StackState& current) noexcept; + inline int copy_stack_to_heap(char* const stackref, const StackState& current) noexcept; + inline bool started() const noexcept; + inline bool main() const noexcept; + inline bool active() const noexcept; + inline void set_active() noexcept; + inline void set_inactive() noexcept; + inline intptr_t stack_saved() const noexcept; + inline char* stack_start() const noexcept; + static inline StackState make_main() noexcept; +#ifdef GREENLET_USE_STDIO + friend std::ostream& operator<<(std::ostream& os, const StackState& s); +#endif + + // Fill in [dest, dest + n) with the values that would be at + // [src, src + n) while this greenlet is running. This is like memcpy + // except that if the greenlet is suspended it accounts for the portion + // of the greenlet's stack that was spilled to the heap. `src` may + // be on this greenlet's stack, or on the heap, but not on a different + // greenlet's stack. + void copy_from_stack(void* dest, const void* src, size_t n) const; + }; +#ifdef GREENLET_USE_STDIO + std::ostream& operator<<(std::ostream& os, const StackState& s); +#endif + + class SwitchingArgs + { + private: + G_NO_ASSIGNMENT_OF_CLS(SwitchingArgs); + // If args and kwargs are both false (NULL), this is a *throw*, not a + // switch. PyErr_... must have been called already. + OwnedObject _args; + OwnedObject _kwargs; + public: + + SwitchingArgs() + {} + + SwitchingArgs(const OwnedObject& args, const OwnedObject& kwargs) + : _args(args), + _kwargs(kwargs) + {} + + SwitchingArgs(const SwitchingArgs& other) + : _args(other._args), + _kwargs(other._kwargs) + {} + + const OwnedObject& args() + { + return this->_args; + } + + const OwnedObject& kwargs() + { + return this->_kwargs; + } + + /** + * Moves ownership from the argument to this object. + */ + SwitchingArgs& operator<<=(SwitchingArgs& other) + { + if (this != &other) { + this->_args = other._args; + this->_kwargs = other._kwargs; + other.CLEAR(); + } + return *this; + } + + /** + * Acquires ownership of the argument (consumes the reference). + */ + SwitchingArgs& operator<<=(PyObject* args) + { + this->_args = OwnedObject::consuming(args); + this->_kwargs.CLEAR(); + return *this; + } + + /** + * Acquires ownership of the argument. + * + * Sets the args to be the given value; clears the kwargs. + */ + SwitchingArgs& operator<<=(OwnedObject& args) + { + assert(&args != &this->_args); + this->_args = args; + this->_kwargs.CLEAR(); + args.CLEAR(); + + return *this; + } + + explicit operator bool() const noexcept + { + return this->_args || this->_kwargs; + } + + inline void CLEAR() + { + this->_args.CLEAR(); + this->_kwargs.CLEAR(); + } + + const std::string as_str() const noexcept + { + return PyUnicode_AsUTF8( + OwnedObject::consuming( + PyUnicode_FromFormat( + "SwitchingArgs(args=%R, kwargs=%R)", + this->_args.borrow(), + this->_kwargs.borrow() + ) + ).borrow() + ); + } + }; + + class ThreadState; + + class UserGreenlet; + class MainGreenlet; + + class Greenlet + { + private: + G_NO_COPIES_OF_CLS(Greenlet); + PyGreenlet* const _self; + private: + // XXX: Work to remove these. + friend class ThreadState; + friend class UserGreenlet; + friend class MainGreenlet; + protected: + ExceptionState exception_state; + SwitchingArgs switch_args; + StackState stack_state; + PythonState python_state; + Greenlet(PyGreenlet* p, const StackState& initial_state); + public: + // This constructor takes ownership of the PyGreenlet, by + // setting ``p->pimpl = this;``. + Greenlet(PyGreenlet* p); + virtual ~Greenlet(); + + const OwnedObject context() const; + + // You MUST call this _very_ early in the switching process to + // prepare anything that may need prepared. This might perform + // garbage collections or otherwise run arbitrary Python code. + // + // One specific use of it is for Python 3.11+, preventing + // running arbitrary code at unsafe times. See + // PythonState::may_switch_away(). + inline void may_switch_away() + { + this->python_state.may_switch_away(); + } + + inline void context(refs::BorrowedObject new_context); + + inline SwitchingArgs& args() + { + return this->switch_args; + } + + virtual const refs::BorrowedMainGreenlet main_greenlet() const = 0; + + inline intptr_t stack_saved() const noexcept + { + return this->stack_state.stack_saved(); + } + + // This is used by the macro SLP_SAVE_STATE to compute the + // difference in stack sizes. It might be nice to handle the + // computation ourself, but the type of the result + // varies by platform, so doing it in the macro is the + // simplest way. + inline const char* stack_start() const noexcept + { + return this->stack_state.stack_start(); + } + + virtual OwnedObject throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state); + virtual OwnedObject g_switch() = 0; + /** + * Force the greenlet to appear dead. Used when it's not + * possible to throw an exception into a greenlet anymore. + * + * This losses access to the thread state and the main greenlet. + */ + virtual void murder_in_place(); + + /** + * Called when somebody notices we were running in a dead + * thread to allow cleaning up resources (because we can't + * raise GreenletExit into it anymore). + * This is very similar to ``murder_in_place()``, except that + * it DOES NOT lose the main greenlet or thread state. + */ + inline void deactivate_and_free(); + + + // Called when some thread wants to deallocate a greenlet + // object. + // The thread may or may not be the same thread the greenlet + // was running in. + // The thread state will be null if the thread the greenlet + // was running in was known to have exited. + void deallocing_greenlet_in_thread(const ThreadState* current_state); + + // Must be called on 3.12+ before exposing a suspended greenlet's + // frames to user code. This rewrites the linked list of interpreter + // frames to skip the ones that are being stored on the C stack (which + // can't be safely accessed while the greenlet is suspended because + // that stack space might be hosting a different greenlet), and + // sets PythonState::frames_were_exposed so we remember to restore + // the original list before resuming the greenlet. The C-stack frames + // are a low-level interpreter implementation detail; while they're + // important to the bytecode eval loop, they're superfluous for + // introspection purposes. + void expose_frames(); + + + // TODO: Figure out how to make these non-public. + inline void slp_restore_state() noexcept; + inline int slp_save_state(char *const stackref) noexcept; + + inline bool is_currently_running_in_some_thread() const; + virtual bool belongs_to_thread(const ThreadState* state) const; + + inline bool started() const + { + return this->stack_state.started(); + } + inline bool active() const + { + return this->stack_state.active(); + } + inline bool main() const + { + return this->stack_state.main(); + } + virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const = 0; + + virtual const OwnedGreenlet parent() const = 0; + virtual void parent(const refs::BorrowedObject new_parent) = 0; + + inline const PythonState::OwnedFrame& top_frame() + { + return this->python_state.top_frame(); + } + + virtual const OwnedObject& run() const = 0; + virtual void run(const refs::BorrowedObject nrun) = 0; + + + virtual int tp_traverse(visitproc visit, void* arg); + virtual int tp_clear(); + + + // Return the thread state that the greenlet is running in, or + // null if the greenlet is not running or the thread is known + // to have exited. + virtual ThreadState* thread_state() const noexcept = 0; + + // Return true if the greenlet is known to have been running + // (active) in a thread that has now exited. + virtual bool was_running_in_dead_thread() const noexcept = 0; + + // Return a borrowed greenlet that is the Python object + // this object represents. + inline BorrowedGreenlet self() const noexcept + { + return BorrowedGreenlet(this->_self); + } + + // For testing. If this returns true, we should pretend that + // slp_switch() failed. + virtual bool force_slp_switch_error() const noexcept; + + protected: + inline void release_args(); + + // The functions that must not be inlined are declared virtual. + // We also mark them as protected, not private, so that the + // compiler is forced to call them through a function pointer. + // (A sufficiently smart compiler could directly call a private + // virtual function since it can never be overridden in a + // subclass). + + // Also TODO: Switch away from integer error codes and to enums, + // or throw exceptions when possible. + struct switchstack_result_t + { + int status; + Greenlet* the_new_current_greenlet; + OwnedGreenlet origin_greenlet; + + switchstack_result_t() + : status(0), + the_new_current_greenlet(nullptr) + {} + + switchstack_result_t(int err) + : status(err), + the_new_current_greenlet(nullptr) + {} + + switchstack_result_t(int err, Greenlet* state, OwnedGreenlet& origin) + : status(err), + the_new_current_greenlet(state), + origin_greenlet(origin) + { + } + + switchstack_result_t(int err, Greenlet* state, const BorrowedGreenlet& origin) + : status(err), + the_new_current_greenlet(state), + origin_greenlet(origin) + { + } + + switchstack_result_t(const switchstack_result_t& other) + : status(other.status), + the_new_current_greenlet(other.the_new_current_greenlet), + origin_greenlet(other.origin_greenlet) + {} + + switchstack_result_t& operator=(const switchstack_result_t& other) + { + this->status = other.status; + this->the_new_current_greenlet = other.the_new_current_greenlet; + this->origin_greenlet = other.origin_greenlet; + return *this; + } + }; + + OwnedObject on_switchstack_or_initialstub_failure( + Greenlet* target, + const switchstack_result_t& err, + const bool target_was_me=false, + const bool was_initial_stub=false); + + // Returns the previous greenlet we just switched away from. + virtual OwnedGreenlet g_switchstack_success() noexcept; + + + // Check the preconditions for switching to this greenlet; if they + // aren't met, throws PyErrOccurred. Most callers will want to + // catch this and clear the arguments + inline void check_switch_allowed() const; + class GreenletStartedWhileInPython : public std::runtime_error + { + public: + GreenletStartedWhileInPython() : std::runtime_error("") + {} + }; + + protected: + + + /** + Perform a stack switch into this greenlet. + + This temporarily sets the global variable + ``switching_thread_state`` to this greenlet; as soon as the + call to ``slp_switch`` completes, this is reset to NULL. + Consequently, this depends on the GIL. + + TODO: Adopt the stackman model and pass ``slp_switch`` a + callback function and context pointer; this eliminates the + need for global variables altogether. + + Because the stack switch happens in this function, this + function can't use its own stack (local) variables, set + before the switch, and then accessed after the switch. + + Further, you con't even access ``g_thread_state_global`` + before and after the switch from the global variable. + Because it is thread local some compilers cache it in a + register/on the stack, notably new versions of MSVC; this + breaks with strange crashes sometime later, because writing + to anything in ``g_thread_state_global`` after the switch + is actually writing to random memory. For this reason, we + call a non-inlined function to finish the operation. (XXX: + The ``/GT`` MSVC compiler argument probably fixes that.) + + It is very important that stack switch is 'atomic', i.e. no + calls into other Python code allowed (except very few that + are safe), because global variables are very fragile. (This + should no longer be the case with thread-local variables.) + + */ + // Made virtual to facilitate subclassing UserGreenlet for testing. + virtual switchstack_result_t g_switchstack(void); + +class TracingGuard +{ +private: + PyThreadState* tstate; +public: + TracingGuard() + : tstate(PyThreadState_GET()) + { + PyThreadState_EnterTracing(this->tstate); + } + + ~TracingGuard() + { + PyThreadState_LeaveTracing(this->tstate); + this->tstate = nullptr; + } + + inline void CallTraceFunction(const OwnedObject& tracefunc, + const greenlet::refs::ImmortalEventName& event, + const BorrowedGreenlet& origin, + const BorrowedGreenlet& target) + { + // TODO: This calls tracefunc(event, (origin, target)). Add a shortcut + // function for that that's specialized to avoid the Py_BuildValue + // string parsing, or start with just using "ON" format with PyTuple_Pack(2, + // origin, target). That seems like what the N format is meant + // for. + // XXX: Why does event not automatically cast back to a PyObject? + // It tries to call the "deleted constructor ImmortalEventName + // const" instead. + assert(tracefunc); + assert(event); + assert(origin); + assert(target); + greenlet::refs::NewReference retval( + PyObject_CallFunction( + tracefunc.borrow(), + "O(OO)", + event.borrow(), + origin.borrow(), + target.borrow() + )); + if (!retval) { + throw PyErrOccurred::from_current(); + } + } +}; + + static void + g_calltrace(const OwnedObject& tracefunc, + const greenlet::refs::ImmortalEventName& event, + const greenlet::refs::BorrowedGreenlet& origin, + const BorrowedGreenlet& target); + private: + OwnedObject g_switch_finish(const switchstack_result_t& err); + + }; + + class UserGreenlet : public Greenlet + { + private: + static greenlet::PythonAllocator allocator; + OwnedMainGreenlet _main_greenlet; + OwnedObject _run_callable; + OwnedGreenlet _parent; + public: + static void* operator new(size_t UNUSED(count)); + static void operator delete(void* ptr); + + UserGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent); + virtual ~UserGreenlet(); + + virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const; + virtual bool was_running_in_dead_thread() const noexcept; + virtual ThreadState* thread_state() const noexcept; + virtual OwnedObject g_switch(); + virtual const OwnedObject& run() const + { + if (this->started() || !this->_run_callable) { + throw AttributeError("run"); + } + return this->_run_callable; + } + virtual void run(const refs::BorrowedObject nrun); + + virtual const OwnedGreenlet parent() const; + virtual void parent(const refs::BorrowedObject new_parent); + + virtual const refs::BorrowedMainGreenlet main_greenlet() const; + + virtual void murder_in_place(); + virtual bool belongs_to_thread(const ThreadState* state) const; + virtual int tp_traverse(visitproc visit, void* arg); + virtual int tp_clear(); + class ParentIsCurrentGuard + { + private: + OwnedGreenlet oldparent; + UserGreenlet* greenlet; + G_NO_COPIES_OF_CLS(ParentIsCurrentGuard); + public: + ParentIsCurrentGuard(UserGreenlet* p, const ThreadState& thread_state); + ~ParentIsCurrentGuard(); + }; + virtual OwnedObject throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state); + protected: + virtual switchstack_result_t g_initialstub(void* mark); + private: + // This function isn't meant to return. + // This accepts raw pointers and the ownership of them at the + // same time. The caller should use ``inner_bootstrap(origin.relinquish_ownership())``. + void inner_bootstrap(PyGreenlet* origin_greenlet, PyObject* run); + }; + + class BrokenGreenlet : public UserGreenlet + { + private: + static greenlet::PythonAllocator allocator; + public: + bool _force_switch_error = false; + bool _force_slp_switch_error = false; + + static void* operator new(size_t UNUSED(count)); + static void operator delete(void* ptr); + BrokenGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent) + : UserGreenlet(p, the_parent) + {} + virtual ~BrokenGreenlet() + {} + + virtual switchstack_result_t g_switchstack(void); + virtual bool force_slp_switch_error() const noexcept; + + }; + + class MainGreenlet : public Greenlet + { + private: + static greenlet::PythonAllocator allocator; + refs::BorrowedMainGreenlet _self; + ThreadState* _thread_state; + G_NO_COPIES_OF_CLS(MainGreenlet); + public: + static void* operator new(size_t UNUSED(count)); + static void operator delete(void* ptr); + + MainGreenlet(refs::BorrowedMainGreenlet::PyType*, ThreadState*); + virtual ~MainGreenlet(); + + + virtual const OwnedObject& run() const; + virtual void run(const refs::BorrowedObject nrun); + + virtual const OwnedGreenlet parent() const; + virtual void parent(const refs::BorrowedObject new_parent); + + virtual const refs::BorrowedMainGreenlet main_greenlet() const; + + virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const; + virtual bool was_running_in_dead_thread() const noexcept; + virtual ThreadState* thread_state() const noexcept; + void thread_state(ThreadState*) noexcept; + virtual OwnedObject g_switch(); + virtual int tp_traverse(visitproc visit, void* arg); + }; + + // Instantiate one on the stack to save the GC state, + // and then disable GC. When it goes out of scope, GC will be + // restored to its original state. Sadly, these APIs are only + // available on 3.10+; luckily, we only need them on 3.11+. +#if GREENLET_PY310 + class GCDisabledGuard + { + private: + int was_enabled = 0; + public: + GCDisabledGuard() + : was_enabled(PyGC_IsEnabled()) + { + PyGC_Disable(); + } + + ~GCDisabledGuard() + { + if (this->was_enabled) { + PyGC_Enable(); + } + } + }; +#endif + + OwnedObject& operator<<=(OwnedObject& lhs, greenlet::SwitchingArgs& rhs) noexcept; + + //TODO: Greenlet::g_switch() should call this automatically on its + //return value. As it is, the module code is calling it. + static inline OwnedObject + single_result(const OwnedObject& results) + { + if (results + && PyTuple_Check(results.borrow()) + && PyTuple_GET_SIZE(results.borrow()) == 1) { + PyObject* result = PyTuple_GET_ITEM(results.borrow(), 0); + assert(result); + return OwnedObject::owning(result); + } + return results; + } + + + static OwnedObject + g_handle_exit(const OwnedObject& greenlet_result); + + + template + void operator<<(const PyThreadState *const lhs, T& rhs) + { + rhs.operator<<(lhs); + } + +} // namespace greenlet ; + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/TGreenletGlobals.cpp b/venv/lib/python3.12/site-packages/greenlet/TGreenletGlobals.cpp new file mode 100644 index 0000000..0087d2f --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/TGreenletGlobals.cpp @@ -0,0 +1,94 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + * Implementation of GreenletGlobals. + * + * Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ +#ifndef T_GREENLET_GLOBALS +#define T_GREENLET_GLOBALS + +#include "greenlet_refs.hpp" +#include "greenlet_exceptions.hpp" +#include "greenlet_thread_support.hpp" +#include "greenlet_internal.hpp" + +namespace greenlet { + +// This encapsulates what were previously module global "constants" +// established at init time. +// This is a step towards Python3 style module state that allows +// reloading. +// +// In an earlier iteration of this code, we used placement new to be +// able to allocate this object statically still, so that references +// to its members don't incur an extra pointer indirection. +// But under some scenarios, that could result in crashes at +// shutdown because apparently the destructor was getting run twice? +class GreenletGlobals +{ + +public: + const greenlet::refs::ImmortalEventName event_switch; + const greenlet::refs::ImmortalEventName event_throw; + const greenlet::refs::ImmortalException PyExc_GreenletError; + const greenlet::refs::ImmortalException PyExc_GreenletExit; + const greenlet::refs::ImmortalObject empty_tuple; + const greenlet::refs::ImmortalObject empty_dict; + const greenlet::refs::ImmortalString str_run; + Mutex* const thread_states_to_destroy_lock; + greenlet::cleanup_queue_t thread_states_to_destroy; + + GreenletGlobals() : + event_switch("switch"), + event_throw("throw"), + PyExc_GreenletError("greenlet.error"), + PyExc_GreenletExit("greenlet.GreenletExit", PyExc_BaseException), + empty_tuple(Require(PyTuple_New(0))), + empty_dict(Require(PyDict_New())), + str_run("run"), + thread_states_to_destroy_lock(new Mutex()) + {} + + ~GreenletGlobals() + { + // This object is (currently) effectively immortal, and not + // just because of those placement new tricks; if we try to + // deallocate the static object we allocated, and overwrote, + // we would be doing so at C++ teardown time, which is after + // the final Python GIL is released, and we can't use the API + // then. + // (The members will still be destructed, but they also don't + // do any deallocation.) + } + + void queue_to_destroy(ThreadState* ts) const + { + // we're currently accessed through a static const object, + // implicitly marking our members as const, so code can't just + // call push_back (or pop_back) without casting away the + // const. + // + // Do that for callers. + greenlet::cleanup_queue_t& q = const_cast(this->thread_states_to_destroy); + q.push_back(ts); + } + + ThreadState* take_next_to_destroy() const + { + greenlet::cleanup_queue_t& q = const_cast(this->thread_states_to_destroy); + ThreadState* result = q.back(); + q.pop_back(); + return result; + } +}; + +}; // namespace greenlet + +static const greenlet::GreenletGlobals* mod_globs; + +#endif // T_GREENLET_GLOBALS diff --git a/venv/lib/python3.12/site-packages/greenlet/TMainGreenlet.cpp b/venv/lib/python3.12/site-packages/greenlet/TMainGreenlet.cpp new file mode 100644 index 0000000..a2a9cfe --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/TMainGreenlet.cpp @@ -0,0 +1,153 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + * Implementation of greenlet::MainGreenlet. + * + * Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ +#ifndef T_MAIN_GREENLET_CPP +#define T_MAIN_GREENLET_CPP + +#include "TGreenlet.hpp" + + + +// Protected by the GIL. Incremented when we create a main greenlet, +// in a new thread, decremented when it is destroyed. +static Py_ssize_t G_TOTAL_MAIN_GREENLETS; + +namespace greenlet { +greenlet::PythonAllocator MainGreenlet::allocator; + +void* MainGreenlet::operator new(size_t UNUSED(count)) +{ + return allocator.allocate(1); +} + + +void MainGreenlet::operator delete(void* ptr) +{ + return allocator.deallocate(static_cast(ptr), + 1); +} + + +MainGreenlet::MainGreenlet(PyGreenlet* p, ThreadState* state) + : Greenlet(p, StackState::make_main()), + _self(p), + _thread_state(state) +{ + G_TOTAL_MAIN_GREENLETS++; +} + +MainGreenlet::~MainGreenlet() +{ + G_TOTAL_MAIN_GREENLETS--; + this->tp_clear(); +} + +ThreadState* +MainGreenlet::thread_state() const noexcept +{ + return this->_thread_state; +} + +void +MainGreenlet::thread_state(ThreadState* t) noexcept +{ + assert(!t); + this->_thread_state = t; +} + + +const BorrowedMainGreenlet +MainGreenlet::main_greenlet() const +{ + return this->_self; +} + +BorrowedMainGreenlet +MainGreenlet::find_main_greenlet_in_lineage() const +{ + return BorrowedMainGreenlet(this->_self); +} + +bool +MainGreenlet::was_running_in_dead_thread() const noexcept +{ + return !this->_thread_state; +} + +OwnedObject +MainGreenlet::g_switch() +{ + try { + this->check_switch_allowed(); + } + catch (const PyErrOccurred&) { + this->release_args(); + throw; + } + + switchstack_result_t err = this->g_switchstack(); + if (err.status < 0) { + // XXX: This code path is untested, but it is shared + // with the UserGreenlet path that is tested. + return this->on_switchstack_or_initialstub_failure( + this, + err, + true, // target was me + false // was initial stub + ); + } + + return err.the_new_current_greenlet->g_switch_finish(err); +} + +int +MainGreenlet::tp_traverse(visitproc visit, void* arg) +{ + if (this->_thread_state) { + // we've already traversed main, (self), don't do it again. + int result = this->_thread_state->tp_traverse(visit, arg, false); + if (result) { + return result; + } + } + return Greenlet::tp_traverse(visit, arg); +} + +const OwnedObject& +MainGreenlet::run() const +{ + throw AttributeError("Main greenlets do not have a run attribute."); +} + +void +MainGreenlet::run(const BorrowedObject UNUSED(nrun)) +{ + throw AttributeError("Main greenlets do not have a run attribute."); +} + +void +MainGreenlet::parent(const BorrowedObject raw_new_parent) +{ + if (!raw_new_parent) { + throw AttributeError("can't delete attribute"); + } + throw AttributeError("cannot set the parent of a main greenlet"); +} + +const OwnedGreenlet +MainGreenlet::parent() const +{ + return OwnedGreenlet(); // null becomes None +} + +}; // namespace greenlet + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/TPythonState.cpp b/venv/lib/python3.12/site-packages/greenlet/TPythonState.cpp new file mode 100644 index 0000000..8833a80 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/TPythonState.cpp @@ -0,0 +1,406 @@ +#ifndef GREENLET_PYTHON_STATE_CPP +#define GREENLET_PYTHON_STATE_CPP + +#include +#include "TGreenlet.hpp" + +namespace greenlet { + +PythonState::PythonState() + : _top_frame() +#if GREENLET_USE_CFRAME + ,cframe(nullptr) + ,use_tracing(0) +#endif +#if GREENLET_PY314 + ,py_recursion_depth(0) + ,current_executor(nullptr) +#elif GREENLET_PY312 + ,py_recursion_depth(0) + ,c_recursion_depth(0) +#else + ,recursion_depth(0) +#endif +#if GREENLET_PY313 + ,delete_later(nullptr) +#else + ,trash_delete_nesting(0) +#endif +#if GREENLET_PY311 + ,current_frame(nullptr) + ,datastack_chunk(nullptr) + ,datastack_top(nullptr) + ,datastack_limit(nullptr) +#endif +{ +#if GREENLET_USE_CFRAME + /* + The PyThreadState->cframe pointer usually points to memory on + the stack, alloceted in a call into PyEval_EvalFrameDefault. + + Initially, before any evaluation begins, it points to the + initial PyThreadState object's ``root_cframe`` object, which is + statically allocated for the lifetime of the thread. + + A greenlet can last for longer than a call to + PyEval_EvalFrameDefault, so we can't set its ``cframe`` pointer + to be the current ``PyThreadState->cframe``; nor could we use + one from the greenlet parent for the same reason. Yet a further + no: we can't allocate one scoped to the greenlet and then + destroy it when the greenlet is deallocated, because inside the + interpreter the _PyCFrame objects form a linked list, and that too + can result in accessing memory beyond its dynamic lifetime (if + the greenlet doesn't actually finish before it dies, its entry + could still be in the list). + + Using the ``root_cframe`` is problematic, though, because its + members are never modified by the interpreter and are set to 0, + meaning that its ``use_tracing`` flag is never updated. We don't + want to modify that value in the ``root_cframe`` ourself: it + *shouldn't* matter much because we should probably never get + back to the point where that's the only cframe on the stack; + even if it did matter, the major consequence of an incorrect + value for ``use_tracing`` is that if its true the interpreter + does some extra work --- however, it's just good code hygiene. + + Our solution: before a greenlet runs, after its initial + creation, it uses the ``root_cframe`` just to have something to + put there. However, once the greenlet is actually switched to + for the first time, ``g_initialstub`` (which doesn't actually + "return" while the greenlet is running) stores a new _PyCFrame on + its local stack, and copies the appropriate values from the + currently running _PyCFrame; this is then made the _PyCFrame for the + newly-minted greenlet. ``g_initialstub`` then proceeds to call + ``glet.run()``, which results in ``PyEval_...`` adding the + _PyCFrame to the list. Switches continue as normal. Finally, when + the greenlet finishes, the call to ``glet.run()`` returns and + the _PyCFrame is taken out of the linked list and the stack value + is now unused and free to expire. + + XXX: I think we can do better. If we're deallocing in the same + thread, can't we traverse the list and unlink our frame? + Can we just keep a reference to the thread state in case we + dealloc in another thread? (Is that even possible if we're still + running and haven't returned from g_initialstub?) + */ + this->cframe = &PyThreadState_GET()->root_cframe; +#endif +} + + +inline void PythonState::may_switch_away() noexcept +{ +#if GREENLET_PY311 + // PyThreadState_GetFrame is probably going to have to allocate a + // new frame object. That may trigger garbage collection. Because + // we call this during the early phases of a switch (it doesn't + // matter to which greenlet, as this has a global effect), if a GC + // triggers a switch away, two things can happen, both bad: + // - We might not get switched back to, halting forward progress. + // this is pathological, but possible. + // - We might get switched back to with a different set of + // arguments or a throw instead of a switch. That would corrupt + // our state (specifically, PyErr_Occurred() and this->args() + // would no longer agree). + // + // Thus, when we call this API, we need to have GC disabled. + // This method serves as a bottleneck we call when maybe beginning + // a switch. In this way, it is always safe -- no risk of GC -- to + // use ``_GetFrame()`` whenever we need to, just as it was in + // <=3.10 (because subsequent calls will be cached and not + // allocate memory). + + GCDisabledGuard no_gc; + Py_XDECREF(PyThreadState_GetFrame(PyThreadState_GET())); +#endif +} + +void PythonState::operator<<(const PyThreadState *const tstate) noexcept +{ + this->_context.steal(tstate->context); +#if GREENLET_USE_CFRAME + /* + IMPORTANT: ``cframe`` is a pointer into the STACK. Thus, because + the call to ``slp_switch()`` changes the contents of the stack, + you cannot read from ``ts_current->cframe`` after that call and + necessarily get the same values you get from reading it here. + Anything you need to restore from now to then must be saved in a + global/threadlocal variable (because we can't use stack + variables here either). For things that need to persist across + the switch, use `will_switch_from`. + */ + this->cframe = tstate->cframe; + #if !GREENLET_PY312 + this->use_tracing = tstate->cframe->use_tracing; + #endif +#endif // GREENLET_USE_CFRAME +#if GREENLET_PY311 + #if GREENLET_PY314 + this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; + this->current_executor = tstate->current_executor; + #elif GREENLET_PY312 + this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; + this->c_recursion_depth = Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining; + #else // not 312 + this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; + #endif // GREENLET_PY312 + #if GREENLET_PY313 + this->current_frame = tstate->current_frame; + #elif GREENLET_USE_CFRAME + this->current_frame = tstate->cframe->current_frame; + #endif + this->datastack_chunk = tstate->datastack_chunk; + this->datastack_top = tstate->datastack_top; + this->datastack_limit = tstate->datastack_limit; + + PyFrameObject *frame = PyThreadState_GetFrame((PyThreadState *)tstate); + Py_XDECREF(frame); // PyThreadState_GetFrame gives us a new + // reference. + this->_top_frame.steal(frame); + #if GREENLET_PY313 + this->delete_later = Py_XNewRef(tstate->delete_later); + #elif GREENLET_PY312 + this->trash_delete_nesting = tstate->trash.delete_nesting; + #else // not 312 + this->trash_delete_nesting = tstate->trash_delete_nesting; + #endif // GREENLET_PY312 +#else // Not 311 + this->recursion_depth = tstate->recursion_depth; + this->_top_frame.steal(tstate->frame); + this->trash_delete_nesting = tstate->trash_delete_nesting; +#endif // GREENLET_PY311 +} + +#if GREENLET_PY312 +void GREENLET_NOINLINE(PythonState::unexpose_frames)() +{ + if (!this->top_frame()) { + return; + } + + // See GreenletState::expose_frames() and the comment on frames_were_exposed + // for more information about this logic. + _PyInterpreterFrame *iframe = this->_top_frame->f_frame; + while (iframe != nullptr) { + _PyInterpreterFrame *prev_exposed = iframe->previous; + assert(iframe->frame_obj); + memcpy(&iframe->previous, &iframe->frame_obj->_f_frame_data[0], + sizeof(void *)); + iframe = prev_exposed; + } +} +#else +void PythonState::unexpose_frames() +{} +#endif + +void PythonState::operator>>(PyThreadState *const tstate) noexcept +{ + tstate->context = this->_context.relinquish_ownership(); + /* Incrementing this value invalidates the contextvars cache, + which would otherwise remain valid across switches */ + tstate->context_ver++; +#if GREENLET_USE_CFRAME + tstate->cframe = this->cframe; + /* + If we were tracing, we need to keep tracing. + There should never be the possibility of hitting the + root_cframe here. See note above about why we can't + just copy this from ``origin->cframe->use_tracing``. + */ + #if !GREENLET_PY312 + tstate->cframe->use_tracing = this->use_tracing; + #endif +#endif // GREENLET_USE_CFRAME +#if GREENLET_PY311 + #if GREENLET_PY314 + tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth; + tstate->current_executor = this->current_executor; + this->unexpose_frames(); + #elif GREENLET_PY312 + tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth; + tstate->c_recursion_remaining = Py_C_RECURSION_LIMIT - this->c_recursion_depth; + this->unexpose_frames(); + #else // \/ 3.11 + tstate->recursion_remaining = tstate->recursion_limit - this->recursion_depth; + #endif // GREENLET_PY312 + #if GREENLET_PY313 + tstate->current_frame = this->current_frame; + #elif GREENLET_USE_CFRAME + tstate->cframe->current_frame = this->current_frame; + #endif + tstate->datastack_chunk = this->datastack_chunk; + tstate->datastack_top = this->datastack_top; + tstate->datastack_limit = this->datastack_limit; + this->_top_frame.relinquish_ownership(); + #if GREENLET_PY313 + Py_XDECREF(tstate->delete_later); + tstate->delete_later = this->delete_later; + Py_CLEAR(this->delete_later); + #elif GREENLET_PY312 + tstate->trash.delete_nesting = this->trash_delete_nesting; + #else // not 3.12 + tstate->trash_delete_nesting = this->trash_delete_nesting; + #endif // GREENLET_PY312 +#else // not 3.11 + tstate->frame = this->_top_frame.relinquish_ownership(); + tstate->recursion_depth = this->recursion_depth; + tstate->trash_delete_nesting = this->trash_delete_nesting; +#endif // GREENLET_PY311 +} + +inline void PythonState::will_switch_from(PyThreadState *const origin_tstate) noexcept +{ +#if GREENLET_USE_CFRAME && !GREENLET_PY312 + // The weird thing is, we don't actually save this for an + // effect on the current greenlet, it's saved for an + // effect on the target greenlet. That is, we want + // continuity of this setting across the greenlet switch. + this->use_tracing = origin_tstate->cframe->use_tracing; +#endif +} + +void PythonState::set_initial_state(const PyThreadState* const tstate) noexcept +{ + this->_top_frame = nullptr; +#if GREENLET_PY314 + this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; + this->current_executor = tstate->current_executor; +#elif GREENLET_PY312 + this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; + // XXX: TODO: Comment from a reviewer: + // Should this be ``Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining``? + // But to me it looks more like that might not be the right + // initialization either? + this->c_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining; +#elif GREENLET_PY311 + this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining; +#else + this->recursion_depth = tstate->recursion_depth; +#endif +} +// TODO: Better state management about when we own the top frame. +int PythonState::tp_traverse(visitproc visit, void* arg, bool own_top_frame) noexcept +{ + Py_VISIT(this->_context.borrow()); + if (own_top_frame) { + Py_VISIT(this->_top_frame.borrow()); + } + return 0; +} + +void PythonState::tp_clear(bool own_top_frame) noexcept +{ + PythonStateContext::tp_clear(); + // If we get here owning a frame, + // we got dealloc'd without being finished. We may or may not be + // in the same thread. + if (own_top_frame) { + this->_top_frame.CLEAR(); + } +} + +#if GREENLET_USE_CFRAME +void PythonState::set_new_cframe(_PyCFrame& frame) noexcept +{ + frame = *PyThreadState_GET()->cframe; + /* Make the target greenlet refer to the stack value. */ + this->cframe = &frame; + /* + And restore the link to the previous frame so this one gets + unliked appropriately. + */ + this->cframe->previous = &PyThreadState_GET()->root_cframe; +} +#endif + +const PythonState::OwnedFrame& PythonState::top_frame() const noexcept +{ + return this->_top_frame; +} + +void PythonState::did_finish(PyThreadState* tstate) noexcept +{ +#if GREENLET_PY311 + // See https://github.com/gevent/gevent/issues/1924 and + // https://github.com/python-greenlet/greenlet/issues/328. In + // short, Python 3.11 allocates memory for frames as a sort of + // linked list that's kept as part of PyThreadState in the + // ``datastack_chunk`` member and friends. These are saved and + // restored as part of switching greenlets. + // + // When we initially switch to a greenlet, we set those to NULL. + // That causes the frame management code to treat this like a + // brand new thread and start a fresh list of chunks, beginning + // with a new "root" chunk. As we make calls in this greenlet, + // those chunks get added, and as calls return, they get popped. + // But the frame code (pystate.c) is careful to make sure that the + // root chunk never gets popped. + // + // Thus, when a greenlet exits for the last time, there will be at + // least a single root chunk that we must be responsible for + // deallocating. + // + // The complex part is that these chunks are allocated and freed + // using ``_PyObject_VirtualAlloc``/``Free``. Those aren't public + // functions, and they aren't exported for linking. It so happens + // that we know they are just thin wrappers around the Arena + // allocator, so we can use that directly to deallocate in a + // compatible way. + // + // CAUTION: Check this implementation detail on every major version. + // + // It might be nice to be able to do this in our destructor, but + // can we be sure that no one else is using that memory? Plus, as + // described below, our pointers may not even be valid anymore. As + // a special case, there is one time that we know we can do this, + // and that's from the destructor of the associated UserGreenlet + // (NOT main greenlet) + PyObjectArenaAllocator alloc; + _PyStackChunk* chunk = nullptr; + if (tstate) { + // We really did finish, we can never be switched to again. + chunk = tstate->datastack_chunk; + // Unfortunately, we can't do much sanity checking. Our + // this->datastack_chunk pointer is out of date (evaluation may + // have popped down through it already) so we can't verify that + // we deallocate it. I don't think we can even check datastack_top + // for the same reason. + + PyObject_GetArenaAllocator(&alloc); + tstate->datastack_chunk = nullptr; + tstate->datastack_limit = nullptr; + tstate->datastack_top = nullptr; + + } + else if (this->datastack_chunk) { + // The UserGreenlet (NOT the main greenlet!) is being deallocated. If we're + // still holding a stack chunk, it's garbage because we know + // we can never switch back to let cPython clean it up. + // Because the last time we got switched away from, and we + // haven't run since then, we know our chain is valid and can + // be dealloced. + chunk = this->datastack_chunk; + PyObject_GetArenaAllocator(&alloc); + } + + if (alloc.free && chunk) { + // In case the arena mechanism has been torn down already. + while (chunk) { + _PyStackChunk *prev = chunk->previous; + chunk->previous = nullptr; + alloc.free(alloc.ctx, chunk, chunk->size); + chunk = prev; + } + } + + this->datastack_chunk = nullptr; + this->datastack_limit = nullptr; + this->datastack_top = nullptr; +#endif +} + + +}; // namespace greenlet + +#endif // GREENLET_PYTHON_STATE_CPP diff --git a/venv/lib/python3.12/site-packages/greenlet/TStackState.cpp b/venv/lib/python3.12/site-packages/greenlet/TStackState.cpp new file mode 100644 index 0000000..9743ab5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/TStackState.cpp @@ -0,0 +1,265 @@ +#ifndef GREENLET_STACK_STATE_CPP +#define GREENLET_STACK_STATE_CPP + +#include "TGreenlet.hpp" + +namespace greenlet { + +#ifdef GREENLET_USE_STDIO +#include +using std::cerr; +using std::endl; + +std::ostream& operator<<(std::ostream& os, const StackState& s) +{ + os << "StackState(stack_start=" << (void*)s._stack_start + << ", stack_stop=" << (void*)s.stack_stop + << ", stack_copy=" << (void*)s.stack_copy + << ", stack_saved=" << s._stack_saved + << ", stack_prev=" << s.stack_prev + << ", addr=" << &s + << ")"; + return os; +} +#endif + +StackState::StackState(void* mark, StackState& current) + : _stack_start(nullptr), + stack_stop((char*)mark), + stack_copy(nullptr), + _stack_saved(0), + /* Skip a dying greenlet */ + stack_prev(current._stack_start + ? ¤t + : current.stack_prev) +{ +} + +StackState::StackState() + : _stack_start(nullptr), + stack_stop(nullptr), + stack_copy(nullptr), + _stack_saved(0), + stack_prev(nullptr) +{ +} + +StackState::StackState(const StackState& other) +// can't use a delegating constructor because of +// MSVC for Python 2.7 + : _stack_start(nullptr), + stack_stop(nullptr), + stack_copy(nullptr), + _stack_saved(0), + stack_prev(nullptr) +{ + this->operator=(other); +} + +StackState& StackState::operator=(const StackState& other) +{ + if (&other == this) { + return *this; + } + if (other._stack_saved) { + throw std::runtime_error("Refusing to steal memory."); + } + + //If we have memory allocated, dispose of it + this->free_stack_copy(); + + this->_stack_start = other._stack_start; + this->stack_stop = other.stack_stop; + this->stack_copy = other.stack_copy; + this->_stack_saved = other._stack_saved; + this->stack_prev = other.stack_prev; + return *this; +} + +inline void StackState::free_stack_copy() noexcept +{ + PyMem_Free(this->stack_copy); + this->stack_copy = nullptr; + this->_stack_saved = 0; +} + +inline void StackState::copy_heap_to_stack(const StackState& current) noexcept +{ + + /* Restore the heap copy back into the C stack */ + if (this->_stack_saved != 0) { + memcpy(this->_stack_start, this->stack_copy, this->_stack_saved); + this->free_stack_copy(); + } + StackState* owner = const_cast(¤t); + if (!owner->_stack_start) { + owner = owner->stack_prev; /* greenlet is dying, skip it */ + } + while (owner && owner->stack_stop <= this->stack_stop) { + // cerr << "\tOwner: " << owner << endl; + owner = owner->stack_prev; /* find greenlet with more stack */ + } + this->stack_prev = owner; + // cerr << "\tFinished with: " << *this << endl; +} + +inline int StackState::copy_stack_to_heap_up_to(const char* const stop) noexcept +{ + /* Save more of g's stack into the heap -- at least up to 'stop' + g->stack_stop |________| + | | + | __ stop . . . . . + | | ==> . . + |________| _______ + | | | | + | | | | + g->stack_start | | |_______| g->stack_copy + */ + intptr_t sz1 = this->_stack_saved; + intptr_t sz2 = stop - this->_stack_start; + assert(this->_stack_start); + if (sz2 > sz1) { + char* c = (char*)PyMem_Realloc(this->stack_copy, sz2); + if (!c) { + PyErr_NoMemory(); + return -1; + } + memcpy(c + sz1, this->_stack_start + sz1, sz2 - sz1); + this->stack_copy = c; + this->_stack_saved = sz2; + } + return 0; +} + +inline int StackState::copy_stack_to_heap(char* const stackref, + const StackState& current) noexcept +{ + /* must free all the C stack up to target_stop */ + const char* const target_stop = this->stack_stop; + + StackState* owner = const_cast(¤t); + assert(owner->_stack_saved == 0); // everything is present on the stack + if (!owner->_stack_start) { + owner = owner->stack_prev; /* not saved if dying */ + } + else { + owner->_stack_start = stackref; + } + + while (owner->stack_stop < target_stop) { + /* ts_current is entierely within the area to free */ + if (owner->copy_stack_to_heap_up_to(owner->stack_stop)) { + return -1; /* XXX */ + } + owner = owner->stack_prev; + } + if (owner != this) { + if (owner->copy_stack_to_heap_up_to(target_stop)) { + return -1; /* XXX */ + } + } + return 0; +} + +inline bool StackState::started() const noexcept +{ + return this->stack_stop != nullptr; +} + +inline bool StackState::main() const noexcept +{ + return this->stack_stop == (char*)-1; +} + +inline bool StackState::active() const noexcept +{ + return this->_stack_start != nullptr; +} + +inline void StackState::set_active() noexcept +{ + assert(this->_stack_start == nullptr); + this->_stack_start = (char*)1; +} + +inline void StackState::set_inactive() noexcept +{ + this->_stack_start = nullptr; + // XXX: What if we still have memory out there? + // That case is actually triggered by + // test_issue251_issue252_explicit_reference_not_collectable (greenlet.tests.test_leaks.TestLeaks) + // and + // test_issue251_issue252_need_to_collect_in_background + // (greenlet.tests.test_leaks.TestLeaks) + // + // Those objects never get deallocated, so the destructor never + // runs. + // It *seems* safe to clean up the memory here? + if (this->_stack_saved) { + this->free_stack_copy(); + } +} + +inline intptr_t StackState::stack_saved() const noexcept +{ + return this->_stack_saved; +} + +inline char* StackState::stack_start() const noexcept +{ + return this->_stack_start; +} + + +inline StackState StackState::make_main() noexcept +{ + StackState s; + s._stack_start = (char*)1; + s.stack_stop = (char*)-1; + return s; +} + +StackState::~StackState() +{ + if (this->_stack_saved != 0) { + this->free_stack_copy(); + } +} + +void StackState::copy_from_stack(void* vdest, const void* vsrc, size_t n) const +{ + char* dest = static_cast(vdest); + const char* src = static_cast(vsrc); + if (src + n <= this->_stack_start + || src >= this->_stack_start + this->_stack_saved + || this->_stack_saved == 0) { + // Nothing we're copying was spilled from the stack + memcpy(dest, src, n); + return; + } + + if (src < this->_stack_start) { + // Copy the part before the saved stack. + // We know src + n > _stack_start due to the test above. + const size_t nbefore = this->_stack_start - src; + memcpy(dest, src, nbefore); + dest += nbefore; + src += nbefore; + n -= nbefore; + } + // We know src >= _stack_start after the before-copy, and + // src < _stack_start + _stack_saved due to the first if condition + size_t nspilled = std::min(n, this->_stack_start + this->_stack_saved - src); + memcpy(dest, this->stack_copy + (src - this->_stack_start), nspilled); + dest += nspilled; + src += nspilled; + n -= nspilled; + if (n > 0) { + // Copy the part after the saved stack + memcpy(dest, src, n); + } +} + +}; // namespace greenlet + +#endif // GREENLET_STACK_STATE_CPP diff --git a/venv/lib/python3.12/site-packages/greenlet/TThreadState.hpp b/venv/lib/python3.12/site-packages/greenlet/TThreadState.hpp new file mode 100644 index 0000000..e4e6f6c --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/TThreadState.hpp @@ -0,0 +1,497 @@ +#ifndef GREENLET_THREAD_STATE_HPP +#define GREENLET_THREAD_STATE_HPP + +#include +#include + +#include "greenlet_internal.hpp" +#include "greenlet_refs.hpp" +#include "greenlet_thread_support.hpp" + +using greenlet::refs::BorrowedObject; +using greenlet::refs::BorrowedGreenlet; +using greenlet::refs::BorrowedMainGreenlet; +using greenlet::refs::OwnedMainGreenlet; +using greenlet::refs::OwnedObject; +using greenlet::refs::OwnedGreenlet; +using greenlet::refs::OwnedList; +using greenlet::refs::PyErrFetchParam; +using greenlet::refs::PyArgParseParam; +using greenlet::refs::ImmortalString; +using greenlet::refs::CreatedModule; +using greenlet::refs::PyErrPieces; +using greenlet::refs::NewReference; + +namespace greenlet { +/** + * Thread-local state of greenlets. + * + * Each native thread will get exactly one of these objects, + * automatically accessed through the best available thread-local + * mechanism the compiler supports (``thread_local`` for C++11 + * compilers or ``__thread``/``declspec(thread)`` for older GCC/clang + * or MSVC, respectively.) + * + * Previously, we kept thread-local state mostly in a bunch of + * ``static volatile`` variables in the main greenlet file.. This had + * the problem of requiring extra checks, loops, and great care + * accessing these variables if we potentially invoked any Python code + * that could release the GIL, because the state could change out from + * under us. Making the variables thread-local solves this problem. + * + * When we detected that a greenlet API accessing the current greenlet + * was invoked from a different thread than the greenlet belonged to, + * we stored a reference to the greenlet in the Python thread + * dictionary for the thread the greenlet belonged to. This could lead + * to memory leaks if the thread then exited (because of a reference + * cycle, as greenlets referred to the thread dictionary, and deleting + * non-current greenlets leaked their frame plus perhaps arguments on + * the C stack). If a thread exited while still having running + * greenlet objects (perhaps that had just switched back to the main + * greenlet), and did not invoke one of the greenlet APIs *in that + * thread, immediately before it exited, without some other thread + * then being invoked*, such a leak was guaranteed. + * + * This can be partly solved by using compiler thread-local variables + * instead of the Python thread dictionary, thus avoiding a cycle. + * + * To fully solve this problem, we need a reliable way to know that a + * thread is done and we should clean up the main greenlet. On POSIX, + * we can use the destructor function of ``pthread_key_create``, but + * there's nothing similar on Windows; a C++11 thread local object + * reliably invokes its destructor when the thread it belongs to exits + * (non-C++11 compilers offer ``__thread`` or ``declspec(thread)`` to + * create thread-local variables, but they can't hold C++ objects that + * invoke destructors; the C++11 version is the most portable solution + * I found). When the thread exits, we can drop references and + * otherwise manipulate greenlets and frames that we know can no + * longer be switched to. For compilers that don't support C++11 + * thread locals, we have a solution that uses the python thread + * dictionary, though it may not collect everything as promptly as + * other compilers do, if some other library is using the thread + * dictionary and has a cycle or extra reference. + * + * There are two small wrinkles. The first is that when the thread + * exits, it is too late to actually invoke Python APIs: the Python + * thread state is gone, and the GIL is released. To solve *this* + * problem, our destructor uses ``Py_AddPendingCall`` to transfer the + * destruction work to the main thread. (This is not an issue for the + * dictionary solution.) + * + * The second is that once the thread exits, the thread local object + * is invalid and we can't even access a pointer to it, so we can't + * pass it to ``Py_AddPendingCall``. This is handled by actually using + * a second object that's thread local (ThreadStateCreator) and having + * it dynamically allocate this object so it can live until the + * pending call runs. + */ + + + +class ThreadState { +private: + // As of commit 08ad1dd7012b101db953f492e0021fb08634afad + // this class needed 56 bytes in o Py_DEBUG build + // on 64-bit macOS 11. + // Adding the vector takes us up to 80 bytes () + + /* Strong reference to the main greenlet */ + OwnedMainGreenlet main_greenlet; + + /* Strong reference to the current greenlet. */ + OwnedGreenlet current_greenlet; + + /* Strong reference to the trace function, if any. */ + OwnedObject tracefunc; + + typedef std::vector > deleteme_t; + /* A vector of raw PyGreenlet pointers representing things that need + deleted when this thread is running. The vector owns the + references, but you need to manually INCREF/DECREF as you use + them. We don't use a vector because we + make copy of this vector, and that would become O(n) as all the + refcounts are incremented in the copy. + */ + deleteme_t deleteme; + +#ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED + void* exception_state; +#endif + + static std::clock_t _clocks_used_doing_gc; + static ImmortalString get_referrers_name; + static PythonAllocator allocator; + + G_NO_COPIES_OF_CLS(ThreadState); + + + // Allocates a main greenlet for the thread state. If this fails, + // exits the process. Called only during constructing a ThreadState. + MainGreenlet* alloc_main() + { + PyGreenlet* gmain; + + /* create the main greenlet for this thread */ + gmain = reinterpret_cast(PyType_GenericAlloc(&PyGreenlet_Type, 0)); + if (gmain == NULL) { + throw PyFatalError("alloc_main failed to alloc"); //exits the process + } + + MainGreenlet* const main = new MainGreenlet(gmain, this); + + assert(Py_REFCNT(gmain) == 1); + assert(gmain->pimpl == main); + return main; + } + + +public: + static void* operator new(size_t UNUSED(count)) + { + return ThreadState::allocator.allocate(1); + } + + static void operator delete(void* ptr) + { + return ThreadState::allocator.deallocate(static_cast(ptr), + 1); + } + + static void init() + { + ThreadState::get_referrers_name = "get_referrers"; + ThreadState::_clocks_used_doing_gc = 0; + } + + ThreadState() + { + +#ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED + this->exception_state = slp_get_exception_state(); +#endif + + // XXX: Potentially dangerous, exposing a not fully + // constructed object. + MainGreenlet* const main = this->alloc_main(); + this->main_greenlet = OwnedMainGreenlet::consuming( + main->self() + ); + assert(this->main_greenlet); + this->current_greenlet = main->self(); + // The main greenlet starts with 1 refs: The returned one. We + // then copied it to the current greenlet. + assert(this->main_greenlet.REFCNT() == 2); + } + + inline void restore_exception_state() + { +#ifdef GREENLET_NEEDS_EXCEPTION_STATE_SAVED + // It's probably important this be inlined and only call C + // functions to avoid adding an SEH frame. + slp_set_exception_state(this->exception_state); +#endif + } + + inline bool has_main_greenlet() const noexcept + { + return bool(this->main_greenlet); + } + + // Called from the ThreadStateCreator when we're in non-standard + // threading mode. In that case, there is an object in the Python + // thread state dictionary that points to us. The main greenlet + // also traverses into us, in which case it's crucial not to + // traverse back into the main greenlet. + int tp_traverse(visitproc visit, void* arg, bool traverse_main=true) + { + if (traverse_main) { + Py_VISIT(main_greenlet.borrow_o()); + } + if (traverse_main || current_greenlet != main_greenlet) { + Py_VISIT(current_greenlet.borrow_o()); + } + Py_VISIT(tracefunc.borrow()); + return 0; + } + + inline BorrowedMainGreenlet borrow_main_greenlet() const noexcept + { + assert(this->main_greenlet); + assert(this->main_greenlet.REFCNT() >= 2); + return this->main_greenlet; + }; + + inline OwnedMainGreenlet get_main_greenlet() const noexcept + { + return this->main_greenlet; + } + + /** + * In addition to returning a new reference to the currunt + * greenlet, this performs any maintenance needed. + */ + inline OwnedGreenlet get_current() + { + /* green_dealloc() cannot delete greenlets from other threads, so + it stores them in the thread dict; delete them now. */ + this->clear_deleteme_list(); + //assert(this->current_greenlet->main_greenlet == this->main_greenlet); + //assert(this->main_greenlet->main_greenlet == this->main_greenlet); + return this->current_greenlet; + } + + /** + * As for non-const get_current(); + */ + inline BorrowedGreenlet borrow_current() + { + this->clear_deleteme_list(); + return this->current_greenlet; + } + + /** + * Does no maintenance. + */ + inline OwnedGreenlet get_current() const + { + return this->current_greenlet; + } + + template + inline bool is_current(const refs::PyObjectPointer& obj) const + { + return this->current_greenlet.borrow_o() == obj.borrow_o(); + } + + inline void set_current(const OwnedGreenlet& target) + { + this->current_greenlet = target; + } + +private: + /** + * Deref and remove the greenlets from the deleteme list. Must be + * holding the GIL. + * + * If *murder* is true, then we must be called from a different + * thread than the one that these greenlets were running in. + * In that case, if the greenlet was actually running, we destroy + * the frame reference and otherwise make it appear dead before + * proceeding; otherwise, we would try (and fail) to raise an + * exception in it and wind up right back in this list. + */ + inline void clear_deleteme_list(const bool murder=false) + { + if (!this->deleteme.empty()) { + // It's possible we could add items to this list while + // running Python code if there's a thread switch, so we + // need to defensively copy it before that can happen. + deleteme_t copy = this->deleteme; + this->deleteme.clear(); // in case things come back on the list + for(deleteme_t::iterator it = copy.begin(), end = copy.end(); + it != end; + ++it ) { + PyGreenlet* to_del = *it; + if (murder) { + // Force each greenlet to appear dead; we can't raise an + // exception into it anymore anyway. + to_del->pimpl->murder_in_place(); + } + + // The only reference to these greenlets should be in + // this list, decreffing them should let them be + // deleted again, triggering calls to green_dealloc() + // in the correct thread (if we're not murdering). + // This may run arbitrary Python code and switch + // threads or greenlets! + Py_DECREF(to_del); + if (PyErr_Occurred()) { + PyErr_WriteUnraisable(nullptr); + PyErr_Clear(); + } + } + } + } + +public: + + /** + * Returns a new reference, or a false object. + */ + inline OwnedObject get_tracefunc() const + { + return tracefunc; + }; + + + inline void set_tracefunc(BorrowedObject tracefunc) + { + assert(tracefunc); + if (tracefunc == BorrowedObject(Py_None)) { + this->tracefunc.CLEAR(); + } + else { + this->tracefunc = tracefunc; + } + } + + /** + * Given a reference to a greenlet that some other thread + * attempted to delete (has a refcount of 0) store it for later + * deletion when the thread this state belongs to is current. + */ + inline void delete_when_thread_running(PyGreenlet* to_del) + { + Py_INCREF(to_del); + this->deleteme.push_back(to_del); + } + + /** + * Set to std::clock_t(-1) to disable. + */ + inline static std::clock_t& clocks_used_doing_gc() + { + return ThreadState::_clocks_used_doing_gc; + } + + ~ThreadState() + { + if (!PyInterpreterState_Head()) { + // We shouldn't get here (our callers protect us) + // but if we do, all we can do is bail early. + return; + } + + // We should not have an "origin" greenlet; that only exists + // for the temporary time during a switch, which should not + // be in progress as the thread dies. + //assert(!this->switching_state.origin); + + this->tracefunc.CLEAR(); + + // Forcibly GC as much as we can. + this->clear_deleteme_list(true); + + // The pending call did this. + assert(this->main_greenlet->thread_state() == nullptr); + + // If the main greenlet is the current greenlet, + // then we "fell off the end" and the thread died. + // It's possible that there is some other greenlet that + // switched to us, leaving a reference to the main greenlet + // on the stack, somewhere uncollectible. Try to detect that. + if (this->current_greenlet == this->main_greenlet && this->current_greenlet) { + assert(this->current_greenlet->is_currently_running_in_some_thread()); + // Drop one reference we hold. + this->current_greenlet.CLEAR(); + assert(!this->current_greenlet); + // Only our reference to the main greenlet should be left, + // But hold onto the pointer in case we need to do extra cleanup. + PyGreenlet* old_main_greenlet = this->main_greenlet.borrow(); + Py_ssize_t cnt = this->main_greenlet.REFCNT(); + this->main_greenlet.CLEAR(); + if (ThreadState::_clocks_used_doing_gc != std::clock_t(-1) + && cnt == 2 && Py_REFCNT(old_main_greenlet) == 1) { + // Highly likely that the reference is somewhere on + // the stack, not reachable by GC. Verify. + // XXX: This is O(n) in the total number of objects. + // TODO: Add a way to disable this at runtime, and + // another way to report on it. + std::clock_t begin = std::clock(); + NewReference gc(PyImport_ImportModule("gc")); + if (gc) { + OwnedObject get_referrers = gc.PyRequireAttr(ThreadState::get_referrers_name); + OwnedList refs(get_referrers.PyCall(old_main_greenlet)); + if (refs && refs.empty()) { + assert(refs.REFCNT() == 1); + // We found nothing! So we left a dangling + // reference: Probably the last thing some + // other greenlet did was call + // 'getcurrent().parent.switch()' to switch + // back to us. Clean it up. This will be the + // case on CPython 3.7 and newer, as they use + // an internal calling conversion that avoids + // creating method objects and storing them on + // the stack. + Py_DECREF(old_main_greenlet); + } + else if (refs + && refs.size() == 1 + && PyCFunction_Check(refs.at(0)) + && Py_REFCNT(refs.at(0)) == 2) { + assert(refs.REFCNT() == 1); + // Ok, we found a C method that refers to the + // main greenlet, and its only referenced + // twice, once in the list we just created, + // once from...somewhere else. If we can't + // find where else, then this is a leak. + // This happens in older versions of CPython + // that create a bound method object somewhere + // on the stack that we'll never get back to. + if (PyCFunction_GetFunction(refs.at(0).borrow()) == (PyCFunction)green_switch) { + BorrowedObject function_w = refs.at(0); + refs.clear(); // destroy the reference + // from the list. + // back to one reference. Can *it* be + // found? + assert(function_w.REFCNT() == 1); + refs = get_referrers.PyCall(function_w); + if (refs && refs.empty()) { + // Nope, it can't be found so it won't + // ever be GC'd. Drop it. + Py_CLEAR(function_w); + } + } + } + std::clock_t end = std::clock(); + ThreadState::_clocks_used_doing_gc += (end - begin); + } + } + } + + // We need to make sure this greenlet appears to be dead, + // because otherwise deallocing it would fail to raise an + // exception in it (the thread is dead) and put it back in our + // deleteme list. + if (this->current_greenlet) { + this->current_greenlet->murder_in_place(); + this->current_greenlet.CLEAR(); + } + + if (this->main_greenlet) { + // Couldn't have been the main greenlet that was running + // when the thread exited (because we already cleared this + // pointer if it was). This shouldn't be possible? + + // If the main greenlet was current when the thread died (it + // should be, right?) then we cleared its self pointer above + // when we cleared the current greenlet's main greenlet pointer. + // assert(this->main_greenlet->main_greenlet == this->main_greenlet + // || !this->main_greenlet->main_greenlet); + // // self reference, probably gone + // this->main_greenlet->main_greenlet.CLEAR(); + + // This will actually go away when the ivar is destructed. + this->main_greenlet.CLEAR(); + } + + if (PyErr_Occurred()) { + PyErr_WriteUnraisable(NULL); + PyErr_Clear(); + } + + } + +}; + +ImmortalString ThreadState::get_referrers_name(nullptr); +PythonAllocator ThreadState::allocator; +std::clock_t ThreadState::_clocks_used_doing_gc(0); + + + + + +}; // namespace greenlet + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/TThreadStateCreator.hpp b/venv/lib/python3.12/site-packages/greenlet/TThreadStateCreator.hpp new file mode 100644 index 0000000..2ec7ab5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/TThreadStateCreator.hpp @@ -0,0 +1,102 @@ +#ifndef GREENLET_THREAD_STATE_CREATOR_HPP +#define GREENLET_THREAD_STATE_CREATOR_HPP + +#include +#include + +#include "greenlet_internal.hpp" +#include "greenlet_refs.hpp" +#include "greenlet_thread_support.hpp" + +#include "TThreadState.hpp" + +namespace greenlet { + + +typedef void (*ThreadStateDestructor)(ThreadState* const); + +template +class ThreadStateCreator +{ +private: + // Initialized to 1, and, if still 1, created on access. + // Set to 0 on destruction. + ThreadState* _state; + G_NO_COPIES_OF_CLS(ThreadStateCreator); + + inline bool has_initialized_state() const noexcept + { + return this->_state != (ThreadState*)1; + } + + inline bool has_state() const noexcept + { + return this->has_initialized_state() && this->_state != nullptr; + } + +public: + + // Only one of these, auto created per thread. + // Constructing the state constructs the MainGreenlet. + ThreadStateCreator() : + _state((ThreadState*)1) + { + } + + ~ThreadStateCreator() + { + if (this->has_state()) { + Destructor(this->_state); + } + + this->_state = nullptr; + } + + inline ThreadState& state() + { + // The main greenlet will own this pointer when it is created, + // which will be right after this. The plan is to give every + // greenlet a pointer to the main greenlet for the thread it + // runs in; if we are doing something cross-thread, we need to + // access the pointer from the main greenlet. Deleting the + // thread, and hence the thread-local storage, will delete the + // state pointer in the main greenlet. + if (!this->has_initialized_state()) { + // XXX: Assuming allocation never fails + this->_state = new ThreadState; + // For non-standard threading, we need to store an object + // in the Python thread state dictionary so that it can be + // DECREF'd when the thread ends (ideally; the dict could + // last longer) and clean this object up. + } + if (!this->_state) { + throw std::runtime_error("Accessing state after destruction."); + } + return *this->_state; + } + + operator ThreadState&() + { + return this->state(); + } + + operator ThreadState*() + { + return &this->state(); + } + + inline int tp_traverse(visitproc visit, void* arg) + { + if (this->has_state()) { + return this->_state->tp_traverse(visit, arg); + } + return 0; + } + +}; + + + +}; // namespace greenlet + +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/TThreadStateDestroy.cpp b/venv/lib/python3.12/site-packages/greenlet/TThreadStateDestroy.cpp new file mode 100644 index 0000000..449b788 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/TThreadStateDestroy.cpp @@ -0,0 +1,217 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + * Implementation of the ThreadState destructors. + * + * Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ +#ifndef T_THREADSTATE_DESTROY +#define T_THREADSTATE_DESTROY + +#include "TGreenlet.hpp" + +#include "greenlet_thread_support.hpp" +#include "greenlet_compiler_compat.hpp" +#include "TGreenletGlobals.cpp" +#include "TThreadState.hpp" +#include "TThreadStateCreator.hpp" + +namespace greenlet { + +extern "C" { + +struct ThreadState_DestroyNoGIL +{ + /** + This function uses the same lock that the PendingCallback does + */ + static void + MarkGreenletDeadAndQueueCleanup(ThreadState* const state) + { +#if GREENLET_BROKEN_THREAD_LOCAL_CLEANUP_JUST_LEAK + return; +#endif + // We are *NOT* holding the GIL. Our thread is in the middle + // of its death throes and the Python thread state is already + // gone so we can't use most Python APIs. One that is safe is + // ``Py_AddPendingCall``, unless the interpreter itself has + // been torn down. There is a limited number of calls that can + // be queued: 32 (NPENDINGCALLS) in CPython 3.10, so we + // coalesce these calls using our own queue. + + if (!MarkGreenletDeadIfNeeded(state)) { + // No state, or no greenlet + return; + } + + // XXX: Because we don't have the GIL, this is a race condition. + if (!PyInterpreterState_Head()) { + // We have to leak the thread state, if the + // interpreter has shut down when we're getting + // deallocated, we can't run the cleanup code that + // deleting it would imply. + return; + } + + AddToCleanupQueue(state); + + } + +private: + + // If the state has an allocated main greenlet: + // - mark the greenlet as dead by disassociating it from the state; + // - return 1 + // Otherwise, return 0. + static bool + MarkGreenletDeadIfNeeded(ThreadState* const state) + { + if (state && state->has_main_greenlet()) { + // mark the thread as dead ASAP. + // this is racy! If we try to throw or switch to a + // greenlet from this thread from some other thread before + // we clear the state pointer, it won't realize the state + // is dead which can crash the process. + PyGreenlet* p(state->borrow_main_greenlet().borrow()); + assert(p->pimpl->thread_state() == state || p->pimpl->thread_state() == nullptr); + dynamic_cast(p->pimpl)->thread_state(nullptr); + return true; + } + return false; + } + + static void + AddToCleanupQueue(ThreadState* const state) + { + assert(state && state->has_main_greenlet()); + + // NOTE: Because we're not holding the GIL here, some other + // Python thread could run and call ``os.fork()``, which would + // be bad if that happened while we are holding the cleanup + // lock (it wouldn't function in the child process). + // Make a best effort to try to keep the duration we hold the + // lock short. + // TODO: On platforms that support it, use ``pthread_atfork`` to + // drop this lock. + LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock); + + mod_globs->queue_to_destroy(state); + if (mod_globs->thread_states_to_destroy.size() == 1) { + // We added the first item to the queue. We need to schedule + // the cleanup. + + // A size greater than 1 means that we have already added the pending call, + // and in fact, it may be executing now. + // If it is executing, our lock makes sure that it will see the item we just added + // to the queue on its next iteration (after we release the lock) + // + // A size of 1 means there is no pending call, OR the pending call is + // currently executing, has dropped the lock, and is deleting the last item + // from the queue; its next iteration will go ahead and delete the item we just added. + // And the pending call we schedule here will have no work to do. + int result = AddPendingCall( + PendingCallback_DestroyQueueWithGIL, + nullptr); + if (result < 0) { + // Hmm, what can we do here? + fprintf(stderr, + "greenlet: WARNING: failed in call to Py_AddPendingCall; " + "expect a memory leak.\n"); + } + } + } + + static int + PendingCallback_DestroyQueueWithGIL(void* UNUSED(arg)) + { + // We're holding the GIL here, so no Python code should be able to + // run to call ``os.fork()``. + while (1) { + ThreadState* to_destroy; + { + LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock); + if (mod_globs->thread_states_to_destroy.empty()) { + break; + } + to_destroy = mod_globs->take_next_to_destroy(); + } + assert(to_destroy); + assert(to_destroy->has_main_greenlet()); + // Drop the lock while we do the actual deletion. + // This allows other calls to MarkGreenletDeadAndQueueCleanup + // to enter and add to our queue. + DestroyOneWithGIL(to_destroy); + } + return 0; + } + + static void + DestroyOneWithGIL(const ThreadState* const state) + { + // Holding the GIL. + // Passed a non-shared pointer to the actual thread state. + // state -> main greenlet + assert(state->has_main_greenlet()); + PyGreenlet* main(state->borrow_main_greenlet()); + // When we need to do cross-thread operations, we check this. + // A NULL value means the thread died some time ago. + // We do this here, rather than in a Python dealloc function + // for the greenlet, in case there's still a reference out + // there. + dynamic_cast(main->pimpl)->thread_state(nullptr); + + delete state; // Deleting this runs the destructor, DECREFs the main greenlet. + } + + + static int AddPendingCall(int (*func)(void*), void* arg) + { + // If the interpreter is in the middle of finalizing, we can't add a + // pending call. Trying to do so will end up in a SIGSEGV, as + // Py_AddPendingCall will not be able to get the interpreter and will + // try to dereference a NULL pointer. It's possible this can still + // segfault if we happen to get context switched, and maybe we should + // just always implement our own AddPendingCall, but I'd like to see if + // this works first +#if GREENLET_PY313 + if (Py_IsFinalizing()) { +#else + if (_Py_IsFinalizing()) { +#endif +#ifdef GREENLET_DEBUG + // No need to log in the general case. Yes, we'll leak, + // but we're shutting down so it should be ok. + fprintf(stderr, + "greenlet: WARNING: Interpreter is finalizing. Ignoring " + "call to Py_AddPendingCall; \n"); +#endif + return 0; + } + return Py_AddPendingCall(func, arg); + } + + + + + +}; +}; + +}; // namespace greenlet + +// The intent when GET_THREAD_STATE() is needed multiple times in a +// function is to take a reference to its return value in a local +// variable, to avoid the thread-local indirection. On some platforms +// (macOS), accessing a thread-local involves a function call (plus an +// initial function call in each function that uses a thread local); +// in contrast, static volatile variables are at some pre-computed +// offset. +typedef greenlet::ThreadStateCreator ThreadStateCreator; +static thread_local ThreadStateCreator g_thread_state_global; +#define GET_THREAD_STATE() g_thread_state_global + +#endif //T_THREADSTATE_DESTROY diff --git a/venv/lib/python3.12/site-packages/greenlet/TUserGreenlet.cpp b/venv/lib/python3.12/site-packages/greenlet/TUserGreenlet.cpp new file mode 100644 index 0000000..73a8133 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/TUserGreenlet.cpp @@ -0,0 +1,662 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ +/** + * Implementation of greenlet::UserGreenlet. + * + * Format with: + * clang-format -i --style=file src/greenlet/greenlet.c + * + * + * Fix missing braces with: + * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements" +*/ +#ifndef T_USER_GREENLET_CPP +#define T_USER_GREENLET_CPP + +#include "greenlet_internal.hpp" +#include "TGreenlet.hpp" + +#include "TThreadStateDestroy.cpp" + + +namespace greenlet { +using greenlet::refs::BorrowedMainGreenlet; +greenlet::PythonAllocator UserGreenlet::allocator; + +void* UserGreenlet::operator new(size_t UNUSED(count)) +{ + return allocator.allocate(1); +} + + +void UserGreenlet::operator delete(void* ptr) +{ + return allocator.deallocate(static_cast(ptr), + 1); +} + + +UserGreenlet::UserGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent) + : Greenlet(p), _parent(the_parent) +{ +} + +UserGreenlet::~UserGreenlet() +{ + // Python 3.11: If we don't clear out the raw frame datastack + // when deleting an unfinished greenlet, + // TestLeaks.test_untracked_memory_doesnt_increase_unfinished_thread_dealloc_in_main fails. + this->python_state.did_finish(nullptr); + this->tp_clear(); +} + + +const BorrowedMainGreenlet +UserGreenlet::main_greenlet() const +{ + return this->_main_greenlet; +} + + +BorrowedMainGreenlet +UserGreenlet::find_main_greenlet_in_lineage() const +{ + if (this->started()) { + assert(this->_main_greenlet); + return BorrowedMainGreenlet(this->_main_greenlet); + } + + if (!this->_parent) { + /* garbage collected greenlet in chain */ + // XXX: WHAT? + return BorrowedMainGreenlet(nullptr); + } + + return this->_parent->find_main_greenlet_in_lineage(); +} + + +/** + * CAUTION: This will allocate memory and may trigger garbage + * collection and arbitrary Python code. + */ +OwnedObject +UserGreenlet::throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state) +{ + /* The dying greenlet cannot be a parent of ts_current + because the 'parent' field chain would hold a + reference */ + UserGreenlet::ParentIsCurrentGuard with_current_parent(this, current_thread_state); + + // We don't care about the return value, only whether an + // exception happened. Whether or not an exception happens, + // we need to restore the parent in case the greenlet gets + // resurrected. + return Greenlet::throw_GreenletExit_during_dealloc(current_thread_state); +} + +ThreadState* +UserGreenlet::thread_state() const noexcept +{ + // TODO: maybe make this throw, if the thread state isn't there? + // if (!this->main_greenlet) { + // throw std::runtime_error("No thread state"); // TODO: Better exception + // } + if (!this->_main_greenlet) { + return nullptr; + } + return this->_main_greenlet->thread_state(); +} + + +bool +UserGreenlet::was_running_in_dead_thread() const noexcept +{ + return this->_main_greenlet && !this->thread_state(); +} + +OwnedObject +UserGreenlet::g_switch() +{ + assert(this->args() || PyErr_Occurred()); + + try { + this->check_switch_allowed(); + } + catch (const PyErrOccurred&) { + this->release_args(); + throw; + } + + // Switching greenlets used to attempt to clean out ones that need + // deleted *if* we detected a thread switch. Should it still do + // that? + // An issue is that if we delete a greenlet from another thread, + // it gets queued to this thread, and ``kill_greenlet()`` switches + // back into the greenlet + + /* find the real target by ignoring dead greenlets, + and if necessary starting a greenlet. */ + switchstack_result_t err; + Greenlet* target = this; + // TODO: probably cleaner to handle the case where we do + // switch to ourself separately from the other cases. + // This can probably even further be simplified if we keep + // track of the switching_state we're going for and just call + // into g_switch() if it's not ourself. The main problem with that + // is that we would be using more stack space. + bool target_was_me = true; + bool was_initial_stub = false; + while (target) { + if (target->active()) { + if (!target_was_me) { + target->args() <<= this->args(); + assert(!this->args()); + } + err = target->g_switchstack(); + break; + } + if (!target->started()) { + // We never encounter a main greenlet that's not started. + assert(!target->main()); + UserGreenlet* real_target = static_cast(target); + assert(real_target); + void* dummymarker; + was_initial_stub = true; + if (!target_was_me) { + target->args() <<= this->args(); + assert(!this->args()); + } + try { + // This can only throw back to us while we're + // still in this greenlet. Once the new greenlet + // is bootstrapped, it has its own exception state. + err = real_target->g_initialstub(&dummymarker); + } + catch (const PyErrOccurred&) { + this->release_args(); + throw; + } + catch (const GreenletStartedWhileInPython&) { + // The greenlet was started sometime before this + // greenlet actually switched to it, i.e., + // "concurrent" calls to switch() or throw(). + // We need to retry the switch. + // Note that the current greenlet has been reset + // to this one (or we wouldn't be running!) + continue; + } + break; + } + + target = target->parent(); + target_was_me = false; + } + // The ``this`` pointer and all other stack or register based + // variables are invalid now, at least where things succeed + // above. + // But this one, probably not so much? It's not clear if it's + // safe to throw an exception at this point. + + if (err.status < 0) { + // If we get here, either g_initialstub() + // failed, or g_switchstack() failed. Either one of those + // cases SHOULD leave us in the original greenlet with a valid + // stack. + return this->on_switchstack_or_initialstub_failure(target, err, target_was_me, was_initial_stub); + } + + // err.the_new_current_greenlet would be the same as ``target``, + // if target wasn't probably corrupt. + return err.the_new_current_greenlet->g_switch_finish(err); +} + + + +Greenlet::switchstack_result_t +UserGreenlet::g_initialstub(void* mark) +{ + OwnedObject run; + + // We need to grab a reference to the current switch arguments + // in case we're entered concurrently during the call to + // GetAttr() and have to try again. + // We'll restore them when we return in that case. + // Scope them tightly to avoid ref leaks. + { + SwitchingArgs args(this->args()); + + /* save exception in case getattr clears it */ + PyErrPieces saved; + + /* + self.run is the object to call in the new greenlet. + This could run arbitrary python code and switch greenlets! + */ + run = this->self().PyRequireAttr(mod_globs->str_run); + /* restore saved exception */ + saved.PyErrRestore(); + + + /* recheck that it's safe to switch in case greenlet reparented anywhere above */ + this->check_switch_allowed(); + + /* by the time we got here another start could happen elsewhere, + * that means it should now be a regular switch. + * This can happen if the Python code is a subclass that implements + * __getattribute__ or __getattr__, or makes ``run`` a descriptor; + * all of those can run arbitrary code that switches back into + * this greenlet. + */ + if (this->stack_state.started()) { + // the successful switch cleared these out, we need to + // restore our version. They will be copied on up to the + // next target. + assert(!this->args()); + this->args() <<= args; + throw GreenletStartedWhileInPython(); + } + } + + // Sweet, if we got here, we have the go-ahead and will switch + // greenlets. + // Nothing we do from here on out should allow for a thread or + // greenlet switch: No arbitrary calls to Python, including + // decref'ing + +#if GREENLET_USE_CFRAME + /* OK, we need it, we're about to switch greenlets, save the state. */ + /* + See green_new(). This is a stack-allocated variable used + while *self* is in PyObject_Call(). + We want to defer copying the state info until we're sure + we need it and are in a stable place to do so. + */ + _PyCFrame trace_info; + + this->python_state.set_new_cframe(trace_info); +#endif + /* start the greenlet */ + ThreadState& thread_state = GET_THREAD_STATE().state(); + this->stack_state = StackState(mark, + thread_state.borrow_current()->stack_state); + this->python_state.set_initial_state(PyThreadState_GET()); + this->exception_state.clear(); + this->_main_greenlet = thread_state.get_main_greenlet(); + + /* perform the initial switch */ + switchstack_result_t err = this->g_switchstack(); + /* returns twice! + The 1st time with ``err == 1``: we are in the new greenlet. + This one owns a greenlet that used to be current. + The 2nd time with ``err <= 0``: back in the caller's + greenlet; this happens if the child finishes or switches + explicitly to us. Either way, the ``err`` variable is + created twice at the same memory location, but possibly + having different ``origin`` values. Note that it's not + constructed for the second time until the switch actually happens. + */ + if (err.status == 1) { + // In the new greenlet. + + // This never returns! Calling inner_bootstrap steals + // the contents of our run object within this stack frame, so + // it is not valid to do anything with it. + try { + this->inner_bootstrap(err.origin_greenlet.relinquish_ownership(), + run.relinquish_ownership()); + } + // Getting a C++ exception here isn't good. It's probably a + // bug in the underlying greenlet, meaning it's probably a + // C++ extension. We're going to abort anyway, but try to + // display some nice information *if* possible. Some obscure + // platforms don't properly support this (old 32-bit Arm, see see + // https://github.com/python-greenlet/greenlet/issues/385); that's not + // great, but should usually be OK because, as mentioned above, we're + // terminating anyway. + // + // The catching is tested by + // ``test_cpp.CPPTests.test_unhandled_exception_in_greenlet_aborts``. + // + // PyErrOccurred can theoretically be thrown by + // inner_bootstrap() -> g_switch_finish(), but that should + // never make it back to here. It is a std::exception and + // would be caught if it is. + catch (const std::exception& e) { + std::string base = "greenlet: Unhandled C++ exception: "; + base += e.what(); + Py_FatalError(base.c_str()); + } + catch (...) { + // Some compilers/runtimes use exceptions internally. + // It appears that GCC on Linux with libstdc++ throws an + // exception internally at process shutdown time to unwind + // stacks and clean up resources. Depending on exactly + // where we are when the process exits, that could result + // in an unknown exception getting here. If we + // Py_FatalError() or abort() here, we interfere with + // orderly process shutdown. Throwing the exception on up + // is the right thing to do. + // + // gevent's ``examples/dns_mass_resolve.py`` demonstrates this. +#ifndef NDEBUG + fprintf(stderr, + "greenlet: inner_bootstrap threw unknown exception; " + "is the process terminating?\n"); +#endif + throw; + } + Py_FatalError("greenlet: inner_bootstrap returned with no exception.\n"); + } + + + // In contrast, notice that we're keeping the origin greenlet + // around as an owned reference; we need it to call the trace + // function for the switch back into the parent. It was only + // captured at the time the switch actually happened, though, + // so we haven't been keeping an extra reference around this + // whole time. + + /* back in the parent */ + if (err.status < 0) { + /* start failed badly, restore greenlet state */ + this->stack_state = StackState(); + this->_main_greenlet.CLEAR(); + // CAUTION: This may run arbitrary Python code. + run.CLEAR(); // inner_bootstrap didn't run, we own the reference. + } + + // In the success case, the spawned code (inner_bootstrap) will + // take care of decrefing this, so we relinquish ownership so as + // to not double-decref. + + run.relinquish_ownership(); + + return err; +} + + +void +UserGreenlet::inner_bootstrap(PyGreenlet* origin_greenlet, PyObject* run) +{ + // The arguments here would be another great place for move. + // As it is, we take them as a reference so that when we clear + // them we clear what's on the stack above us. Do that NOW, and + // without using a C++ RAII object, + // so there's no way that exiting the parent frame can clear it, + // or we clear it unexpectedly. This arises in the context of the + // interpreter shutting down. See https://github.com/python-greenlet/greenlet/issues/325 + //PyObject* run = _run.relinquish_ownership(); + + /* in the new greenlet */ + assert(this->thread_state()->borrow_current() == BorrowedGreenlet(this->_self)); + // C++ exceptions cannot propagate to the parent greenlet from + // here. (TODO: Do we need a catch(...) clause, perhaps on the + // function itself? ALl we could do is terminate the program.) + // NOTE: On 32-bit Windows, the call chain is extremely + // important here in ways that are subtle, having to do with + // the depth of the SEH list. The call to restore it MUST NOT + // add a new SEH handler to the list, or we'll restore it to + // the wrong thing. + this->thread_state()->restore_exception_state(); + /* stack variables from above are no good and also will not unwind! */ + // EXCEPT: That can't be true, we access run, among others, here. + + this->stack_state.set_active(); /* running */ + + // We're about to possibly run Python code again, which + // could switch back/away to/from us, so we need to grab the + // arguments locally. + SwitchingArgs args; + args <<= this->args(); + assert(!this->args()); + + // XXX: We could clear this much earlier, right? + // Or would that introduce the possibility of running Python + // code when we don't want to? + // CAUTION: This may run arbitrary Python code. + this->_run_callable.CLEAR(); + + + // The first switch we need to manually call the trace + // function here instead of in g_switch_finish, because we + // never return there. + if (OwnedObject tracefunc = this->thread_state()->get_tracefunc()) { + OwnedGreenlet trace_origin; + trace_origin = origin_greenlet; + try { + g_calltrace(tracefunc, + args ? mod_globs->event_switch : mod_globs->event_throw, + trace_origin, + this->_self); + } + catch (const PyErrOccurred&) { + /* Turn trace errors into switch throws */ + args.CLEAR(); + } + } + + // We no longer need the origin, it was only here for + // tracing. + // We may never actually exit this stack frame so we need + // to explicitly clear it. + // This could run Python code and switch. + Py_CLEAR(origin_greenlet); + + OwnedObject result; + if (!args) { + /* pending exception */ + result = NULL; + } + else { + /* call g.run(*args, **kwargs) */ + // This could result in further switches + try { + //result = run.PyCall(args.args(), args.kwargs()); + // CAUTION: Just invoking this, before the function even + // runs, may cause memory allocations, which may trigger + // GC, which may run arbitrary Python code. + result = OwnedObject::consuming(PyObject_Call(run, args.args().borrow(), args.kwargs().borrow())); + } + catch (...) { + // Unhandled C++ exception! + + // If we declare ourselves as noexcept, if we don't catch + // this here, most platforms will just abort() the + // process. But on 64-bit Windows with older versions of + // the C runtime, this can actually corrupt memory and + // just return. We see this when compiling with the + // Windows 7.0 SDK targeting Windows Server 2008, but not + // when using the Appveyor Visual Studio 2019 image. So + // this currently only affects Python 2.7 on Windows 64. + // That is, the tests pass and the runtime aborts + // everywhere else. + // + // However, if we catch it and try to continue with a + // Python error, then all Windows 64 bit platforms corrupt + // memory. So all we can do is manually abort, hopefully + // with a good error message. (Note that the above was + // tested WITHOUT the `/EHr` switch being used at compile + // time, so MSVC may have "optimized" out important + // checking. Using that switch, we may be in a better + // place in terms of memory corruption.) But sometimes it + // can't be caught here at all, which is confusing but not + // terribly surprising; so again, the G_NOEXCEPT_WIN32 + // plus "/EHr". + // + // Hopefully the basic C stdlib is still functional enough + // for us to at least print an error. + // + // It gets more complicated than that, though, on some + // platforms, specifically at least Linux/gcc/libstdc++. They use + // an exception to unwind the stack when a background + // thread exits. (See comments about noexcept.) So this + // may not actually represent anything untoward. On those + // platforms we allow throws of this to propagate, or + // attempt to anyway. +# if defined(WIN32) || defined(_WIN32) + Py_FatalError( + "greenlet: Unhandled C++ exception from a greenlet run function. " + "Because memory is likely corrupted, terminating process."); + std::abort(); +#else + throw; +#endif + } + } + // These lines may run arbitrary code + args.CLEAR(); + Py_CLEAR(run); + + if (!result + && mod_globs->PyExc_GreenletExit.PyExceptionMatches() + && (this->args())) { + // This can happen, for example, if our only reference + // goes away after we switch back to the parent. + // See test_dealloc_switch_args_not_lost + PyErrPieces clear_error; + result <<= this->args(); + result = single_result(result); + } + this->release_args(); + this->python_state.did_finish(PyThreadState_GET()); + + result = g_handle_exit(result); + assert(this->thread_state()->borrow_current() == this->_self); + + /* jump back to parent */ + this->stack_state.set_inactive(); /* dead */ + + + // TODO: Can we decref some things here? Release our main greenlet + // and maybe parent? + for (Greenlet* parent = this->_parent; + parent; + parent = parent->parent()) { + // We need to somewhere consume a reference to + // the result; in most cases we'll never have control + // back in this stack frame again. Calling + // green_switch actually adds another reference! + // This would probably be clearer with a specific API + // to hand results to the parent. + parent->args() <<= result; + assert(!result); + // The parent greenlet now owns the result; in the + // typical case we'll never get back here to assign to + // result and thus release the reference. + try { + result = parent->g_switch(); + } + catch (const PyErrOccurred&) { + // Ignore, keep passing the error on up. + } + + /* Return here means switch to parent failed, + * in which case we throw *current* exception + * to the next parent in chain. + */ + assert(!result); + } + /* We ran out of parents, cannot continue */ + PyErr_WriteUnraisable(this->self().borrow_o()); + Py_FatalError("greenlet: ran out of parent greenlets while propagating exception; " + "cannot continue"); + std::abort(); +} + +void +UserGreenlet::run(const BorrowedObject nrun) +{ + if (this->started()) { + throw AttributeError( + "run cannot be set " + "after the start of the greenlet"); + } + this->_run_callable = nrun; +} + +const OwnedGreenlet +UserGreenlet::parent() const +{ + return this->_parent; +} + +void +UserGreenlet::parent(const BorrowedObject raw_new_parent) +{ + if (!raw_new_parent) { + throw AttributeError("can't delete attribute"); + } + + BorrowedMainGreenlet main_greenlet_of_new_parent; + BorrowedGreenlet new_parent(raw_new_parent.borrow()); // could + // throw + // TypeError! + for (BorrowedGreenlet p = new_parent; p; p = p->parent()) { + if (p == this->self()) { + throw ValueError("cyclic parent chain"); + } + main_greenlet_of_new_parent = p->main_greenlet(); + } + + if (!main_greenlet_of_new_parent) { + throw ValueError("parent must not be garbage collected"); + } + + if (this->started() + && this->_main_greenlet != main_greenlet_of_new_parent) { + throw ValueError("parent cannot be on a different thread"); + } + + this->_parent = new_parent; +} + +void +UserGreenlet::murder_in_place() +{ + this->_main_greenlet.CLEAR(); + Greenlet::murder_in_place(); +} + +bool +UserGreenlet::belongs_to_thread(const ThreadState* thread_state) const +{ + return Greenlet::belongs_to_thread(thread_state) && this->_main_greenlet == thread_state->borrow_main_greenlet(); +} + + +int +UserGreenlet::tp_traverse(visitproc visit, void* arg) +{ + Py_VISIT(this->_parent.borrow_o()); + Py_VISIT(this->_main_greenlet.borrow_o()); + Py_VISIT(this->_run_callable.borrow_o()); + + return Greenlet::tp_traverse(visit, arg); +} + +int +UserGreenlet::tp_clear() +{ + Greenlet::tp_clear(); + this->_parent.CLEAR(); + this->_main_greenlet.CLEAR(); + this->_run_callable.CLEAR(); + return 0; +} + +UserGreenlet::ParentIsCurrentGuard::ParentIsCurrentGuard(UserGreenlet* p, + const ThreadState& thread_state) + : oldparent(p->_parent), + greenlet(p) +{ + p->_parent = thread_state.get_current(); +} + +UserGreenlet::ParentIsCurrentGuard::~ParentIsCurrentGuard() +{ + this->greenlet->_parent = oldparent; + oldparent.CLEAR(); +} + +}; //namespace greenlet +#endif diff --git a/venv/lib/python3.12/site-packages/greenlet/__init__.py b/venv/lib/python3.12/site-packages/greenlet/__init__.py new file mode 100644 index 0000000..6401497 --- /dev/null +++ b/venv/lib/python3.12/site-packages/greenlet/__init__.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +""" +The root of the greenlet package. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__all__ = [ + '__version__', + '_C_API', + + 'GreenletExit', + 'error', + + 'getcurrent', + 'greenlet', + + 'gettrace', + 'settrace', +] + +# pylint:disable=no-name-in-module + +### +# Metadata +### +__version__ = '3.2.4' +from ._greenlet import _C_API # pylint:disable=no-name-in-module + +### +# Exceptions +### +from ._greenlet import GreenletExit +from ._greenlet import error + +### +# greenlets +### +from ._greenlet import getcurrent +from ._greenlet import greenlet + +### +# tracing +### +try: + from ._greenlet import gettrace + from ._greenlet import settrace +except ImportError: + # Tracing wasn't supported. + # XXX: The option to disable it was removed in 1.0, + # so this branch should be dead code. + pass + +### +# Constants +# These constants aren't documented and aren't recommended. +# In 1.0, USE_GC and USE_TRACING are always true, and USE_CONTEXT_VARS +# is the same as ``sys.version_info[:2] >= 3.7`` +### +from ._greenlet import GREENLET_USE_CONTEXT_VARS # pylint:disable=unused-import +from ._greenlet import GREENLET_USE_GC # pylint:disable=unused-import +from ._greenlet import GREENLET_USE_TRACING # pylint:disable=unused-import + +# Controlling the use of the gc module. Provisional API for this greenlet +# implementation in 2.0. +from ._greenlet import CLOCKS_PER_SEC # pylint:disable=unused-import +from ._greenlet import enable_optional_cleanup # pylint:disable=unused-import +from ._greenlet import get_clocks_used_doing_optional_cleanup # pylint:disable=unused-import + +# Other APIS in the _greenlet module are for test support. diff --git a/venv/lib/python3.12/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..86f8e6ca000604e87628cfbc3438d89bdaa63370 GIT binary patch literal 1056 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 JelZR4{{@l8vwHvl literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/wire.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/wire.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b846b92d1e60deef1a37cb0006d2878feb300184 GIT binary patch literal 5766 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/zone.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/zone.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d86ae4acdba524c4b99ad99c25f6a105ae34489 GIT binary patch literal 68604 zcmeIb33Oc7c_#R3MXjj9z7PNl1c;3QxKkAO1>8gul1NITAj@D8uRsC>3e>BDNR$Da zQfp$sQ9{6$12A!#(6pyT9w|pucXCAMq{p(8iG4ablPVRJDwR1sOyW7|o}4~YBA1@L6{aqp-T`AqH(~`WnjO?E+hLjb(z?&xyy`S(|~2r+GQQg>&hFnb=d~(UG_mo zmt)Y`4b)8%D(3*_HI`L{^^EtY>vHw@m&mmwzkd-%9y+ znfzNN|5nStHS+Ir_G=#S4c2zm4zB20F<94CXAq3S0YS99CWzL6xBs9r5SicpCo+D; z_qK`qx*AySJmj_o8r0B67HUVRLlfG>LY)Y8X+oP>s2idApQHX3mfM5e-p`SH70X?K z+=ZVbcPq?uuoBHDdYVbM@N9ir36t zYgwNv(Whm|D=PJ8e*1efe#NJL_YEv}6>?W2cfS&!`RyOb_!Xa;`!VD>uY_iP^}<*^ zHK=EK;4vlt+j5q!$62fov9)g-d0^KT7PbOmb%8BPj;^gstg#;XS7!37HEv_2Hz2lg zfl-TO`lI;NR%{oI$Tg~jXMX#?p7AR_u_>@YG>Xk)6Wpd=yVw%2E@gHEcCp#P=g6uB z>U~wIE%VFfQ!DCf3+xhk?sm+u)%aV3QuOBWYnj}NPtLhEuqI#&?5aR8YfX7zLvN|L z?lnu--hbj)?D{nsi}e_bj?Xa`P3pYZ@M|+KHexLHVb-H>y(V!J`bG1Q_BWb+GgYQY2!#*&LuYpnoEeh( z!e<9lE*^Ni=WJjwkn(hkEKT=WBnn8O9%cU1uR(s9^TIPN1O$K<0)Z7>CUn4*%G=`? zsr`}46K4ZH>P-Z1zP^xe&_B>SBn<|{_Rj0(l&!lv=pPJpcc&cP-Gf8o@BsN;-Q6z@ z`v>HhyzXvssHeO8x-dtzezt4%*`dL}>Yky2(<7^U&icdQk<~8;f-kQg=sUgo{7Crh zP;hZ_%5 z==hVnBh6Vo#i03uLq0_Kjve#)!@j<7$cJ%0(-&;-lnSV#QgT?8<>aeWoUQPvjatuB zP~?&tW$qm6JRVu4*Cu?7W5+^)@S<8(MXe%dr?yoad8;UVNv(1}6Fe6jx)6Ml4Me0? z+q_0)d}x*o^K=UgRSh+YoW0sct>TTM@Fg{>?3rL_`26`HDI5^BD`&T5{Ob zq@X3W)7Ck}2Qji*`*nTtg6Mj(bKlX9B}~AT)D&`_#Gkq>9_Ov2@Flgbg17D2fYjI9 z*W(ZO4F&i6`v!)kK%|bED>sjSZ(lE>0-`TG8>xxp9M?9njW?0PQ)b%A zQda&!QVuCFfT~~aiy%x2gomYIj|s_?#|#g>&oyA1ofbw7mz-f`!v*&Bw(Nh^Fe>zG z^U3)Yh)!CIc$#)O&!}2LNL#K^Q?5}eK;FEZjCjrD9TP^;Upw4>`vcRNMvWoksEMbq z6T%E{r5ydP`T6?u=ZDn`lFJ}yM!=M5=>_4U>3QLTq1oISsla~AR%>S7i-F$0U_gv) zr1AHi_e=P}vFRHe4uyTE13temG<;ed8ua%CeM7yzu(IBH36bUUhYI*u{_xO{Z(t~R z#)kup)zoZD*$xLr_DK>BUuR#a+kZMVG%y?vq+EDWicr5{5tQ6)W?@n@B6uzvieyJ*h-QTkk3^?MD!=<>|3IJU4+mJRv;l$7;V<-kIG2R9 zOR(i%`ucsF;3*(p#RU+l7_UWyj)$ z$D^*}A3IBuMP<`Fr*_V6iWjwvcciNXm-o`)bcw~jBU!dAQPvbIYr0k%FIzKy@UBDf zR;H~&an*Q7(&@Q$_|y9Zg16}M#fggvPhHGYcTX_cciguMMa!bgw#5s#M_t>~c?kL} zL|^|$-h$nY!jBr8_jpY`cvl~f?3+uvweZC=nEqX+fVp3tL~0J*>C}}Mu$>VWpT5^f z;C#R?8n@A}qsz(iyHEn1z&TGpoqsvcQtMJ{G*OMBxyuug((K9AzhfQa!w8@dY7j)t73;kBP;T+SofdyOD*EWU>&4BcU^5}gaw)0$HW zUO_PN_F6?ZfZQ@M|23hjT=c-L0MTQqbr&o?N3U5d1WBM$1c{?-nOF?BN-Tj}EtbNq z5zF8%7t7)LKs*q-YDEwZx>ktG;MUm%ao^`?9|#8`wQ-TN6r)muQK|ps)W+u%>RgUG zSALG(6^Lexst=>ufIfE4|I+@=CFZ&FK631^Rw={%9?bQfF7ee)`MU9SZ^* z1DnvQ#lt^~ToD|)A$SYxq6MS$*aCh3bsVM5=&LmU>-UG}hxM9hjO1}zg>l+y2inE| ziTgoD+y-*Cm!K=LJy62u9f*ovB5AW-CtqBPT-%jAfHL+!C`X{A*Tu-#JH!LxdhAy_ zk>jAa0WH2Xzjgh3WOfc45(M;`P7a;#+5a0l>2$&XcMu`9Be1Xk_4zq)%4`Z85FZQd z1N3zevuZQa9$G}&#kcc~`L*`zgrV-oQTJgsa$A-#4mxG&l`qO8%pkF?sOgA!NZgKZ zbrkSw1N-jVFjhMd(}}TI!usVU2bKV-@f>`XVo%zMRy-;00`&ZpxEt=%;vTri#JzBj zqb-|}rUPkCjF_9BNSV7plJ!SG(b%n0iQ63x!gF`~PMxCIQ>Tc6L|?)JDXE7YNgT5p`bZ8yN6`I3x81UY5%Nb%3h#`9q^2zOJ zA%Bb91fQQZ;?yZ1{Ov4JCt`iPnWs**f+!5yu$tHxLXAN*$9E{`3!rpRD?qakw6aqA zIE{jep+Ql}6DR<$N=HF@R%0+kMAeW~k^7#tBa3 zAe@!G_Yi$UdB|_V-`rC&1?-hbhW;~Z2pg4z3U5^Mh1LB1RwXv``w$wHA-FoL{0En% zgVrhS07VT13;u{&ikhKcA!#Vl_*E!%S-CXStx-_;x@}5^Q6t_|S{vwYB}z=@S1A!G zol5Aa32!Q`Z3MJ3e;mVlDA*V70}Uxci-Kl@JSXMlM@z7UPn#AtxwNxLrIgV;)lA-; zS&r$=W*C*_#{$FLmS_B_Q=M#SQ~qAW@V8bfH>eh#;Rw)TR7(Qgj<^@6M_9t%J_)qE zQ>W^mJa*{7p-!ya0g(KBAn3`x>IDr~+b4NKwzT^ELFDTThN(@oNN5mHrv$2+-^XU{ zRu&E_qJMaREio4AJJgv~pi0f9GEwB|p`igPQzTmBAXI;U#{$NiaWy1HA)?%7*LZ+Q|=I(IdVmM`{Juc;V);};D zVAN5mosu!KCR_2F>2*6fL>t9EVGacbdNm0sIEUm3USS{RY=dz4d1W3OGn)tr|DK?o5`|;K!BR zdmyp*cx>h^?2sax6qikE2(8C!1~3mHa;sIP86$RaI&wEHb3TR-X3E-5IW?Q#gm!S`%g4mBw{{Fo5*3a zhs`6JJ=cv=2YynPAzEA^8h3?2Ztd<4yo9;pl;;Y)Tt|H90Gvz0{d^FvXI6f{7;_|9 zRDQ?hy?kWi$jrX0N3R_H(A5m`_~oxnd~GIhb>Pathn^OCk4!{np1JzMl@~trG~FpG zN6`GQZ9?JnH`f3MtQ(RIj>1i?>N$ADh`m=Y1|nK229%cr+aZJim87p;tXRz__rIn%0%idzLIYbp~jiFMA* zPUWlB2~&Y7(8zFswi)f1V{*?tlV$axT2m=lCoMdk^g&e$1Q z!+^0R?6=JhKAm@=sLbvu?WWf~2a)q9U zgQ;#6T&0uZ_x9d$`feAMPH&#t%s04M@mfc`XzfJD?Sf@99pA5etLe?Ax7yxpyH&7; zF@%oB3p=B(&fA{q8F9Abt;25~zES-C^7od1M0{EQk1 z**z{?H*})kY#_92-E=N6q8)>jv%C9DAPixTK}>b8yxi50zd%_bADl} zovn-KH;kKVE^ zQ5%;Z>iII7i*-OtAZ4bxFTFsm&@}X`c#qiG#HaWaUgwJ7sExe*jfro>J-(>T_vMeM zl~3k*jr3C^`C-gc{2vI&9ZOz1uw!C@EP$FVm#KyXV_`kzjh=gWz zmK~qZm%l#o^_d>+H_+|eE|jsT&MmOVzl{Aovm4H9Hc0>iVF^PnBZN)8K;G0dL4Qvj zsa>EWg_GiR|5Sf8GvoKCtUZhh)I*;~dDKsJ10jp*jqYCf;7>JX8Z}*Ngba)|tb*fA za>uSTBlG*V?4>*pI!Q)N1UC9R@oDz=1x25q)1`p(DrjS?@BC0G1Y~c-$2ty@5~xgC ztd+o)mMts;gJ5Lw7e?R$eh9f4F$09308r0XZH7)Y57j3^76I@K$>(^zf;=+xIs^R^ zcJL4h92NCf2m}$r_%b;1sZ(lkAbcs9u!erX`ow5>AQxovp-*bY7Ct4ikak{O7PRWA);hv6YwY-6Nn`Ah<~Yo z?}9(O+|c4gnBqL+%_L-A?nSn<66lY|*Ok|9~u^6L5fcGzi{eesIoihEAlC3qpv|iD3?G6`R5nclG(j^n4e8YR%cV0(CJyvB31~fp!y%%bnLeDR z{b;`k2fn=hLPG_~S)sJIo>EAr>PPDY6gz~8%oph(d^xMph9jRZMouwPUh}XLLXRUo z)?$;%UQY6UJEnHb)?c&6i&jJ8&x!>)?)>z=Mevs26AUcHLjsA1!O-En=#8ysxQnpKZWwOnh zP5scL`K(>iAlzg7Vdn?74w7Mgz_yWr1v(i5D;=fjMmwd7@Bz8QH{kP)qv4v6TAFf1 zE5?*7owxlvJ(C-zw@ht`IjfW2;%Uc}V`f9l>r0yi7styXCoP6Y&O=_hXNdSuJzBYD zf~(kcYeFT9nQoaKUXQ4%M>f^vAF2GEG2<6hqRRUL1Ss@7(XX5b=apGKuPmdMewEvl zNr;>}Qqe8O9ZqLkV0R9xGO58 zscuIU%MU9$i`2pb!5;tl&@f57=K`SwR;uZER`6++N$KkuIzO`2hZ{aYT;L(51x`Z- zi}4_}Vx=jX<*k5RR2UtQRICPY!c=Gd00V|CfZm~D0(aFCADwo-7QW!*(~iy~_N8dp z+K&;3>;!y(XxYgmGxq31uvC0>0Uh>9*-!*uy-O&R%`=vZE@DvS>PLDe1m*(}oG`&t z96N!B<0YB4cH+Vb0aT}lvE8vLpccS;NK+&?)A2(Gl&CMy06D;5lXf9`3M7kV)XYd`xNFXs>^; z4}(JJ97Zc73pcR5O}m?0aq|XsH+3FYy||**oEcOOochmU!LgKpREK-c`U3t?A0!d5 z1}_Xr=adZs%N#?>)2Z!X-Jzu}_VxCnO+kRLjHAlhi#naVO z)ro?pSV2>wpeFz&WzE;><&(1P z!1zI;@|2HzGE^Q)H*qlODI50?m8VLk@)VZ~GtNfSzcw_QvV=70h-GN^z&``D`)%}* zo-y;N`IQ}`=A|LtipU7AF&FT1AZ=w<$`p;~KvkCtfw&XfHs{Gm3mF*23p6j2|0lrX zH8d|}GJri3?m0`qw}QSg%%O#&7(lxKuJ?siP@Pl7NDlaS-JG*5_w*~B3QkKo}C3qsJ&*W zzVGv*_`n>g0F0Ab;F3{%npg{?_G-B_{n(V`G!J@JQG8Iah*-HK)Ltz(14zg?^XEkE zF_AUHKnUPcQG4DE9h6>e>mpEl8AwuAZ-(N~DG?PJQgIvb!v*3lknbWnBjmgUC*_fe z$w~nXV@;U`0zoN+`0Ey3+?AoNbWmxq)^Ff7Qp~W{oH8%d!TDW818?2(MWL-@6Js+c zAW*@0no;Mnq|2Ld`C=~LZ1G!FZ&qD<`Nqq)Tsv-i3MV)G=E%(MZ+#sU@PyX~^7m}< zN8Sd|z`ZqTi{Pqah;7O#1t<8a_+g}(9~v4Yr+oQ*kvdI0 zsN(!iNbPohrAmH3(Ft8HOjvy{)&(cIdF6a6?2aTI6w72PUL_yfuV^JxWt_hbM7Ns3#XY;y~?BV)P5IrEP|(I{(?AQ>Yni>@zfkj2Q$B;W|BAld;tBJMbmE` z336S4-n=k}?rr%Sj9Evm{Umjm^Ndd0-4@0VKe!}P`aeqfwi<;x|d2UcOWLHlbths%k~8W7Z?gg zYVg!vgffQ@4NcClkw6(22-4K{XunsH-<++U;S|RdW{ASz`|@~~S(wcu=H;OSkC>NC z2>HvCAp4b;IF%#@YteqLtUdV>u(}^NI~T?R=>i1fIC9L-!iq~-*!hsM4xh)m4m9U! z04kM(i3>KgLrbGn!f(I{5w=1Q79M`x#=*Iv)WV2|!CIMszal7W8aV)EoiqpqB?)g` z%v(3x5%)HaTNdV7E*wMhc93?O2|%PZQM4{rwC=`+_qV;b?Ss;vmd1;o8n@l{7ERly z?9-kp&#YxO{MP83qc@(t`E1XoP+?QPwuE~?PY&A(w@wjU~@TO8`>3Y$_7@i7k zk|>)Cush9QcOp*1?ri@b2J&%NvdqGBUnST-Tf0mjfWKZh!^{a7t7By z-!nCu&^uHJy zO!H`}b*N#E?z$fq3^+^)s^E{_Oq;cc@w*^(1!cFt zW0*D=gx3$4h1Z`pgVp&5Cg^oG`o9gb^f5NyKvX>w0N(8fg%t`_2qPcq3k(1yZzA}v zRmGZ{ePOb85TY|zJzya#swRN+WJZEnE-ItFLKS*en5daDAnc(qQ^C~%IaW!+S#l~> zk)fKqJD*on%gAb5GGrFeETCK^t4WH`C?jQLi_JbdVshDcOymZmt9}|bphOAcJhw-J zxC?UaWGwvi3F68JjIXoVrpv1nvHoGIr1N=6MZXjw7d3#>yNT4DGJ%gWPrjvquH*xo zQ|aWPNGZd~=7q!36GzSdqwpydU9<~@B}*4At*p7)a;4?>TJH%Kd&9&N;~kUr5QMoW znB5JNCTIit-tnt1UU@N5-5#rM|FEb%SzI>##MBeBIANNu9e=m$de_ZO@wVM}mMxzx znpqVuUU}D!oOewq>>opg=;W(=D)($Q{J7dfZkrQs))@m#3YQLX#?1VBoiYBLGv>Mf z&S#7T=N!aqKv~CWqpR)3+G~=}pc0%x&*KmBPU<=I+lxPk!r=k=FnY*I^s9CZ@#{69 zO+Pw=7*5ZQafov$^s4Oh{m~ip-{&$&<_xkgcm_$ojV(2E2Bl0`lL~cE=OB_UBYDcg z2|4qqgZbG9kF0PhJMd{biv9qld@)Cn)xJ@46j|IGmvR)<*5lG{$;KHAuFB6If9tt7 zpSu=_)@+EEZ@6nh_+2~7yK6!f>H)RaK?fAMZPjp7hTfDl#4IAEjKP7F`+`3hhDMG- zk~#^coa~jW80gVrH#YPi+oUkJ0({JeDZ7*0VO6pO@+1aTo+K|=5>`v?r!sS%j7a2I z-e}%N0XUR;SCS-CM3YL?w2j&>8AbD$ZPa{eMOejB^ze~>RWvhG-KgztoXYf!*+t8k zIk-Wi^-RO_R@x3BPV>Xc2_nsy9qDw0J>TY)k(jAf>INQHEC*Fol!O{GK0QRTa7&VkLRCZO13@kCobP!$3_23F<@{U)m8TLp5rGO*t5UFdQkNV? z0`)F6RSp>gwy6@_2cw9`_w7Z0U>AxhwN{H5z->#&P7L{QNfKxfl*;&gPog&I0xmME z9f3|+Qc(?HO{uCcs1|VnDygBjT9S)LW>U|29=)Y<8L2}<0fDxij5=)XaAXwcEMZ8v zid(fQyx($dL&M?oxQMH?JX3y_#GR`BM=b<$>#{x{>1*n1N9{-U`=Em5sV9#gI@#*mdyKz62icLK3n5&!RV#1FI#qQ>IHT7nFbJ^} zZH3BNtQLkT4keUf>M_j+g;&K^5~Ea|IG#RHnEBBAGiB3wLU%B4CA}=Rbv|m9+Ex0*>PyOa;dIId38f(seeV3sG92!0+x(fq z5wR)FfU-qZlcqF-&9u27qk=PKrad6#Al+h6U5xwJDQ9qaU_gFP<$>M|6QfefI220d z(Z>!2&Zn%bLa3t<1E@H_%1`Bmhq(G<3w?n=%6_@0nPcG zQBA_e((jPdLJo16Sqr5r@TH7BgDD%0Q8%iCzNLZg(<#FMgr4zhVe2Yo2!@EXs%&)p zm-Do#Ig~l#V#E{qjijwZe+{4p^pb-H0h&Sm-z&NWmB|}6-z>P<@o$e#?D=`YvP3~+ ztf29lAzsijz9;GSq=nUX-{e}T5-ndo^U`d~H8Ebcew^+KMLo-t%T`RrQ#qRr?kD?>P}xQh86vcl%IwarsQa)B$29prGWkY~TZV!^*=xWkvZ4I+{u-gY_MTwOw0id&PbjV4^E-#r7DWBO z_gfZ`>a$N?P|N)+^bBUhAMI&6Xfge1&5HeX#y=BwZ@|m{wsFOQV)LJu8R7r)x>W}X zEI-L7|4+OI@)wxMU%cv|G4CfkDg37f1N=WVQut3TmV^0uJw&Kd9u?64U4&=>eLbd# z0=$9rqHmDWN@}?1>NUh0wuA0vh;=cq0S@(3~*(VhsIlUwINSO{2p1Q|Sn449RMheHM&6ypHfgCQ3k z*gypi_`-)EFZk39M9jbq&6bq0ZzyFu&LG1>PfGt5J6Vb}rm;(ZDNE15P$&Q{M+d?e zKBApaB7463^f0M9Nx8V-FqvkB0zKLzm2;Vk^1p?U%xO%!&|lDLydS|hjrSM?oQ>zE z&V6Ta+zI)7Cgt_QY{Rvd8>er!{dCza+cU|s6^XJ{v9eY1vbJ$o(pHqPRm5x+w`|Lj zo@G(nGA;@xQ!G%b#{9s3gfcmP7M$%!%QRB#*e`P*J&jn<&t8e`T!%Wt=@a^KOy{>V zw|p`^plN|hrOBkzqt_@{@+E6nmAq$~RX{U0o^u3p1NtVoRz7FuE8$cGG>MQ*iJ*ZK`gcfT!U5_r3cUa)WkQ3Q1^bk- z^SD$+;qw&4x!5Rm^NQqa>)N1uv=)d&1$mA_m>j;m^IJP*ef6ZBaH8W5#3o^_AX?NC zb+y1HF-p8M%fkt0Rm=$(@B8cD+WzMDMBRp1-G;bxW7N6*c4680VW<<$O|d>!w?6LN z5Or?5U06E)1nHXF&ac>+!WV;C`V3Wh%0K&}7m+boqrXrvsa0_cHmP+M+L&?FAguza zPEQG(RwbQ|@P!@&OquXwreF(Nr9++^{U?e~1Q{+7T@ryNALW*5T4z+_b-i|`(4{Puk z2?%DE8@~zb&RKHOpP;{d!4V~p>*b|YC;dBe*dnAYk8Eub!z#phA5i7XPOc9q#>p2Q zGZmi73k~_fS`Wc~&yYWqBW^|LZa2c^V@iIrfT$n(mnccQG%KsGHeG4D+J2>d;_w~l zXM(+ysAmOG#ImYMXA)fXRaaJxAO0I>ezLS;`pndsnRD-~e|P)!?TJ;}Vym{rOSexP zxKmo0C~b_DHePf7;qmuheDB4?#{IF4`{Sht#t$T&h2uNf;{ADH=^G)!38%j?^^HVX zORTIVQPv(SYrm0q)9{`nUbuC9Z?Xt$m@nUaC<-{+?H@RUPKVD9f{}q;W-yS&bSKP# zF)GI4J`8`+up4mI6^gvoBN8jqm{IGkcNns(wOO+sD@*5q!x5~mQoVWHuX8|w9YII1 zdN`p^@}nNXI&ouWBa|7xEvLibFZNW6Y+hHBU6WCcL9wCkFBUZ;0J8Y8^F`fS9yiGct6t7zJhe0{lD7;}v|HKXZL#u($cl}dg{zs%t_rI{J_j=EdAAu6$2iO>9w+D098QjfZs z>m*mH7b1Kh>c9SXX3UeGdagl0=;k37j(YQO^m9YS;K z;4B2YVxm#HoTt~AG4v5e9Mfb6J4wXm)tJus%(iX7KX_X7?}#)oA{p^GL7)zFi+#{J z8gAb{lZZY!{y;T{-ZJMZr~$L}A1n<}_O&N!80<%K8&$e&QePbtDi+h$)7wC+D;>F5=A z;c0fWYspY4C=tyOxDLBXgX2c(?YkHdrSGuxoOcc$x&)mJ3lb$gIU_79V#o=trBVJS z<^s?nhfrLXC~AlmHOziJUbJc4_OYkzcHx?+YfaKw@WxjXB~7uCrVpJ>w+ox2uI4*V z4}%-C4yRnWQ8}AFE~xlUCDaa9k}ihIH!J^DRlKr&PRO?(Fv!gJ@~T96ORT)*+J<+x zUf&uoe{B5F?Wz^C8)9uw->N!xPjI;p7$$81L}#{sSk$1LZ#!eFb|#CfIQ@|Qq%Ecn zL*ZS))LNLf2_@C2l8(n@aplaxt505e^4iHn+xA%7_L~=fIQoOpIg@c&;bb19mk$}9 zHvDzP(>I4_RwgRjVwG*z#COkKKlf4PmT1M(lU6vltLtX>|K3-Ll!&J6L+h>{zH<1Q z`JJ8{E8p&qS3N$t54B7mojN+(eCH!}OI>fBGCjwt z6aY$+pDexn<_90K{VJfW{Fr>dP0s&L&Ngxgrc=N+bzMp+ii*L>1=aGi_bOQ`B|C60EhO4M z0Ju4X!qVxgsjBbPBnld01r6iYB<`{mtc(?`oDJT92F~qR3Z6oOB|N1u8Ylhx};3r4$xgI`v{YX?Pr1gewI6N6+P zHJmYw8G}c#f1`Q1j|#xh?8Xr>-=0P3<4lTi~;DH$$_niuFomtUo zc4-iDhO0CDfK4p|Yxu;=%qf~qDf>QVV*+Zkq(;0eV!F2}E{~rQA$DN0*_X1(GfSK& zuj>Y*xvzIbKAvQSuvuZ9G!6d%1z!^G+F+tHSynxHcH+Q1gubveQCJr%#NA4=Hur}` z?^nN9omjgswsv22PvQ92xJ8iC3SbDO)%OIey<~hp*gtOK(_=nqWzWzKy=>;?t7BKjJ}heexVRF?esNnm4|(tB3!bK^t&y&X6r(tU zyX0eUaoU96&zSV?k196qDL4MO+`gyY^5Z%K+?4YKDT;x)WDH#vz(N}l+YY}1j~1&K zg%OdAdL6uxp*S-VwFT-OjI=sh@Sm^IKu<&*n_nDr2_FTQ(StDIDk9Ro3^T{;)p% zE<6hXoS&0e>cY3B<1*)A1W}!Eq|Ql6ucL-un4#fI-m)Onh?`b4W+^)qaljTjWEQdv zH5AIk00!$p&=<2fQpTX)`4NLOmYYx*&JQeHPLnHNcGY&pb~XP>{=^~TpE|EN#}AR+ z2IrI$T-NR@-Pih}p3PC)WF?TkzD`8CQj%;Lzs1)q| z0yUS8es211bYfH`ipw)nX$NnFnKeQ&%t(7Kti#dR@u^06lpEr9@ZiO6S~_y2>iZ}v zog)|qmD=Rn*o&-9`hSou>*F|Bv1}sC7tu2T-#|suU1}Yn>{+@vsXjKWo$z2-7f}Ay zjadF*e&x0i5tnQLWCYS5%AXO%$Rnndy1OV^i#|Y_R30g~qa%d&ogDKs4AzI>hO(A5 zu)ZerRr+Ri$c^DEshWOq>P2Qx8F2j8iLFs*O|sHA-tk+9i9Wl0{(M5dtWSv>W1Z5^ z5h&5u%(X(8Dkf8*-9d0(lty*;iIED9`{_h|lN!gr?H!MoRe#;Ezu9u)MCbnf7?f40 zq?4~l{=&B7=4T{2;U&ToB|5t$qI7b=F9;v(W0TOxV-#qncTab>KL{xtwisdYE@dWp zENL?(pv{3}VSC62kOcY3#JB|X9*Z1?LI;Mzl*z$RFt!j<#zURbE{bCfEhwh^2*)m$g5HNxEweNoQ`d1Te zJ7R4+05{#WS6Noxfzk=vlx^BQ<(@T>f(tR`ZAn`YgF11ss%8p^LrbinC7nlMKw~TB zDBrzp9!v4P6;?|_+M8$DW0+n4*5)@i6C36Rcr2UJ0tMb&|HI8c*!;uoKiEFU-tQF} zEz9rOj20g`RrhQL6l676YRRc1XT?3M5kAD$qQQ^vxNEAk6w~FagHs0?I=eSk+?Zy8 zYd5mrJ9gLQ@`>`x)f3fo79@raN6J>NmyH4`8-6v}W{P8W5SYhulw*qS}bvZ}kzHp@Z7o&18!XD7~H9-J7Q>4B-? zw1q-3fG~Q1flYWDW8TJV&>sTulf~F{V(cu&0aYVuRwx9G9PZ0i6IBU&P0U_1=VtM} zhWSlsx0KIs0Rr_KKoPq2Qv|P`eA%_nVvJVKxhdjob_?#>>n)A*t4E-I^%S97Jw-SK zNB*3Xd`ArB7EnrZKRg9)di?!?_XZN14#zg(fI!F{yPH1Ws`R$3vAJtsXDONAG6d?k zj3RVfMiFS4`*OuZ#pUG_%jcXFaz1w)TJ>r}pnh!>p<5e8XlsK}EIX>C;8U3@cgNQ3 zPOe#>nT&VyYAg-3nsX+c1;P3)pa`4bD4bk5V`8wK`-*!uL~3UejoV_4+oI*$Vujmd z_8oI}O4{+j%9mR<>Q#m6obIzzr?ogQt%F<23F}siU zw6gRL!PlBtzAm~ir4~gBG z%9dk(CF~6`d&8WaV#|dcdkl9?MV2Pw;XyQIdT44WQQ8_SZB3M}kCm>!A;wC#q$vSg zdH9`cH&~7v?pjTjqS^YifS()K^6`^)*es9VvFDG6E?=CuNEF7IaLiLTd*a&qcQ;?( zOq{`+8$dXXdUnR_yXGuNeXrPnqrqXYxamNEW1_PFqMoxo_XYegc#NIAJve#QM*(c- z>Jju!fWrZy%ODC}MiDfpF4MTsi#tNE*}BZ*!bL+D?6@?WQtoFt92*b`G=pf6sE--0 zL6P4z0N+(>n}F@Y-vWsEwj4qRLjgl8jQ|9F9Uv*aq8dwR_&kW}3=new%vMn~*gGZ= z4Zs|w^j+eR1*MCWW;LxH7R!Txsk^5MG$pw5Qy}m*_e5UIR({J?d8e>w-27X4Y-m8z z$$>-vKcr{1r`&RlJ%ND%X8xQ=xQsQji`Q+(yF|_#R1rb3pS~u5VmN8Nbm$Ya2}dNp zYBB!=lq~BL5Tr#9zI^s4V1sYK;F|*G#S(5sV=u0BA$>kCRO{*y+eGsMW$TdLM9UJ= zS_2L-59ta1%X#!<6nY)3x3<^ZUG4>HQN~GA+W)A%73~o~zH*-(=yU$!`-}zotMGmE zM0>AIbiQWk@`$d*e`k^MnML<&<}NShhXb=UAN~Tiy%b`5@uW%*4Gs>6iF22=n6oHq zgy>+hEGV6t>7#%YZwEII_$T$^v!l z6wFegKoBm?Db#H9q6#5!sogu5t9i4=UEM^l(%yCm`LZ|BBse9FQ;i@*5@s1AXhIdk{mPebg*DGq+5&y4W*fQWF+T2nexo!FlLnmk0Z}aNX3I zviB*oZ9nLz*Qo()yd&^kmyNt_qtKkTu~8&M9HXd?BV~%zFEEZ-M&$lA4JOfnFqn7q zLFT_*xNPQdys#E zp&34gJYy&(Qnx@)i8v(5`rO`oct`7I4_tc|Ivbyfd-g_cds#=bD0Z~c>2Izaj+TC| z{on*SRG_KX4eTo_^FI6AWmDZGqP0XC7U)He!Gk}cj?l^mpWP*R%4a|-uaDa5`KkUY zBC>Xcb*CzT2H3C2$8Uan}Y*7T zl4GJUT3cLzFGC@#r3eo>UN~BatdIi9`F(25MR)*_-8WkRsFw<>+TU%v-j-hFe$Mu^bhqe z#4wI5zFY325rJNy_x&_pIZq~++*9ccQq1odag-wJXJ%|T5MllUb!jD{*qNK~tcZCa zc^CIIG4sEdBNGvB5;*Rui`we=r&IyjELcnZlyn9!qKmjAaLF(NKwM9DVey}t?zCoP zHu$nIGiz4-%x9mP!E&#lUAc3gL8HaW+}DDie@g>H5+9iVE|{rev^3Sk)c7`tBLYZC zcziJr2{u-|S&^t+7pq-&WB*N9PtJg&+V8CH9mL0CLxkVlM{hWk9YOiRFCtSe+m$gC zBQB*c=K>?4|BOCJv^(($`K2_aN3SU&0wW5wuN~L)2SeB%1Sz1j1q|fQ=8|04j`8@+ zXeQ^!GANJj8+#}pLqtC#-#&6^q4Muc9Kj54lmmVAVL6MH+LFsmWRg7O`~`jGQq=ew ziSF$;JYh&SuDa`VTdMEm7p5)v#s22@rt`>WgORYAo~viCoV_}DWiZ1=jMi+4dA8mZ zW1c-}J0%2oRjhLP%rS5M9F>@^F<1_07-cS)L#p|d-3|kcgL=e-3-T8@13Ktw2go?4Z9@`$P-Z8e8>}lxd0hU$(SB0 zzl(F6oD_4f6fD9y;tullP6I`sS$z~j2Lr*fXulrO007JwF!zG)O9Us7s4M`+77y-N z0(PV>R_jtddIZ76m&*Y+07zB_AT6y$i*66f&tss`lFAotGT@fKq>*x<_d3@y^uQs} zf;y}qx`AM}q&f%`t5-jP>e0Qt;a);oYLAT(@QN7$uLJ}LP8SeJ%)zL?vIzXRxKtCw$o;y^d+1>~3hG^7K0uU*=c&beLZ1=cJ2} zidd#pfUxK9QX8bnV9x7w0GC%Tbd#Ph28l=N=gnW8R2EFC(7!?9Y%0Uz z=YzmoW;%3y8)S5uDVW%@^`s;RT67X+ZLzYpc-iXlgSS1UlNV=;Z+RMk6D_a1dgjWR zN!uGv0D{oabH`hr@UDn?S0ud6F)z%3zuSJj{g(IfqIjc#67dnw1UYegC+XlppoHHxCLPCa!z4&AQY*}P1NbvP;l0|q&Bd{nX@EaxV@*+=_6ISqoR#%pkeW$9C-=SrI(p-+*7Z$ zj(PSYTO;pvm}(q`qmNp?gP28h9jK*l*Y&50&Pkv}Dq&9Ej%wMXF(=RLE=c*9etL(! zVLJvF!^;+*Rbs=-sLvU6^b$(sG^SVJ%SB8nab$V!$mVbsT4`{fKuQHi%^BRPm`&Em z#TBd5b)s9gowuF&mBKA5ms=sV;ijro?fGbhpler}k5K|g6< zsRe4uRMEAx3Gdl5Fe=g(^{k57TjwlDkzQl4Xe31m8(Vjy=>78d%IQK3A!F;(Rt9K(H3=V6@eTwg!51WN`wfUZX6FK#qWO%(+a}(U8fJ*x1jT#B`dVLEno6+&fp8iV3 zm-+3l&iECdeu?Z>lg^f-hYBimF^rqKg^kd?rpQjePgS^)I}u=r?WN&933^mDiE?(L z6U|<(-H|QQy=bxqPhoo;qe(MjD?3#)a3W){vAu(st|@beB80D?FfPppY z_?E5pPGQA#%Tx=Ix$l%#PY0)hgu&b?UPkwfVXnfy*8o*GMU~Urr?w}G>S0@O_C&m> zWxV5WJw>-0w%t4tZ`d=LM?#@9;)mY4w4HL`{u?CwaOomZ(ikggOuC9LADuV~NK>X8 zZi>}zLc2;yzJcGEnlDtwNLGySrMp>f!&pk>Y^J^!Q{O?-GM8B#vw5Eg__<3C-#;i> z4^pTPEpmG1z78jgp5v88$?POb26;YJ01s3h6Idr!(F`|Fw7@0ime6Gf1w`m_h&H%R zu%iU9qa1MEpgss)`JxN12Vy(|#CV7WQ@~g-g^UVN1pK0aT`$ODxv2Zl5j2nC`zBEs zHbAQeHJUvW(0WL+JAfgL&~GSr2>S@#v{9jG#(q&ct3p3!&D5p2m&zaXp9^Go*NR4a zHmw`B!-k?9|KLhc|oC{wJ zb&GhlEhSdi3)D*5iZ2PIenxxQiz-$gq~eX3g=e8gyux&<>x1%oKs=!YxHGJ7`C|qs zRUsrn2Se9~!<3^5lR#;y`q{1Qz>^?m&F}j#P$NTeQiMkHdU5MzNE*pv68$|&i&SdI zD*HkuiDiZMA`wFd*r+l7Nz_wEQctZ@t%<_NVug>5gO^4McuH4}ANaVeW+r@fy2}NKmh_V`hF*QtUejt`aE3r{rT*#0x0wujq@U_R zYz5JHu7hM5NGSoo=U~=sI+xHYPJqoh)l;@^nL!ZA214Y}F&Z9XyJB|WGVl&i1d7T@ zW5l+pZPB7tQP(QC&<{Boo*tbVmDQpawSw$dM9P8YPY-n|379ks4b+2nT{R9Z%YCFO z3|wv?-gT*c8eLhCGB8NLuOZ{;lF9XOQgvDyX!xbCp?(5}avo_d!ga7wJ^Yhc5CCda z+;hwbpoTP6Xcw7WzEV4MMUrL%ofJ&9Pfly-wZCM3APluEz#!0LttyZJZQZDQesQ{b z>_)Rjos+-Z9H?SIGdm~QZQn~Mkx$v(bapZ@jP6=9B!*uDX4vB#!epXk$_ni$xYRC@ znq79<=0G1_c8x!rQ5=%9HIi}xP3&tl`Tq>%WAbA!7?~J}dPp!A?z|ILUK~))aA@LC z!nHi+T0U$3$hGogchUDuGaXltTse}cS`({U6E9zTWA}&U8&Lw-4Ig?|kdnsME3Jvj zU9rktlX<`$CwJbiX`ejsaV5^`w_4w9jaROo%DcmpA%RlI%9v{<=yQ{202Yp38RhrU zeqjRU3V{@G-B?3>F&CX`F8Nf`P7(66^Zqn3G5aqQu@fX zoJg9SvfQKH%~2S)2KgKCaErtGQ(WDQkS_Pp1ELk4Ac#y@;7oZV^e-_jN5EOISPi3~ z$I12&9Z=v0#rY!w+2Bl=90_Lk@#Eg05pl!Rsc5d#8dzQCrIcpdh;8_|X1o_adaHM-Qb^zU5c&oyV|RXq8+#tiK-hAf#6|EivlhT>9t!ucH{oz>a;dtxOn5}ud zd~(aoNYYspbuODVzvX(|& zo99gMrO9{CYO!p)mv6M}yk|36*4=ZMERE!By@xB$kKMCbENk!O7a^?0WNE(VFvI7P zHDs*%FE*!zbGmtb`%`8zFTie~rCdZU>z0W{+@KKcDJeJXk3({yFW56M40SX=MEg>X zJ~8NrKm+U=rMx^|1i_tak?PSB_i3iGGyL1=KUSOtb{0YS1jq>8euP6prLVj{GMj@) zmHWWbog4}qj?RoIlpe)RACQJNJ=;5GwH}wI40R=CYSojK8FitpPAD6;zhiluPy{?< zZs`1T>*0tjdB$v@V$*XT$F_UZ5aU93{Lt3c=uGb4v9=N?~%&P`P|6iSu zZCmg44%xPzwKHPd26eD7ORxj?$6C;@6A-q5&JF^2&frJtBfp(%yrG>TWe*RX3k3Tj zuw`LCp?YmJf5?(l%6bemWki~Zwk(l^ADcB$CM!~T3@YA6p;Vg`pfG4D5qNDhzgYFG zUcT-=Kp1SZ4xMAVcNWwd>*(&4h6b5&ElEOwe@yG?r^pNLa)V&=M)ROcp&F`Ton3p*t@)3oX4^JHat)rimpxnDP zf~^?3zcSW1oSsXEKfUi3yfVG8Ip%35fW`Oo!sQU5fpsUl@3T+Jke4;=vk;Bto4fP& zY&ZR=*}JFI_~X+0JzFe4-eN+?kGEU)TJy3NA7OfI@zFqMT~6S924+hMlR6KYn(c4P z%N8iQ!Jm(Hv1hn%K=k>UnF`;~>3%G4W`R%%qHpcv`@C#eQC5rJLYs=#)j?3D#?*bT)Mld43ugo9jO63?l$cpda6~2Ey^LiheVdB%&}Q0!5hH_5 zr%owtpr$Lw1zMpRNsvzvvLH#;5n*jnF`tMs5A~{VEK87e@6;)km)nI>@ktcZk0?Qu zo5IZHz~uM{8ME{UNBD42`Ov*RKti{ytT1d^f&m;JmV(6p4Go{>gNKJWoqc-c)Dr3jL0dG*YjZw_#L!En%Jkzo7)G0O+nG>15x({#+NDpB)p6oMd z4Lzd0u#HHt9d&|MTTWr@gGgkUhQ%Hq35NX__fa1)Q~^artTLclgDp$!M#vAnB_n*+ zlf9gfvdZK&$3)eX=`o0N#&;T10Ru-3S0-U2UpA*sX&chijHV7^O$>=jJ2PO6+)8!h zIn>d)doRH^v{IEFgxiOuDAqo1-BDUCTX$4G5g8X%*0-{LD35R5nldC zLbO!WR^ddBTORa=LDs#fd|?Ja9iC~3J@u2%TL-o zNn2r(ASQ3pR>C}ZEuSlKx~yrzX|Xc&UivO_W&u5J1eowQ_m^<8fu0ORt-#~3#14#P zRDdXk4AqmIxoCVxy<9Qoc&MFum1Im93~&@7>_pY<0m+(SIb^gl7trbfCPe>~wiv)2 z$%WH{vJGsq<=$-eR{|^};2QHg1E0{!Jv|>*Q}(zVtJrL_SiD{A#n{Ad`SpX{f;a)8 zTU6P`9}cF;(8wbYE$|3LV_@9m9)TLx%^#FQ6&20YHI~POMu|#CU@5g058A0)4j8xZBi4osMAWyO zN0+p1(`)D3_=xnt!k#_UjM50K&UqAqHKoz7Z&bHzqW!}5ot~~hn;wG8+OelmiycPRKWxK0cYGqP-wnGGp`%CE$cw1^CB&XVimCT9pnq2n-u?)a^(4FHyW0jW-0yW#oJxkyJPt zhLg=+8V*P!?I@4PC`$1eDqi~-My6>#NMVEt zYX?IT^dL^eA)10h))5&tj3K0a>PRVl)RK_0difxV3l$ag{W57;>hYqIQWt47S})_5 zMsZLqTR%&-&lU0^qtk;j$L1p=QTs;v0s|t*FQDz|eUv3pF1T7{=^S>n6OnvQ^-&g4 zvxgmkDJ$001(0l;uzI;d&h6woOU`5DM95Kv##4^r^GvByI6NSIo1)gCbZIa7h=B!5 zTQ5@p<82Z1g|kf!krb#X8PW+!@1!gdB56Wz7a7|%!Xd5=O>O1j2ds@9uvmZ6H&F5` zbiUH{Be1r12zK|S-AT81PVic)#|=rRYy5HWzLOP|SIe)IPg(D9&us0LWn8`0q%~Pu zHhp&L?95A3=MtsOvC?LU?|Z9eo}GQ}X5}q!$N27KN$K>-sgw5vo4tA*wgF3~4@@1H zK0I}J*7BC)O-H%3vQ5U} zx5nNayAdEg{2%Q5$&nu&i5`14Ui2JpB0*T}$%!Xt_FX-6<AnNgPUQyC!znnjjf6G>m0l0LK zQHPojaGNT#^a=imoPR~mU%^RPX_pJ0;nqSxgAyd70C5W=bPI;7@UfGR=vNYfJt;S> zE|uKP)X%0I6m|x?@)}7{e!(uX-7eBIQ!wNizm3izOs` z&8Z?D$!s?(2BK4?xq;l)r(B7o7L(!FdMF2*{@fS}jJQ!ic!1YVX9#2BvfAm5RmhB@ za2qMaU53#ph2)=db#xxj*odQpnwfa#=G__d*GFM>(;8$e8vd(j(3ko-#F;+^X3s3Zr{r+_=ksqw)lL-9?pqCp zmE%2}_uLfAZ@Moa;J(dZ*f4&8{2R!>Y5XzrZz6x|BnxjP|9Yr)f?B6U&E{CmW=gT1 zh0Y#->%});Osw1)Te(vW-2(mk(5;{F5X+fDw@})4Tor~HlZ0I=OgP(O&bIpkh1`#nTMfmNdlhRf_XWJB3!xtBo7JyWCk*8=L-~DU zi=lW1%BAoFF&JY-ngTxQGBg-g+!f&5KW2c;UhU+W`vU!@p;j|Hf4RXid0|e#kDMRA z`otkyEwSb;kQPjGTN0JboJ8xI zWZT-iC5>!g(kA?B2V^e^*4D>d4QUHvFe;EG`0B)0xe}Um9)&@fRY}@TK8H|Lns$;8 zdUXpYJ0|+mZhFfXip$d;@_B`#vUCCY3T2&KTt&z1325j@7g0npG}X+Uob9>tVyv_! zP3?lBSF2;LjC>~zi|oz;gTaaqoi>qYfeumBydJSY-X-ScePZ5Br`Rj+74!0LF|TgF zSWHgGSQzga^PV(5pldZ`_(T3uPZ|I5KodI_$I-JD1r}A4_a?aqTaG3%9bdvEI(k103}i+C=WnMq(QIj z_-?^nUqqhwj@0dLL}fQI6+0!la#gFVO4a$E>)c+YFao`Rv8qIy-K1`ls}wEia*nnt z$@lkQWs28`2HyPw9sZrwqf!Q^sM_Dbuj|lzG^4%Ayg|7o4)P z^tSBVo_#yAZ)f)H%DxM;@1pFxIQw>I-zC|%=af~;?dO8}ce$V;WF7QN4OiZSYZSla z`%owPPL;FVM&vdoh#&iUdp4q<=noYHmF6u9wkol4ZbM(l=oLpN#(MV)nI*6DN*tTUHz6|VMre= zLLF*(RK_RNPU9m&J6Ig@W_|7zTKS4pm-1dxLdn<8=RuCwl+q;M;Oml4c`vD{6!Rjt z?PV+U`LvQ>@@4y2zCw;CL8&5C7px2!kvk}rsJsXJ6~E-8x!dmvR=sOD)j6j1RgV|! z=|1i~H4+X*{J*9|-9BSR*A*Vim=8uneDCR@P)2`vAQH{!j)$TdL(k}W3hC>OMEj&N z>8;6rJaEA?!-Y7YGmcQ%DIKcOW$Y)y=fWcw!$-=jSpXGexZ9epE1r^h<_&h|#5 zV;vVl;R_u@1E)LAk44XpgxCAmuIq>lL__W8d;8Ayo(V-dg5gNVIC|VaFck8iAL9vG zGp7ljhyl*KTt;8U7fRt@tEYv#ySt9;?Ft8vM$d-$J-yN1@wzT=cqAC|p6!izd%Y~) z+ZzsgM<|6ih$P*-l`13PiPaX7&n-DE@W>5o6;n`TpAL@nke~h@=z)o5xTqA)4Q4Tu z901QCcAxxb5UVf$iD^H~4()_4#tmBX<2f#-kyGen`pcRzE~X!(-sC+WX1^y4F~d`| z&6ZajKF>7+M(bj_rzp*_LTT104LW;*a=+fuJXMQVs$>0AwRole=eAfLt9LL8W1LUd zJ-(Kw=@kh@z0tArA@4v0GcoGz8yP;|8y&#n_Ff!_o|Pvh&C>DB;slj5u9$}pof#eK z6(=y8;66)6hsBlAQ#l!PH{3%5;b5k)Z>TpC8R#DiNQGw1XF}0{m`sYy7+A#_)3Lq# zPaN(##-=}Eqv)kDiUe-RnC1L-$rm_>8u(HSS;j);vG0fx-(L3cw30HFfxfe$zH@;- zhDfmX0&MLC2snHLQV^LF`7=0|xx4PtnFG@YQtnlPdsWK4QE+d(ZBDwoE+0u-9apwZ zZkyF7t<{(Jq)pb#v3q7m+U1_HPut&jUOsrwT=?DNGbg7{rb?QGlBQHin^4kr$J~~- zm4E;EwUbv*rmFoywLew8L8#tv$F|{Dj@pFxc+%06F!wCW>laG=cg+4}c{_#b&O5eF z!dWwJ7|z-5e*k^MX6ep>WVf4fv8U(oBU9tiV+Z#i?0#fv+`qSb@3F3)y^qX|2fKG4KCx%-l0ASX)O!3q zSO@12fKL0ss{r(bCMxd+3NiQlni-9Td#_NBs{gI-qUMIC+oxf;8hBpoUz5=UBh(zP zh}R!kww(!uLzm9;+sEt01?>-DH4a1r0spq4k-pxc$acS+=C_eELMZ!hxGyhrcYG&5 zsat5-a(iv6JT{M)jNtF@W$`mpDgUB2Vr0Wd^Gdj>>{2ElpuO){DgQsE16p0kd`O&CM zB{9mxXUpg#p`m^T2%e@3LwGz*625~RmD0oxX#x>if{YHzI^rgbvz9nC z&6p3Dxy1_3T^iTFXHKu}{Bh;Sm8rEyg|$bMYhMt|{6-dfjbEto z$Mp;5%5+&dve?r#4dSQOov?b-6%Fx2X{#q;tx4B<<9iltRq0hN@jai}>hIO!&Axr~ zu1}X&r^?%e@-~#>NLR5^%G1Rju^sEF(6V$hVi(NipPOxIYvpWIur_3x_EGkR&z)5u z$5O7XsjdsOE-;X81AL68pcg;5(UYI>`!1 zS2Ut3XS%$eHK4kMYN|+AHM1JiX4@6>q&a175X=n=jXORw?_5r7(W;;o`hXY|GoU?y zzu?P%)JQA|KTT+2nn4-6D>;+(F>fZ(5_7)Ed6o&Wqp01&B&%G=$byk#jhsBKm_#%YdoBG8QD zYC4V_3}^#EpoxM0fe_CVR?1jHm-<5IMeK5XEFA5V+;mjKckpo5H(ecU5~ zb7}bdl#v!YfQs{idD=Wh>P2ngO_$?Hp4Dp zzXcCjgZZL5$KH0RhfipyG~w-tqq&;*TsB4t`?3chQ>8uB_y9reM>=W0V)GYm%YUT# z8);jiT$x5tauRK&)=r7vial1Qb;A2rC|9OqlyarEu(ryl{^jvlYb!yaSV=AUlnoSA z4X1W;n9#URoPWZYd-IJ+m80Cgiq+hXP?xQCA}c3 zCGUBPUVv+dT2-Vi=Qe`4c$%3iQ{kA6y37BRM@NOf&PGQie8Ldk^0h{1XWr1@^C?Eh zuw=w{eZ?~MF$3BvZ;?tagsR|tgnE@Uk`JM3I3ru?1kOQf^0>&G;Fc=bZKqV4@>Y|H zF<$O#(IZl-oD#aFFy%e?yyBO9YNe`$#f;L(!mxUe(hA8p_zlUYym8jnp&vv8;34eX@FeU;hlISR5v!Z+QY!OgP{yuG zrrf>lC2mUl-ua8%CEZKh#ig~Y?Pn?{U(~MEaPM%@4N?sVX=UwE!9^2Y9Y zZ4Xd~((|lUTcma>Z=NvuHsxFLP3Xd9h-s3(l{argc%$+y`IvO&Y5Nz->0d51{hHia zp*AjRbcRuV*ZUqM`ah5wYoBer>3M#nFBFM*iC0NSDQ`bN zGVIM`VEVgzzyr5k>;)TQ2n^1F@R{*i$o;&;S@4cVLP0OF7f3QQ8U_Ob`H=xg zV8Um-SssTM%p>oG-l5TuuT5ki>_%=;6iOTy{x}?VG`e^e!qW(Zr=g6gH^TU*{C-M7 zxC$KqFb+EG(8QmjP+}nPT@+=*VKg#&Iyf@iI}qjxi}AF5iF}Ve68qHE?BV^ zf?psSx&%2hYg)#*t2c;-GA;=?J0z`PV98kaO6N2F02M;Bh4DN@UW!GAOvb>xV7rV( zGG+jUagOjA$vMtZ@;i4-7}!hMl=FFY@vyk*`?HsC5!N8ZQb>oRxG z<&GQfS)EsQOzucno6^OlGdrete0$G5XR&zvPB>fcdKwb0hMe>Hd~oV;(!Cys^y0Eq zag$Kov{2lfu3Pm%+4Zti-CCh;?X6X}-Kouoh0TYPbw{T3zvsADR&~vM)qKr%)%HEd zlp#0cJ$Hq4Qguu_W=9uXtEd^*ylck%c^ovH$=DM6 ztZK@zSb%y`)@s38ov`}So{E&GS@1MZ?q6ECSZ+o2HUCwAs$!i`vF_G0Ki>NB)?~$w zDbu2jbCu3ErYc&6iq<>M);#!hCp||KuA}#?j`-H4rDmI4@HLxU#F|_sSgR7&=5$H9 zD07_Lx3us@tf9@I z?!D8p7YsH}#rGSowO(z#)^W8XRnaa~w5KZ83KeT_txJ|{i0@0+uDbTt)wh;10G&ng zH}83>X2R3qDcz!(vpM5im-0p0_9u%DBy0!niQKp=wn^Jm&+Pg+?PunOFCX@4!M2>) zIK7dk>U~aQZ_4(CCfw|3%C%}yhrr)FbW^%#r=Lv~w+Y2JD44T%5E!$|`4K)3Nv8nmaxBMxxX=c_4mls%R-Z{X70#_sOn5Gu$sn6^1Yzt{zK#DV;=6OCSQ+{ z`{~*)8#rSL-KOVv=n~sCa0P9b{aCT?PLYOOPnWMpr(4i!$Te>4c~dVvgrx!6H>}b5)-;7q?C!B34KsMVF(%~ zj6vgs34i9G35N%*8jDsTl1b#o*Tf3c)M_MB<`wjE0_74RgA$EFJv#`Up}N9B2oebj zc~4Fa)|i#|#;lL60WcJ6kW1HQd%}7VUJG@d$Jb(u+0YN$V{5^#CG{h(9zZH8v)>eQ zYr>urDid214&*jp(D3z9+B?~!CYB$R1%y&6C=LRC-{XEDk1q~XmSDkCmSv4u^2#zm z@Mn)XVwQdbG#+f-!)vU-K)$R$p7z_4_7#{+7n^GL1#niMkmz zM-o0BvaYpDzSb@ITA$HDd5_lhnQ+D^wlZV5I1r4U&FD`<6DgxTvlf5r@V7o=8px(F z4zMH<8drs4Vm^^F73{Th`lNkT zT>H7zo^J2B`O=M-K73iQwZwN%xu-77j?M)^#!T_w9#5B6OzZCzR;LOZgu;foJ;_2} zeAnmB!gOQP2M4bo{NW+NRX3%>hP46v#q7G@-IA`Xnc9;ss|5MQvX<1PN>&RctLOE} zlJ+U>=fx%IjUy+CA%@y)ZWlYH61L-SKp7{j8pq@T;Pdss8s{<0kCp zQzzc9h#NsaPK^uJx;$*Z>yyq;ni6dXlSR)ZY|o{w&P3@mg7ujtspf}o@43Ap(Y7mD zv^!zjea~4mwP$wq+=ltigtH@IR#7pc<;u`?1|r9A)vhG;fpkGe@{HpF#F76z4$(|# zzz>rJK5Al~K~))o8Yf;l;gR;mgmyy5cnn{=Jvc2tMtT52mD1Xx{GNg%D4(5`xOdP; zNDVb=ZHD&bSJvH>Hkl_$IG(S53vB?b$7f|i`{0>$5N4qD&UPZR`gN?6dIlh2V8UZ8~co8z_p8tCKMzFiBbkakGk z6O&~#jP$fl*e2`~jtS=qsVy>&Btq3SQ8-aFQ9R*}lI{w7h!Y)jjl&<)OZ@0P+?3|k zGe}>e<)Bkm60^nZQrv_m8&()AlEX^1++=Oc6LZEKa#$G)D~-8h#d27A%rLkb})a9x!Y zi`;5_Z>md$^PH9SRtyq-nD?mXkVZ06g!mnKS@0=g6%CUoAkEI@X&~*b7$oLi-t+hx zCn}b#dSU|RJ*rSPL7R@`!Fg%vQ@wR7v?9w@pQu9of#rn_9$Fq2tH3@~C5@5>^Z7jt zEt9gyJZ4BMD#5XSJT_F_-6b9}WH$e}r73Mu+l^(JmeODJ;XNkXkY}M17ik5&yvn!a zi_|`$&uTk>e3WK1pGv7rl2R|#p}cVn-h_6jw2$SeP*O@htxtA8-+6 zez~pvI&z<)tlX!1qUI@jSR8GaU>K{G@FDu=&Izwk!d2xyCF9R8ll%0IKtl{N1Bh2h zvLh+tdYNMAFf_|@Btr$#=~h;8hlGd^pEIY38h{`V;U zGMpuMzvxUw659t}I5>Mjp@=i3wT8t|+ zp-W;TsRP-H#XD+@&*0~O3t4~tfAO$OYS22oh5&w!9BNX8&VJ~ccvyD@pzkY|4|xm9 z9`BIO>k2y7?2y;`I<Tp=e)&JW-~ z%1ISKju|>X1W7HEQDzDTg5lmkA1=l9i4xl`NKA>TUF#`2G zOT>$bcqtK|O~kWAnt_ZF!gb&fND0KZc?k25g_!xQPZY|sz7US%-y|nS4o$6y7Y@z; z$2>bl%;kVSugK&SWwa%#lX4f$z84MR?1P93qQe@&S_4V#-J;qAM3SQPOj7Y()Eg~r zcfAcC6kIPzc{>Gf=dBYze);2fHw>l_j8Qh|`G1DE{VzC|0# zOc#~Tv`)7|B$}tC{p8$5mLnQs`K_K_ohoV&iW=tjesJjep=8mTr2^IkTkY2_+-tTp zQ==imOL_c)$3H)M^UWJ?COuoRxW0BvIa~2mP?X}{v94aircAUwmn=GzupJ@@AJg0| zDVuq9`c?7T<@|}8FW-1MS+W^&#;;$e(|zUKN}9V|~iMQ}FNnq&Vr{E0|m3`gmyS8?*g$ zp|pS9N>O(UJyWBz!MPW%o&_hku>Aq&w=~DQfXO^fbG`E=w@k^BP4Ru7m#oV9`bBBu z+>7%kZtY8!ZjB%Ke970nV$aOh>8*2xbDeWdv*YudlEt0Cgb;!rnm#mFH@9bQ!(74q zfu#GH_}*Vtd&TRj9YS>nB$hM)KEdOg@4UI`#->#3cA<4UuAbI7Ac*e<{sn+V#elCV z{r~*&(tmx4y@&?&rRkSaCCvh)sP4)%^b*|V7}At`wcuV&V|#S^DEn>{+>H=Sdy1yC zi*1~}@XCS716K}D9tOueGPmnzw$)#}ssRwceE6!CbCpBdidC7XaSy?&rR_Hli=Ghg zA-U_t$%|Lsntba%M)`iN!8YuBCDuBEGX&7q{{aKd#Mdy6P8XF3K=f~RR( zmo6-U==}~v@16?MsF>D0EZ|C+S5mIX=6N{loRQ_Asnof>jCZTv=uF14;} z(@QOeB^Tpp$0q9aK@2{+uk&?o>_8VyCLI}{9h*Y_Baw3adpOAwIhyypO*|fzaJ5?& zQdv0RS*2o-+a~#yw@h&A&Vi`lkEo|o6{O1gZm-~k2OgDNDE#u74}L|gyY%iVL6kj6 zlhaD&J}AOv9{+o{$Fw+k$~v}EUWFsYYu@`Sq^MP5B%cHg@TFAwM1yrwO64u(!R5xk zL#Zk}hae-^g9akcr*!-qOCa5-lqC67d+~s#PIk{DI zaMgIZ>M)kZ0y9ir6YSQrJl|I6`(tOylr}sB=oK*NO{`GDGkKuHrvx3eCTHCYE>Flk zex{9EUQVK;16|VB&Rw-V2tCuCglX#%v{|*>f*{aa+A0oYyS@x+N zpCu?T{HU_tnC`P&g5r5%S&x~IP|8&1<2I#C$@kS~lsa5i=>2x7eB~{(2}pxo36_12 zANlZB%yT-oFDn~i52cefdmzQKf8>yuKv+RCF3g!nbQ;fl24zihB?9%T z^+%Uex}2EqUMaXCycsLZ$*>Hq?L%@h*&~fR>>E7IuHa(3LSZgHcS`@>80wO1P&|W2 z6u;zCl+PG%`luAHyp@$4WI=b#07;=NEuAn$k4wqatm{c_2$m4DZ^9fi zsovX>=U!0z4mh(DhPMnC<(m{f&-h;wyTO}xg@Ez&PLB=@Q4C`nkDMNaSrdOjfyB_* z$@dOKLJ=>lFZn5jzk3AA?OVLvBVN3{xK3~pw#WLxP(~TB1qG&|SP(P6_PAoYkF!!> zljYbkQaJlc<@AWADn#btDgb1>L}qt;Ws3;j)#I+!(hY~Ky#${2xpDJpK!NOA@Eh^T zZ>BI0g^5NUU=+qH*|-=tj7Iz0H{p(jITQx33ru*PcD(Uw$;6y$&5Xr9Zi}$H-CkV# z9^oHpwlGc}W9;Ep1kFtG8ibKutMBKZLrBKZH#7n*Hr-Gt%+un}=+2Lv7kPjCDTl(r zV|+Wp85yyFA#mg(;KvP|;vb11Dqbg+(Tq;L?xam9a2O0AzG2W9cEI0OaNWl2V@~=Pk-& zxCpCs2&Mqy31%F`Xb!*vPhaRXZ0PwcIh;XFU&c%ZFPMppf^g`fWNX713_&I`l(Evy zm_T@R_%tfjf{Uk(o{rGLL7KW*k%6Aa%pbRA*=_XkABo9UttSz{qT?lvWnH?c7|Qz6 zeTBK6`Oak78g^-6X4~|(?{0@mT&lELC~Z!Zb|PV_%qNujQe_=NS;s=z+PkF{vz@8R zR-v*rS=u)L#+}kNi*ByEE>+bgRJEn5)(BN=ZWSe~I=?8kl*8hK_xlInEL3%nx0`v} z$$LZ&U8rhf{&n}tyu^|yshD|X`jzj#mUdUcpDI};l&o4PVH^r(PmI_T3+^T{dgkTn zm*)znUz>|0OIkjP-0DoNe@6TIHl}LV3AO7MYCG@Nulk_`>%YXE8vi0Dsexa;6z4jS;`?EulDr*+XntxW-k}j{99r%M9aI|ioy>T|> z-y!&SB>i2KwslF`1*EO46-Odbxjqf-DX(=DhHGmEKgPU#RpiRIa(}Yr9!?qb%jyD)_dhy{kU3UbjjG zf71Aq=09mp?K&asIsvG))XY8ykX6@RJ8<=YI4?e{@~7*XQgxj|UFSmGh9$sCZ0kwZ zpGZ`lxaVt6`L+qZZL`m@c}EZt=iNOK?r2`hyI%0Fr*=B7JJ|PH!Mm2e?bq$O@hR^b z!MlcqY!JK~77H!aH4k;x@(L*Xx39fb_R0Q*w!;Kf-`$3$51zgLY^q_4(6HrpXKL$V zVe8>f&n6qbF{}T+IqlyxtN#qJ2jtG(Dfh9KwF+gepOv-$YU$kBdb{iP`rEyUs-4Qz z0iUs?ddXb{x&Q>eHcZ`p*ZBoh^^4tKJ`949w&VhW5zDlx<%6@=&wdcT9!@rG5Nr+c zlT&Y|oh9+N(&ekCjC9$gdK&7nEka?-{Mq?ax8As2dE5TUu}_VkTuQV)pDcVKzKar7 zO;^2NGp7*>>*Kr9MeZz@8XBpq=X(;iwZB=kqq0YgxBt$rO~;zKKizt?9j`mh)jeBv zNkiA#o(;N%4F-JpO#6De>47HoW#FD}UnNiEEl;OHo)ipuCpirFen!5ZkwY?NJ5*8g%#CbrdvBOanf71 zr#EiJ^{6jO*BH7q_w24KuTH+2vNsC$#zg}KVUmOMS2JBBj>4je1)4cW;g!H-AZ2eB z?9Gc77FfWQRV`YX*Tz-W&XvtKT(1@?{EK!L=HS4dCHpW(rjN|+!6mFkCkuChTR7{U z-m+N8K8xrc%3ITKWu0B&fF&@qX6GPLd94;CqLS(@4@Tvdz*#g))G$+!4q+edz>6ev{B7rRgERTN?x`$jH=_y{vWMjaL9f_7%-hutLw z)W%n>Foymm$u?5ANumm)Ke*!vSwYHKCphcoI_EvNJc;^EN$2K-d9&DKdBl)j9{sH8~7Jm%<0+3c)tg(GgUndTCHIieXqHN%`j~)=Lg$icnW*Q1OW4YGDILhGWO; z9y_L#WDAYw8z=(f>Gs4eAYE?0b>pp6$8Moxcd}zI-M?!UZ1`Une|5HN_Kmqsq)RZ7 zBwJbKDHmOwoGbgx)v{<2UAZ)QiSF}#=bN~AlC?pG zyS7$wisTNqJVmI7SQNxPhA;o2C7^a;4xzc|CY!BaN31d#Wr)j!MEm>~aG>;~0Naaz zrAj}zp_5~~?lDUwH`!hSDJ?#LboZgS{)%JLF{__Fdu`q|C4@@)jp7z2qV0_NP-tu~v&OcF;`6}dlE5UR>gFebN=kW* zz(t3CoHw;POCb@vxH3GapX;CRPt-mmxHf!w*I7zpn5s)VT~m9$Gxm9D zB~6hkkdUqtap<$F1v$kP$yVh$eB|ER1$vSPn5ATnw9>GjV6I?)W@|(%;(F@r4Gmi! zxfZ0&u;oqKPQk~}9oZDGQI}}TfK_iy_fAq$NIL81UJ#s333Jo(jhFTZ)_jP&X}~o6 zkJ0el4zgw|k{$kEXE$e+&Z%v#A7=Ao(9bR3oTD<|c+i|pnDbJzst-Fr1~us~Lgjl? zN)ZTkDPNNBLz<>|==PPW-JcwS04TFWOr7h(7LkH7hxMeNPf*W$)U4VmD3xs7(m!#n z+j$d86mjL>p&U_>&cNdeD6E69B`w-Qm3OC%ar8V+Y9WzWVB~B8@x@q{NS08@fVvwV z9*6?dQb`e!a8QT@{JM?`k>Cn|g+>f8$pkfQRTDE*jpEHq*-JBU1|0z5Yb~4#l77c` zN75yw&`N6Y-}K+`r&_iPE!&eVJMVJ_`zpcJ1Ql2J)El#PvuE#?RbI1PwOn&vB{Q6} z&8Z49DlmWG=FuBRZ=d|*z#j#0UN0|#`3fUf=pk*h*^>({*gFu>dFV4&<07mJtb%0< zD8oPzCS_~Dd30>Zkde`!g2rXxP-ap~)0?k3~K^N5`PLym^>=*eL#%1J&NES_#l^JQ?L zohu&i`a(lPfdICd|A}}8%bgv!VIETW=`L9@RC5)fC-E(f0dBjiLU3k;8hyitIya@*^EJ9yFm{%1Jz=Q^kiW z2accyX=zW+d(^ODKhE_ll&pd!gF0rTUmvo-&cAvc?z~Fz*$A!L@tDr%%oHCxc3j3rM?_26`}jhD zuv;{_M53-GM`{eCxX7}E39NhO9K>*z2q3fgFw2{io;}QxJ1tN4NZ4A>DpTs>9mp=x zDbx-nYAKzB16gL{N~>X)*(zCSF0H|r4cGC(ILMxn6;#u=#9x22tf9L z-sQ0FXo-PE$qod63x6(#%@{B8gr&w++a<>R7Qrfen!>G#AnY_J%=HOp{at6%Ty(zY z)-$;r2USp!#C#=6RpOs$#H}|Uvsp-@#rQ=LMlr{2(1(tkc%no@;@tf%$=ib5p{+NkqD|Hu=jaV#&Ov1~Fh4 z!9(*l7b8gPTq7>*>bS$nE^=}g zsA1nhhIi>gNptH5m#$y>VB-2jy3(6&Uvu-d8?U9>w+rptzpz*gYwo#~`lvdWi!@P288!lVLoOUZuGx8lQ7YKUpCCE&(HlX@2k@lcV6?%lPce-7snW zu>rpecgac3tgI+WWrg3Y7zI~bQEIBHG4m7h5e``=ygp|_I4@^pj)n~W~YffLmCCq=+l#X%xPZu+NB%3>!utn>n zd@yyOk6DAnWhWE8uo?A{@x$yqnXt#~gAGzjGJ^C7LH zfiSqgRLXhLc&bzYopD$JQu^0lCtW5A_IJa1{q;5~@AcQaq1{9u+4As{lK2TlN(PHq zVhd3V>zd!|J=o8F?oP>tCWKi#a`K_1dR#5+3kB=N>yt>tN)p>TJPJFl^rLwqe8!-S z8y?F^5@Ch)0^(U6CzKHMvSLq%0EbY7ZU{rANrsOE6c|M%n1s=7Sm&Z% zOV9x*2oNiDk(ERSUI)aw#oiIrQ6bf$3^lW}${HlsE;emRkI7oBpLGKe&F?HTB|F?Z zMujT{FX;~(i2-zy{rn&+NKV;CEfPE5BJF0>ar`nNF0i6ZvXLu6B)iSAnIg^*#=T`k zr~jBNsLunH)M5rMRy*}h3e9N%D~DRCKuiT(Uu~9%#(GufM-Hd~w(PtTXNi~#Ac2-1 zGtdU5J{c@h?@=f?AT*RnC4*I3um~Svg^5NLrFG733&VC=e~1>OSWhUjRjQuCB#2P= z**szJ5Ql33{1(i8`jItpC1Y5SXv90hekKJGW8zOt_GY^PeZy=o*v!Bkd0bK+z*J}D zLB}lY?Hi3osa{$S=V1;RerceknILtUaLZ!{oUntqIsly>%yFgdBmJy1A~=BFM8qf6 zB%1HdpHXBoPn=MsuJO3}M7V)@w}ld|7zB^6)s zmyNx_k&uYrX@G(wUREIuIrSz&I|@UNSURi|*nk*J#B`(;ONyJc)`4gw=!hpy1{rCu z`KmiHQzd4o1N$HAWA>-mLP~?EGi+NGaVUNiF55vdGrFt%d>eH>JI`8_&=%^%>#z6l zqga(vt8=0WQIIZ{G%{kswrmq<7giF3(h;j~$##3II5ad5_n~d5fxsUIf*O;usddIn z#SVjMZ6E9?bl|<>FS_}P zNriYIjO+Me-13i7TKSMHnip72=UH|@3{&hc;AHf}z2`!r-GKi_f$Yr7j>?LzTPsD} zBZu+s9K4STB2GrmE&mn@Tk(jwgA$Xvi>NwH2l5D=OK5E?kEnM6O(Q55iI|x8BjpSo zzc0fB+nwVY&RjIr^!!QBbV)~JTt~63-$!f% zT~$@=g_EuDxHWBZBuo`_O%|5N#h>L$6|E79*4!#h7Hyt1-GjAplYG^k456nyols%B z6-avaUOt*O*%PL6S>v%;C~Qs@whM*r^Zm)fXC{sJ3QN!fN8Mc0e9Ntoq+{RZ{b`Hq zO3h>qeq-2Dy~x=Op1ERb_VDCz%CSmtteOkXAN-^x;aHV)96{>~OYy_FXJY}XK`M!PFmly|9vG&T&%*CdS+BzU;g*ic>#|iq!alU z+qz8qOLadfHSBLS`JNHABZ)b})WS1H>^PXzypBTXuqbj`NO~!1F%oMnV%IcqB)F=S)%Z>J|NVLg~Axx$~a^%+seh_SQPDdnPM@T{1HFQ_WN=ZNq!CKeDKV!HMj8-8_G`Aq@hOo+d}-atNOm9X`|xKCFof>=-)$8q`<$m8@Q zkUeM&`;APVEvNrNX?{r#F?KUWIm&)nF1@%E*-E7!UF6y7Wy|^(leP|=M+-$a}IuZJU{0g4~-g4`P6Pw@JzY=g4++yl&1WlS)*y2)m^h+ zwWrF~31#ajwC%pRNK>%r=ZZ?xdegU#ynQ64uN3r^^l{+r0~9c|N6?q4e8(H#eh$HD zy;*c&cHB_#h0ao>z3jMOrq|g2-tnSIv+fIy+=sklmqt@R+ecbLshZ6~&E|(3g*<#t zTcfF-Z4m9vZxbrEJ>(FuxKG11x8Wjudc)=)hd&OdHtZKR>`!mp^5eHYek--{fUxmE zdi}<9V{^J~O$O?UU`Z4MIURcz*@p z{ZSN{e+Bk73}L5EiAeK%B?f6O}~~Zp`ZD_ zRVaOf%ePp_Vv49RvVwpDXLk#>+POx-*0fm6BHdhJ$s+3@Fodxn-f+b?>ATV~ P*&!Gz9+pxP#((^OwEY!Z literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/zonetypes.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/zonetypes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..462e1f3079c106ceaf7b12ade98a7a97f01df063 GIT binary patch literal 1335 zcmbVL&rj4q6rSnN6?a)+LCA`@1P&|-G=L@?j0q7G5?M{W_0p7fLU(Pq*cM{Adf~`H{(V5E$RT{j$Dt zgdCyM9OAjrI0j>zm_#KeH94EAG@dz?=g1Rc@>|3d7+*;>iI;OFKK;^1YnM-aRf^dn zu%-WC%S^6x{e3cej8=NuMCD3vn<%Z^x*9EWYC7bKaxMSJbsV=^sJT@(;W1kem{|zI zHRey&kybKV$)H|58ly066GlKKqH-oxd6QEGCZbNe=bKb16pCV$o-eO5KX_qS%we^R z`mD6DJYPuMI)%LkLhC^78UnyJ3F!`3s{aOihwRXZH*DX>?S-=i#c~~{7_Pk%Du$&8 zL8xr7>V{&MSCq9du-xj*ubH| zWHXF9H?T=I$zisq0R|}*WuR$Q-C>#*b!eL7n(H>&X-#{zuG{A;vZk4?0b%LI((?1C z3(-x()_vb$a5vM-z)9W>ge*cD0dtN47yUtiE%H<7Kg@J)K0Xw?PB<#&8a$D@V=Eyd zr+tmaVC8C!OkNe4WC|^Hl0&g5MY$yZMcudN>{Zt*2NsBOUM`FlF2WuPQpaYvzIL7r zTaqO4jU<&zQpF5wgKd6!q5K`>b|RoarDDp%m*90HU{$;v0BE~7H#2)Lp6^dhdwsal zPTPK5K)}x*20(4^#@ZfSCot|WW|F_j7U%#C7Nww(CRugeiFmLgAvWcuYOnyYq>k|q z^FWRy-_qfZ-tDFX7mc!-8NUk+k7SICB_Poykj(JMyiA3mdql3`w z5y_rPGR?i6*`9qjyPq4`%Z;282%Pq&Xzu;U$FUD%`}y&`{P-z>!0`YP2lj=bJz?mR W{H61A=Yi1Q>K==Hj(d}h75@Wb$r+ab literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/_asyncbackend.py b/venv/lib/python3.12/site-packages/dns/_asyncbackend.py new file mode 100644 index 0000000..23455db --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/_asyncbackend.py @@ -0,0 +1,100 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# This is a nullcontext for both sync and async. 3.7 has a nullcontext, +# but it is only for sync use. + + +class NullContext: + def __init__(self, enter_result=None): + self.enter_result = enter_result + + def __enter__(self): + return self.enter_result + + def __exit__(self, exc_type, exc_value, traceback): + pass + + async def __aenter__(self): + return self.enter_result + + async def __aexit__(self, exc_type, exc_value, traceback): + pass + + +# These are declared here so backends can import them without creating +# circular dependencies with dns.asyncbackend. + + +class Socket: # pragma: no cover + def __init__(self, family: int, type: int): + self.family = family + self.type = type + + async def close(self): + pass + + async def getpeername(self): + raise NotImplementedError + + async def getsockname(self): + raise NotImplementedError + + async def getpeercert(self, timeout): + raise NotImplementedError + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_value, traceback): + await self.close() + + +class DatagramSocket(Socket): # pragma: no cover + async def sendto(self, what, destination, timeout): + raise NotImplementedError + + async def recvfrom(self, size, timeout): + raise NotImplementedError + + +class StreamSocket(Socket): # pragma: no cover + async def sendall(self, what, timeout): + raise NotImplementedError + + async def recv(self, size, timeout): + raise NotImplementedError + + +class NullTransport: + async def connect_tcp(self, host, port, timeout, local_address): + raise NotImplementedError + + +class Backend: # pragma: no cover + def name(self) -> str: + return "unknown" + + async def make_socket( + self, + af, + socktype, + proto=0, + source=None, + destination=None, + timeout=None, + ssl_context=None, + server_hostname=None, + ): + raise NotImplementedError + + def datagram_connection_required(self): + return False + + async def sleep(self, interval): + raise NotImplementedError + + def get_transport_class(self): + raise NotImplementedError + + async def wait_for(self, awaitable, timeout): + raise NotImplementedError diff --git a/venv/lib/python3.12/site-packages/dns/_asyncio_backend.py b/venv/lib/python3.12/site-packages/dns/_asyncio_backend.py new file mode 100644 index 0000000..303908c --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/_asyncio_backend.py @@ -0,0 +1,276 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +"""asyncio library query support""" + +import asyncio +import socket +import sys + +import dns._asyncbackend +import dns._features +import dns.exception +import dns.inet + +_is_win32 = sys.platform == "win32" + + +def _get_running_loop(): + try: + return asyncio.get_running_loop() + except AttributeError: # pragma: no cover + return asyncio.get_event_loop() + + +class _DatagramProtocol: + def __init__(self): + self.transport = None + self.recvfrom = None + + def connection_made(self, transport): + self.transport = transport + + def datagram_received(self, data, addr): + if self.recvfrom and not self.recvfrom.done(): + self.recvfrom.set_result((data, addr)) + + def error_received(self, exc): # pragma: no cover + if self.recvfrom and not self.recvfrom.done(): + self.recvfrom.set_exception(exc) + + def connection_lost(self, exc): + if self.recvfrom and not self.recvfrom.done(): + if exc is None: + # EOF we triggered. Is there a better way to do this? + try: + raise EOFError("EOF") + except EOFError as e: + self.recvfrom.set_exception(e) + else: + self.recvfrom.set_exception(exc) + + def close(self): + if self.transport is not None: + self.transport.close() + + +async def _maybe_wait_for(awaitable, timeout): + if timeout is not None: + try: + return await asyncio.wait_for(awaitable, timeout) + except asyncio.TimeoutError: + raise dns.exception.Timeout(timeout=timeout) + else: + return await awaitable + + +class DatagramSocket(dns._asyncbackend.DatagramSocket): + def __init__(self, family, transport, protocol): + super().__init__(family, socket.SOCK_DGRAM) + self.transport = transport + self.protocol = protocol + + async def sendto(self, what, destination, timeout): # pragma: no cover + # no timeout for asyncio sendto + self.transport.sendto(what, destination) + return len(what) + + async def recvfrom(self, size, timeout): + # ignore size as there's no way I know to tell protocol about it + done = _get_running_loop().create_future() + try: + assert self.protocol.recvfrom is None + self.protocol.recvfrom = done + await _maybe_wait_for(done, timeout) + return done.result() + finally: + self.protocol.recvfrom = None + + async def close(self): + self.protocol.close() + + async def getpeername(self): + return self.transport.get_extra_info("peername") + + async def getsockname(self): + return self.transport.get_extra_info("sockname") + + async def getpeercert(self, timeout): + raise NotImplementedError + + +class StreamSocket(dns._asyncbackend.StreamSocket): + def __init__(self, af, reader, writer): + super().__init__(af, socket.SOCK_STREAM) + self.reader = reader + self.writer = writer + + async def sendall(self, what, timeout): + self.writer.write(what) + return await _maybe_wait_for(self.writer.drain(), timeout) + + async def recv(self, size, timeout): + return await _maybe_wait_for(self.reader.read(size), timeout) + + async def close(self): + self.writer.close() + + async def getpeername(self): + return self.writer.get_extra_info("peername") + + async def getsockname(self): + return self.writer.get_extra_info("sockname") + + async def getpeercert(self, timeout): + return self.writer.get_extra_info("peercert") + + +if dns._features.have("doh"): + import anyio + import httpcore + import httpcore._backends.anyio + import httpx + + _CoreAsyncNetworkBackend = httpcore.AsyncNetworkBackend + _CoreAnyIOStream = httpcore._backends.anyio.AnyIOStream # pyright: ignore + + from dns.query import _compute_times, _expiration_for_this_attempt, _remaining + + class _NetworkBackend(_CoreAsyncNetworkBackend): + def __init__(self, resolver, local_port, bootstrap_address, family): + super().__init__() + self._local_port = local_port + self._resolver = resolver + self._bootstrap_address = bootstrap_address + self._family = family + if local_port != 0: + raise NotImplementedError( + "the asyncio transport for HTTPX cannot set the local port" + ) + + async def connect_tcp( + self, host, port, timeout=None, local_address=None, socket_options=None + ): # pylint: disable=signature-differs + addresses = [] + _, expiration = _compute_times(timeout) + if dns.inet.is_address(host): + addresses.append(host) + elif self._bootstrap_address is not None: + addresses.append(self._bootstrap_address) + else: + timeout = _remaining(expiration) + family = self._family + if local_address: + family = dns.inet.af_for_address(local_address) + answers = await self._resolver.resolve_name( + host, family=family, lifetime=timeout + ) + addresses = answers.addresses() + for address in addresses: + try: + attempt_expiration = _expiration_for_this_attempt(2.0, expiration) + timeout = _remaining(attempt_expiration) + with anyio.fail_after(timeout): + stream = await anyio.connect_tcp( + remote_host=address, + remote_port=port, + local_host=local_address, + ) + return _CoreAnyIOStream(stream) + except Exception: + pass + raise httpcore.ConnectError + + async def connect_unix_socket( + self, path, timeout=None, socket_options=None + ): # pylint: disable=signature-differs + raise NotImplementedError + + async def sleep(self, seconds): # pylint: disable=signature-differs + await anyio.sleep(seconds) + + class _HTTPTransport(httpx.AsyncHTTPTransport): + def __init__( + self, + *args, + local_port=0, + bootstrap_address=None, + resolver=None, + family=socket.AF_UNSPEC, + **kwargs, + ): + if resolver is None and bootstrap_address is None: + # pylint: disable=import-outside-toplevel,redefined-outer-name + import dns.asyncresolver + + resolver = dns.asyncresolver.Resolver() + super().__init__(*args, **kwargs) + self._pool._network_backend = _NetworkBackend( + resolver, local_port, bootstrap_address, family + ) + +else: + _HTTPTransport = dns._asyncbackend.NullTransport # type: ignore + + +class Backend(dns._asyncbackend.Backend): + def name(self): + return "asyncio" + + async def make_socket( + self, + af, + socktype, + proto=0, + source=None, + destination=None, + timeout=None, + ssl_context=None, + server_hostname=None, + ): + loop = _get_running_loop() + if socktype == socket.SOCK_DGRAM: + if _is_win32 and source is None: + # Win32 wants explicit binding before recvfrom(). This is the + # proper fix for [#637]. + source = (dns.inet.any_for_af(af), 0) + transport, protocol = await loop.create_datagram_endpoint( + _DatagramProtocol, # pyright: ignore + source, + family=af, + proto=proto, + remote_addr=destination, + ) + return DatagramSocket(af, transport, protocol) + elif socktype == socket.SOCK_STREAM: + if destination is None: + # This shouldn't happen, but we check to make code analysis software + # happier. + raise ValueError("destination required for stream sockets") + (r, w) = await _maybe_wait_for( + asyncio.open_connection( + destination[0], + destination[1], + ssl=ssl_context, + family=af, + proto=proto, + local_addr=source, + server_hostname=server_hostname, + ), + timeout, + ) + return StreamSocket(af, r, w) + raise NotImplementedError( + "unsupported socket " + f"type {socktype}" + ) # pragma: no cover + + async def sleep(self, interval): + await asyncio.sleep(interval) + + def datagram_connection_required(self): + return False + + def get_transport_class(self): + return _HTTPTransport + + async def wait_for(self, awaitable, timeout): + return await _maybe_wait_for(awaitable, timeout) diff --git a/venv/lib/python3.12/site-packages/dns/_ddr.py b/venv/lib/python3.12/site-packages/dns/_ddr.py new file mode 100644 index 0000000..bf5c11e --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/_ddr.py @@ -0,0 +1,154 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license +# +# Support for Discovery of Designated Resolvers + +import socket +import time +from urllib.parse import urlparse + +import dns.asyncbackend +import dns.inet +import dns.name +import dns.nameserver +import dns.query +import dns.rdtypes.svcbbase + +# The special name of the local resolver when using DDR +_local_resolver_name = dns.name.from_text("_dns.resolver.arpa") + + +# +# Processing is split up into I/O independent and I/O dependent parts to +# make supporting sync and async versions easy. +# + + +class _SVCBInfo: + def __init__(self, bootstrap_address, port, hostname, nameservers): + self.bootstrap_address = bootstrap_address + self.port = port + self.hostname = hostname + self.nameservers = nameservers + + def ddr_check_certificate(self, cert): + """Verify that the _SVCBInfo's address is in the cert's subjectAltName (SAN)""" + for name, value in cert["subjectAltName"]: + if name == "IP Address" and value == self.bootstrap_address: + return True + return False + + def make_tls_context(self): + ssl = dns.query.ssl + ctx = ssl.create_default_context() + ctx.minimum_version = ssl.TLSVersion.TLSv1_2 + return ctx + + def ddr_tls_check_sync(self, lifetime): + ctx = self.make_tls_context() + expiration = time.time() + lifetime + with socket.create_connection( + (self.bootstrap_address, self.port), lifetime + ) as s: + with ctx.wrap_socket(s, server_hostname=self.hostname) as ts: + ts.settimeout(dns.query._remaining(expiration)) + ts.do_handshake() + cert = ts.getpeercert() + return self.ddr_check_certificate(cert) + + async def ddr_tls_check_async(self, lifetime, backend=None): + if backend is None: + backend = dns.asyncbackend.get_default_backend() + ctx = self.make_tls_context() + expiration = time.time() + lifetime + async with await backend.make_socket( + dns.inet.af_for_address(self.bootstrap_address), + socket.SOCK_STREAM, + 0, + None, + (self.bootstrap_address, self.port), + lifetime, + ctx, + self.hostname, + ) as ts: + cert = await ts.getpeercert(dns.query._remaining(expiration)) + return self.ddr_check_certificate(cert) + + +def _extract_nameservers_from_svcb(answer): + bootstrap_address = answer.nameserver + if not dns.inet.is_address(bootstrap_address): + return [] + infos = [] + for rr in answer.rrset.processing_order(): + nameservers = [] + param = rr.params.get(dns.rdtypes.svcbbase.ParamKey.ALPN) + if param is None: + continue + alpns = set(param.ids) + host = rr.target.to_text(omit_final_dot=True) + port = None + param = rr.params.get(dns.rdtypes.svcbbase.ParamKey.PORT) + if param is not None: + port = param.port + # For now we ignore address hints and address resolution and always use the + # bootstrap address + if b"h2" in alpns: + param = rr.params.get(dns.rdtypes.svcbbase.ParamKey.DOHPATH) + if param is None or not param.value.endswith(b"{?dns}"): + continue + path = param.value[:-6].decode() + if not path.startswith("/"): + path = "/" + path + if port is None: + port = 443 + url = f"https://{host}:{port}{path}" + # check the URL + try: + urlparse(url) + nameservers.append(dns.nameserver.DoHNameserver(url, bootstrap_address)) + except Exception: + # continue processing other ALPN types + pass + if b"dot" in alpns: + if port is None: + port = 853 + nameservers.append( + dns.nameserver.DoTNameserver(bootstrap_address, port, host) + ) + if b"doq" in alpns: + if port is None: + port = 853 + nameservers.append( + dns.nameserver.DoQNameserver(bootstrap_address, port, True, host) + ) + if len(nameservers) > 0: + infos.append(_SVCBInfo(bootstrap_address, port, host, nameservers)) + return infos + + +def _get_nameservers_sync(answer, lifetime): + """Return a list of TLS-validated resolver nameservers extracted from an SVCB + answer.""" + nameservers = [] + infos = _extract_nameservers_from_svcb(answer) + for info in infos: + try: + if info.ddr_tls_check_sync(lifetime): + nameservers.extend(info.nameservers) + except Exception: + pass + return nameservers + + +async def _get_nameservers_async(answer, lifetime): + """Return a list of TLS-validated resolver nameservers extracted from an SVCB + answer.""" + nameservers = [] + infos = _extract_nameservers_from_svcb(answer) + for info in infos: + try: + if await info.ddr_tls_check_async(lifetime): + nameservers.extend(info.nameservers) + except Exception: + pass + return nameservers diff --git a/venv/lib/python3.12/site-packages/dns/_features.py b/venv/lib/python3.12/site-packages/dns/_features.py new file mode 100644 index 0000000..65a9a2a --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/_features.py @@ -0,0 +1,95 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import importlib.metadata +import itertools +import string +from typing import Dict, List, Tuple + + +def _tuple_from_text(version: str) -> Tuple: + text_parts = version.split(".") + int_parts = [] + for text_part in text_parts: + digit_prefix = "".join( + itertools.takewhile(lambda x: x in string.digits, text_part) + ) + try: + int_parts.append(int(digit_prefix)) + except Exception: + break + return tuple(int_parts) + + +def _version_check( + requirement: str, +) -> bool: + """Is the requirement fulfilled? + + The requirement must be of the form + + package>=version + """ + package, minimum = requirement.split(">=") + try: + version = importlib.metadata.version(package) + # This shouldn't happen, but it apparently can. + if version is None: + return False + except Exception: + return False + t_version = _tuple_from_text(version) + t_minimum = _tuple_from_text(minimum) + if t_version < t_minimum: + return False + return True + + +_cache: Dict[str, bool] = {} + + +def have(feature: str) -> bool: + """Is *feature* available? + + This tests if all optional packages needed for the + feature are available and recent enough. + + Returns ``True`` if the feature is available, + and ``False`` if it is not or if metadata is + missing. + """ + value = _cache.get(feature) + if value is not None: + return value + requirements = _requirements.get(feature) + if requirements is None: + # we make a cache entry here for consistency not performance + _cache[feature] = False + return False + ok = True + for requirement in requirements: + if not _version_check(requirement): + ok = False + break + _cache[feature] = ok + return ok + + +def force(feature: str, enabled: bool) -> None: + """Force the status of *feature* to be *enabled*. + + This method is provided as a workaround for any cases + where importlib.metadata is ineffective, or for testing. + """ + _cache[feature] = enabled + + +_requirements: Dict[str, List[str]] = { + ### BEGIN generated requirements + "dnssec": ["cryptography>=45"], + "doh": ["httpcore>=1.0.0", "httpx>=0.28.0", "h2>=4.2.0"], + "doq": ["aioquic>=1.2.0"], + "idna": ["idna>=3.10"], + "trio": ["trio>=0.30"], + "wmi": ["wmi>=1.5.1"], + ### END generated requirements +} diff --git a/venv/lib/python3.12/site-packages/dns/_immutable_ctx.py b/venv/lib/python3.12/site-packages/dns/_immutable_ctx.py new file mode 100644 index 0000000..b3d72de --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/_immutable_ctx.py @@ -0,0 +1,76 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# This implementation of the immutable decorator requires python >= +# 3.7, and is significantly more storage efficient when making classes +# with slots immutable. It's also faster. + +import contextvars +import inspect + +_in__init__ = contextvars.ContextVar("_immutable_in__init__", default=False) + + +class _Immutable: + """Immutable mixin class""" + + # We set slots to the empty list to say "we don't have any attributes". + # We do this so that if we're mixed in with a class with __slots__, we + # don't cause a __dict__ to be added which would waste space. + + __slots__ = () + + def __setattr__(self, name, value): + if _in__init__.get() is not self: + raise TypeError("object doesn't support attribute assignment") + else: + super().__setattr__(name, value) + + def __delattr__(self, name): + if _in__init__.get() is not self: + raise TypeError("object doesn't support attribute assignment") + else: + super().__delattr__(name) + + +def _immutable_init(f): + def nf(*args, **kwargs): + previous = _in__init__.set(args[0]) + try: + # call the actual __init__ + f(*args, **kwargs) + finally: + _in__init__.reset(previous) + + nf.__signature__ = inspect.signature(f) # pyright: ignore + return nf + + +def immutable(cls): + if _Immutable in cls.__mro__: + # Some ancestor already has the mixin, so just make sure we keep + # following the __init__ protocol. + cls.__init__ = _immutable_init(cls.__init__) + if hasattr(cls, "__setstate__"): + cls.__setstate__ = _immutable_init(cls.__setstate__) + ncls = cls + else: + # Mixin the Immutable class and follow the __init__ protocol. + class ncls(_Immutable, cls): + # We have to do the __slots__ declaration here too! + __slots__ = () + + @_immutable_init + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + if hasattr(cls, "__setstate__"): + + @_immutable_init + def __setstate__(self, *args, **kwargs): + super().__setstate__(*args, **kwargs) + + # make ncls have the same name and module as cls + ncls.__name__ = cls.__name__ + ncls.__qualname__ = cls.__qualname__ + ncls.__module__ = cls.__module__ + return ncls diff --git a/venv/lib/python3.12/site-packages/dns/_no_ssl.py b/venv/lib/python3.12/site-packages/dns/_no_ssl.py new file mode 100644 index 0000000..edb452d --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/_no_ssl.py @@ -0,0 +1,61 @@ +import enum +from typing import Any + +CERT_NONE = 0 + + +class TLSVersion(enum.IntEnum): + TLSv1_2 = 12 + + +class WantReadException(Exception): + pass + + +class WantWriteException(Exception): + pass + + +class SSLWantReadError(Exception): + pass + + +class SSLWantWriteError(Exception): + pass + + +class SSLContext: + def __init__(self) -> None: + self.minimum_version: Any = TLSVersion.TLSv1_2 + self.check_hostname: bool = False + self.verify_mode: int = CERT_NONE + + def wrap_socket(self, *args, **kwargs) -> "SSLSocket": # type: ignore + raise Exception("no ssl support") # pylint: disable=broad-exception-raised + + def set_alpn_protocols(self, *args, **kwargs): # type: ignore + raise Exception("no ssl support") # pylint: disable=broad-exception-raised + + +class SSLSocket: + def pending(self) -> bool: + raise Exception("no ssl support") # pylint: disable=broad-exception-raised + + def do_handshake(self) -> None: + raise Exception("no ssl support") # pylint: disable=broad-exception-raised + + def settimeout(self, value: Any) -> None: + pass + + def getpeercert(self) -> Any: + raise Exception("no ssl support") # pylint: disable=broad-exception-raised + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + return False + + +def create_default_context(*args, **kwargs) -> SSLContext: # type: ignore + raise Exception("no ssl support") # pylint: disable=broad-exception-raised diff --git a/venv/lib/python3.12/site-packages/dns/_tls_util.py b/venv/lib/python3.12/site-packages/dns/_tls_util.py new file mode 100644 index 0000000..10ddf72 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/_tls_util.py @@ -0,0 +1,19 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import os +from typing import Tuple + + +def convert_verify_to_cafile_and_capath( + verify: bool | str, +) -> Tuple[str | None, str | None]: + cafile: str | None = None + capath: str | None = None + if isinstance(verify, str): + if os.path.isfile(verify): + cafile = verify + elif os.path.isdir(verify): + capath = verify + else: + raise ValueError("invalid verify string") + return cafile, capath diff --git a/venv/lib/python3.12/site-packages/dns/_trio_backend.py b/venv/lib/python3.12/site-packages/dns/_trio_backend.py new file mode 100644 index 0000000..bde7e8b --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/_trio_backend.py @@ -0,0 +1,255 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +"""trio async I/O library query support""" + +import socket + +import trio +import trio.socket # type: ignore + +import dns._asyncbackend +import dns._features +import dns.exception +import dns.inet + +if not dns._features.have("trio"): + raise ImportError("trio not found or too old") + + +def _maybe_timeout(timeout): + if timeout is not None: + return trio.move_on_after(timeout) + else: + return dns._asyncbackend.NullContext() + + +# for brevity +_lltuple = dns.inet.low_level_address_tuple + +# pylint: disable=redefined-outer-name + + +class DatagramSocket(dns._asyncbackend.DatagramSocket): + def __init__(self, sock): + super().__init__(sock.family, socket.SOCK_DGRAM) + self.socket = sock + + async def sendto(self, what, destination, timeout): + with _maybe_timeout(timeout): + if destination is None: + return await self.socket.send(what) + else: + return await self.socket.sendto(what, destination) + raise dns.exception.Timeout( + timeout=timeout + ) # pragma: no cover lgtm[py/unreachable-statement] + + async def recvfrom(self, size, timeout): + with _maybe_timeout(timeout): + return await self.socket.recvfrom(size) + raise dns.exception.Timeout(timeout=timeout) # lgtm[py/unreachable-statement] + + async def close(self): + self.socket.close() + + async def getpeername(self): + return self.socket.getpeername() + + async def getsockname(self): + return self.socket.getsockname() + + async def getpeercert(self, timeout): + raise NotImplementedError + + +class StreamSocket(dns._asyncbackend.StreamSocket): + def __init__(self, family, stream, tls=False): + super().__init__(family, socket.SOCK_STREAM) + self.stream = stream + self.tls = tls + + async def sendall(self, what, timeout): + with _maybe_timeout(timeout): + return await self.stream.send_all(what) + raise dns.exception.Timeout(timeout=timeout) # lgtm[py/unreachable-statement] + + async def recv(self, size, timeout): + with _maybe_timeout(timeout): + return await self.stream.receive_some(size) + raise dns.exception.Timeout(timeout=timeout) # lgtm[py/unreachable-statement] + + async def close(self): + await self.stream.aclose() + + async def getpeername(self): + if self.tls: + return self.stream.transport_stream.socket.getpeername() + else: + return self.stream.socket.getpeername() + + async def getsockname(self): + if self.tls: + return self.stream.transport_stream.socket.getsockname() + else: + return self.stream.socket.getsockname() + + async def getpeercert(self, timeout): + if self.tls: + with _maybe_timeout(timeout): + await self.stream.do_handshake() + return self.stream.getpeercert() + else: + raise NotImplementedError + + +if dns._features.have("doh"): + import httpcore + import httpcore._backends.trio + import httpx + + _CoreAsyncNetworkBackend = httpcore.AsyncNetworkBackend + _CoreTrioStream = httpcore._backends.trio.TrioStream + + from dns.query import _compute_times, _expiration_for_this_attempt, _remaining + + class _NetworkBackend(_CoreAsyncNetworkBackend): + def __init__(self, resolver, local_port, bootstrap_address, family): + super().__init__() + self._local_port = local_port + self._resolver = resolver + self._bootstrap_address = bootstrap_address + self._family = family + + async def connect_tcp( + self, host, port, timeout=None, local_address=None, socket_options=None + ): # pylint: disable=signature-differs + addresses = [] + _, expiration = _compute_times(timeout) + if dns.inet.is_address(host): + addresses.append(host) + elif self._bootstrap_address is not None: + addresses.append(self._bootstrap_address) + else: + timeout = _remaining(expiration) + family = self._family + if local_address: + family = dns.inet.af_for_address(local_address) + answers = await self._resolver.resolve_name( + host, family=family, lifetime=timeout + ) + addresses = answers.addresses() + for address in addresses: + try: + af = dns.inet.af_for_address(address) + if local_address is not None or self._local_port != 0: + source = (local_address, self._local_port) + else: + source = None + destination = (address, port) + attempt_expiration = _expiration_for_this_attempt(2.0, expiration) + timeout = _remaining(attempt_expiration) + sock = await Backend().make_socket( + af, socket.SOCK_STREAM, 0, source, destination, timeout + ) + assert isinstance(sock, StreamSocket) + return _CoreTrioStream(sock.stream) + except Exception: + continue + raise httpcore.ConnectError + + async def connect_unix_socket( + self, path, timeout=None, socket_options=None + ): # pylint: disable=signature-differs + raise NotImplementedError + + async def sleep(self, seconds): # pylint: disable=signature-differs + await trio.sleep(seconds) + + class _HTTPTransport(httpx.AsyncHTTPTransport): + def __init__( + self, + *args, + local_port=0, + bootstrap_address=None, + resolver=None, + family=socket.AF_UNSPEC, + **kwargs, + ): + if resolver is None and bootstrap_address is None: + # pylint: disable=import-outside-toplevel,redefined-outer-name + import dns.asyncresolver + + resolver = dns.asyncresolver.Resolver() + super().__init__(*args, **kwargs) + self._pool._network_backend = _NetworkBackend( + resolver, local_port, bootstrap_address, family + ) + +else: + _HTTPTransport = dns._asyncbackend.NullTransport # type: ignore + + +class Backend(dns._asyncbackend.Backend): + def name(self): + return "trio" + + async def make_socket( + self, + af, + socktype, + proto=0, + source=None, + destination=None, + timeout=None, + ssl_context=None, + server_hostname=None, + ): + s = trio.socket.socket(af, socktype, proto) + stream = None + try: + if source: + await s.bind(_lltuple(source, af)) + if socktype == socket.SOCK_STREAM or destination is not None: + connected = False + with _maybe_timeout(timeout): + assert destination is not None + await s.connect(_lltuple(destination, af)) + connected = True + if not connected: + raise dns.exception.Timeout( + timeout=timeout + ) # lgtm[py/unreachable-statement] + except Exception: # pragma: no cover + s.close() + raise + if socktype == socket.SOCK_DGRAM: + return DatagramSocket(s) + elif socktype == socket.SOCK_STREAM: + stream = trio.SocketStream(s) + tls = False + if ssl_context: + tls = True + try: + stream = trio.SSLStream( + stream, ssl_context, server_hostname=server_hostname + ) + except Exception: # pragma: no cover + await stream.aclose() + raise + return StreamSocket(af, stream, tls) + raise NotImplementedError( + "unsupported socket " + f"type {socktype}" + ) # pragma: no cover + + async def sleep(self, interval): + await trio.sleep(interval) + + def get_transport_class(self): + return _HTTPTransport + + async def wait_for(self, awaitable, timeout): + with _maybe_timeout(timeout): + return await awaitable + raise dns.exception.Timeout( + timeout=timeout + ) # pragma: no cover lgtm[py/unreachable-statement] diff --git a/venv/lib/python3.12/site-packages/dns/asyncbackend.py b/venv/lib/python3.12/site-packages/dns/asyncbackend.py new file mode 100644 index 0000000..0ec58b0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/asyncbackend.py @@ -0,0 +1,101 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +from typing import Dict + +import dns.exception + +# pylint: disable=unused-import +from dns._asyncbackend import ( # noqa: F401 lgtm[py/unused-import] + Backend, + DatagramSocket, + Socket, + StreamSocket, +) + +# pylint: enable=unused-import + +_default_backend = None + +_backends: Dict[str, Backend] = {} + +# Allow sniffio import to be disabled for testing purposes +_no_sniffio = False + + +class AsyncLibraryNotFoundError(dns.exception.DNSException): + pass + + +def get_backend(name: str) -> Backend: + """Get the specified asynchronous backend. + + *name*, a ``str``, the name of the backend. Currently the "trio" + and "asyncio" backends are available. + + Raises NotImplementedError if an unknown backend name is specified. + """ + # pylint: disable=import-outside-toplevel,redefined-outer-name + backend = _backends.get(name) + if backend: + return backend + if name == "trio": + import dns._trio_backend + + backend = dns._trio_backend.Backend() + elif name == "asyncio": + import dns._asyncio_backend + + backend = dns._asyncio_backend.Backend() + else: + raise NotImplementedError(f"unimplemented async backend {name}") + _backends[name] = backend + return backend + + +def sniff() -> str: + """Attempt to determine the in-use asynchronous I/O library by using + the ``sniffio`` module if it is available. + + Returns the name of the library, or raises AsyncLibraryNotFoundError + if the library cannot be determined. + """ + # pylint: disable=import-outside-toplevel + try: + if _no_sniffio: + raise ImportError + import sniffio + + try: + return sniffio.current_async_library() + except sniffio.AsyncLibraryNotFoundError: + raise AsyncLibraryNotFoundError("sniffio cannot determine async library") + except ImportError: + import asyncio + + try: + asyncio.get_running_loop() + return "asyncio" + except RuntimeError: + raise AsyncLibraryNotFoundError("no async library detected") + + +def get_default_backend() -> Backend: + """Get the default backend, initializing it if necessary.""" + if _default_backend: + return _default_backend + + return set_default_backend(sniff()) + + +def set_default_backend(name: str) -> Backend: + """Set the default backend. + + It's not normally necessary to call this method, as + ``get_default_backend()`` will initialize the backend + appropriately in many cases. If ``sniffio`` is not installed, or + in testing situations, this function allows the backend to be set + explicitly. + """ + global _default_backend + _default_backend = get_backend(name) + return _default_backend diff --git a/venv/lib/python3.12/site-packages/dns/asyncquery.py b/venv/lib/python3.12/site-packages/dns/asyncquery.py new file mode 100644 index 0000000..bb77045 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/asyncquery.py @@ -0,0 +1,953 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""Talk to a DNS server.""" + +import base64 +import contextlib +import random +import socket +import struct +import time +import urllib.parse +from typing import Any, Dict, Optional, Tuple, cast + +import dns.asyncbackend +import dns.exception +import dns.inet +import dns.message +import dns.name +import dns.quic +import dns.rdatatype +import dns.transaction +import dns.tsig +import dns.xfr +from dns._asyncbackend import NullContext +from dns.query import ( + BadResponse, + HTTPVersion, + NoDOH, + NoDOQ, + UDPMode, + _check_status, + _compute_times, + _matches_destination, + _remaining, + have_doh, + make_ssl_context, +) + +try: + import ssl +except ImportError: + import dns._no_ssl as ssl # type: ignore + +if have_doh: + import httpx + +# for brevity +_lltuple = dns.inet.low_level_address_tuple + + +def _source_tuple(af, address, port): + # Make a high level source tuple, or return None if address and port + # are both None + if address or port: + if address is None: + if af == socket.AF_INET: + address = "0.0.0.0" + elif af == socket.AF_INET6: + address = "::" + else: + raise NotImplementedError(f"unknown address family {af}") + return (address, port) + else: + return None + + +def _timeout(expiration, now=None): + if expiration is not None: + if not now: + now = time.time() + return max(expiration - now, 0) + else: + return None + + +async def send_udp( + sock: dns.asyncbackend.DatagramSocket, + what: dns.message.Message | bytes, + destination: Any, + expiration: float | None = None, +) -> Tuple[int, float]: + """Send a DNS message to the specified UDP socket. + + *sock*, a ``dns.asyncbackend.DatagramSocket``. + + *what*, a ``bytes`` or ``dns.message.Message``, the message to send. + + *destination*, a destination tuple appropriate for the address family + of the socket, specifying where to send the query. + + *expiration*, a ``float`` or ``None``, the absolute time at which + a timeout exception should be raised. If ``None``, no timeout will + occur. The expiration value is meaningless for the asyncio backend, as + asyncio's transport sendto() never blocks. + + Returns an ``(int, float)`` tuple of bytes sent and the sent time. + """ + + if isinstance(what, dns.message.Message): + what = what.to_wire() + sent_time = time.time() + n = await sock.sendto(what, destination, _timeout(expiration, sent_time)) + return (n, sent_time) + + +async def receive_udp( + sock: dns.asyncbackend.DatagramSocket, + destination: Any | None = None, + expiration: float | None = None, + ignore_unexpected: bool = False, + one_rr_per_rrset: bool = False, + keyring: Dict[dns.name.Name, dns.tsig.Key] | None = None, + request_mac: bytes | None = b"", + ignore_trailing: bool = False, + raise_on_truncation: bool = False, + ignore_errors: bool = False, + query: dns.message.Message | None = None, +) -> Any: + """Read a DNS message from a UDP socket. + + *sock*, a ``dns.asyncbackend.DatagramSocket``. + + See :py:func:`dns.query.receive_udp()` for the documentation of the other + parameters, and exceptions. + + Returns a ``(dns.message.Message, float, tuple)`` tuple of the received message, the + received time, and the address where the message arrived from. + """ + + wire = b"" + while True: + (wire, from_address) = await sock.recvfrom(65535, _timeout(expiration)) + if not _matches_destination( + sock.family, from_address, destination, ignore_unexpected + ): + continue + received_time = time.time() + try: + r = dns.message.from_wire( + wire, + keyring=keyring, + request_mac=request_mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + raise_on_truncation=raise_on_truncation, + ) + except dns.message.Truncated as e: + # See the comment in query.py for details. + if ( + ignore_errors + and query is not None + and not query.is_response(e.message()) + ): + continue + else: + raise + except Exception: + if ignore_errors: + continue + else: + raise + if ignore_errors and query is not None and not query.is_response(r): + continue + return (r, received_time, from_address) + + +async def udp( + q: dns.message.Message, + where: str, + timeout: float | None = None, + port: int = 53, + source: str | None = None, + source_port: int = 0, + ignore_unexpected: bool = False, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + raise_on_truncation: bool = False, + sock: dns.asyncbackend.DatagramSocket | None = None, + backend: dns.asyncbackend.Backend | None = None, + ignore_errors: bool = False, +) -> dns.message.Message: + """Return the response obtained after sending a query via UDP. + + *sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``, + the socket to use for the query. If ``None``, the default, a + socket is created. Note that if a socket is provided, the + *source*, *source_port*, and *backend* are ignored. + + *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, + the default, then dnspython will use the default backend. + + See :py:func:`dns.query.udp()` for the documentation of the other + parameters, exceptions, and return type of this method. + """ + wire = q.to_wire() + (begin_time, expiration) = _compute_times(timeout) + af = dns.inet.af_for_address(where) + destination = _lltuple((where, port), af) + if sock: + cm: contextlib.AbstractAsyncContextManager = NullContext(sock) + else: + if not backend: + backend = dns.asyncbackend.get_default_backend() + stuple = _source_tuple(af, source, source_port) + if backend.datagram_connection_required(): + dtuple = (where, port) + else: + dtuple = None + cm = await backend.make_socket(af, socket.SOCK_DGRAM, 0, stuple, dtuple) + async with cm as s: + await send_udp(s, wire, destination, expiration) # pyright: ignore + (r, received_time, _) = await receive_udp( + s, # pyright: ignore + destination, + expiration, + ignore_unexpected, + one_rr_per_rrset, + q.keyring, + q.mac, + ignore_trailing, + raise_on_truncation, + ignore_errors, + q, + ) + r.time = received_time - begin_time + # We don't need to check q.is_response() if we are in ignore_errors mode + # as receive_udp() will have checked it. + if not (ignore_errors or q.is_response(r)): + raise BadResponse + return r + + +async def udp_with_fallback( + q: dns.message.Message, + where: str, + timeout: float | None = None, + port: int = 53, + source: str | None = None, + source_port: int = 0, + ignore_unexpected: bool = False, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + udp_sock: dns.asyncbackend.DatagramSocket | None = None, + tcp_sock: dns.asyncbackend.StreamSocket | None = None, + backend: dns.asyncbackend.Backend | None = None, + ignore_errors: bool = False, +) -> Tuple[dns.message.Message, bool]: + """Return the response to the query, trying UDP first and falling back + to TCP if UDP results in a truncated response. + + *udp_sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``, + the socket to use for the UDP query. If ``None``, the default, a + socket is created. Note that if a socket is provided the *source*, + *source_port*, and *backend* are ignored for the UDP query. + + *tcp_sock*, a ``dns.asyncbackend.StreamSocket``, or ``None``, the + socket to use for the TCP query. If ``None``, the default, a + socket is created. Note that if a socket is provided *where*, + *source*, *source_port*, and *backend* are ignored for the TCP query. + + *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, + the default, then dnspython will use the default backend. + + See :py:func:`dns.query.udp_with_fallback()` for the documentation + of the other parameters, exceptions, and return type of this + method. + """ + try: + response = await udp( + q, + where, + timeout, + port, + source, + source_port, + ignore_unexpected, + one_rr_per_rrset, + ignore_trailing, + True, + udp_sock, + backend, + ignore_errors, + ) + return (response, False) + except dns.message.Truncated: + response = await tcp( + q, + where, + timeout, + port, + source, + source_port, + one_rr_per_rrset, + ignore_trailing, + tcp_sock, + backend, + ) + return (response, True) + + +async def send_tcp( + sock: dns.asyncbackend.StreamSocket, + what: dns.message.Message | bytes, + expiration: float | None = None, +) -> Tuple[int, float]: + """Send a DNS message to the specified TCP socket. + + *sock*, a ``dns.asyncbackend.StreamSocket``. + + See :py:func:`dns.query.send_tcp()` for the documentation of the other + parameters, exceptions, and return type of this method. + """ + + if isinstance(what, dns.message.Message): + tcpmsg = what.to_wire(prepend_length=True) + else: + # copying the wire into tcpmsg is inefficient, but lets us + # avoid writev() or doing a short write that would get pushed + # onto the net + tcpmsg = len(what).to_bytes(2, "big") + what + sent_time = time.time() + await sock.sendall(tcpmsg, _timeout(expiration, sent_time)) + return (len(tcpmsg), sent_time) + + +async def _read_exactly(sock, count, expiration): + """Read the specified number of bytes from stream. Keep trying until we + either get the desired amount, or we hit EOF. + """ + s = b"" + while count > 0: + n = await sock.recv(count, _timeout(expiration)) + if n == b"": + raise EOFError("EOF") + count = count - len(n) + s = s + n + return s + + +async def receive_tcp( + sock: dns.asyncbackend.StreamSocket, + expiration: float | None = None, + one_rr_per_rrset: bool = False, + keyring: Dict[dns.name.Name, dns.tsig.Key] | None = None, + request_mac: bytes | None = b"", + ignore_trailing: bool = False, +) -> Tuple[dns.message.Message, float]: + """Read a DNS message from a TCP socket. + + *sock*, a ``dns.asyncbackend.StreamSocket``. + + See :py:func:`dns.query.receive_tcp()` for the documentation of the other + parameters, exceptions, and return type of this method. + """ + + ldata = await _read_exactly(sock, 2, expiration) + (l,) = struct.unpack("!H", ldata) + wire = await _read_exactly(sock, l, expiration) + received_time = time.time() + r = dns.message.from_wire( + wire, + keyring=keyring, + request_mac=request_mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + ) + return (r, received_time) + + +async def tcp( + q: dns.message.Message, + where: str, + timeout: float | None = None, + port: int = 53, + source: str | None = None, + source_port: int = 0, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + sock: dns.asyncbackend.StreamSocket | None = None, + backend: dns.asyncbackend.Backend | None = None, +) -> dns.message.Message: + """Return the response obtained after sending a query via TCP. + + *sock*, a ``dns.asyncbacket.StreamSocket``, or ``None``, the + socket to use for the query. If ``None``, the default, a socket + is created. Note that if a socket is provided + *where*, *port*, *source*, *source_port*, and *backend* are ignored. + + *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, + the default, then dnspython will use the default backend. + + See :py:func:`dns.query.tcp()` for the documentation of the other + parameters, exceptions, and return type of this method. + """ + + wire = q.to_wire() + (begin_time, expiration) = _compute_times(timeout) + if sock: + # Verify that the socket is connected, as if it's not connected, + # it's not writable, and the polling in send_tcp() will time out or + # hang forever. + await sock.getpeername() + cm: contextlib.AbstractAsyncContextManager = NullContext(sock) + else: + # These are simple (address, port) pairs, not family-dependent tuples + # you pass to low-level socket code. + af = dns.inet.af_for_address(where) + stuple = _source_tuple(af, source, source_port) + dtuple = (where, port) + if not backend: + backend = dns.asyncbackend.get_default_backend() + cm = await backend.make_socket( + af, socket.SOCK_STREAM, 0, stuple, dtuple, timeout + ) + async with cm as s: + await send_tcp(s, wire, expiration) # pyright: ignore + (r, received_time) = await receive_tcp( + s, # pyright: ignore + expiration, + one_rr_per_rrset, + q.keyring, + q.mac, + ignore_trailing, + ) + r.time = received_time - begin_time + if not q.is_response(r): + raise BadResponse + return r + + +async def tls( + q: dns.message.Message, + where: str, + timeout: float | None = None, + port: int = 853, + source: str | None = None, + source_port: int = 0, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + sock: dns.asyncbackend.StreamSocket | None = None, + backend: dns.asyncbackend.Backend | None = None, + ssl_context: ssl.SSLContext | None = None, + server_hostname: str | None = None, + verify: bool | str = True, +) -> dns.message.Message: + """Return the response obtained after sending a query via TLS. + + *sock*, an ``asyncbackend.StreamSocket``, or ``None``, the socket + to use for the query. If ``None``, the default, a socket is + created. Note that if a socket is provided, it must be a + connected SSL stream socket, and *where*, *port*, + *source*, *source_port*, *backend*, *ssl_context*, and *server_hostname* + are ignored. + + *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, + the default, then dnspython will use the default backend. + + See :py:func:`dns.query.tls()` for the documentation of the other + parameters, exceptions, and return type of this method. + """ + (begin_time, expiration) = _compute_times(timeout) + if sock: + cm: contextlib.AbstractAsyncContextManager = NullContext(sock) + else: + if ssl_context is None: + ssl_context = make_ssl_context(verify, server_hostname is not None, ["dot"]) + af = dns.inet.af_for_address(where) + stuple = _source_tuple(af, source, source_port) + dtuple = (where, port) + if not backend: + backend = dns.asyncbackend.get_default_backend() + cm = await backend.make_socket( + af, + socket.SOCK_STREAM, + 0, + stuple, + dtuple, + timeout, + ssl_context, + server_hostname, + ) + async with cm as s: + timeout = _timeout(expiration) + response = await tcp( + q, + where, + timeout, + port, + source, + source_port, + one_rr_per_rrset, + ignore_trailing, + s, + backend, + ) + end_time = time.time() + response.time = end_time - begin_time + return response + + +def _maybe_get_resolver( + resolver: Optional["dns.asyncresolver.Resolver"], # pyright: ignore +) -> "dns.asyncresolver.Resolver": # pyright: ignore + # We need a separate method for this to avoid overriding the global + # variable "dns" with the as-yet undefined local variable "dns" + # in https(). + if resolver is None: + # pylint: disable=import-outside-toplevel,redefined-outer-name + import dns.asyncresolver + + resolver = dns.asyncresolver.Resolver() + return resolver + + +async def https( + q: dns.message.Message, + where: str, + timeout: float | None = None, + port: int = 443, + source: str | None = None, + source_port: int = 0, # pylint: disable=W0613 + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + client: Optional["httpx.AsyncClient|dns.quic.AsyncQuicConnection"] = None, + path: str = "/dns-query", + post: bool = True, + verify: bool | str | ssl.SSLContext = True, + bootstrap_address: str | None = None, + resolver: Optional["dns.asyncresolver.Resolver"] = None, # pyright: ignore + family: int = socket.AF_UNSPEC, + http_version: HTTPVersion = HTTPVersion.DEFAULT, +) -> dns.message.Message: + """Return the response obtained after sending a query via DNS-over-HTTPS. + + *client*, a ``httpx.AsyncClient``. If provided, the client to use for + the query. + + Unlike the other dnspython async functions, a backend cannot be provided + in this function because httpx always auto-detects the async backend. + + See :py:func:`dns.query.https()` for the documentation of the other + parameters, exceptions, and return type of this method. + """ + + try: + af = dns.inet.af_for_address(where) + except ValueError: + af = None + # we bind url and then override as pyright can't figure out all paths bind. + url = where + if af is not None and dns.inet.is_address(where): + if af == socket.AF_INET: + url = f"https://{where}:{port}{path}" + elif af == socket.AF_INET6: + url = f"https://[{where}]:{port}{path}" + + extensions = {} + if bootstrap_address is None: + # pylint: disable=possibly-used-before-assignment + parsed = urllib.parse.urlparse(url) + if parsed.hostname is None: + raise ValueError("no hostname in URL") + if dns.inet.is_address(parsed.hostname): + bootstrap_address = parsed.hostname + extensions["sni_hostname"] = parsed.hostname + if parsed.port is not None: + port = parsed.port + + if http_version == HTTPVersion.H3 or ( + http_version == HTTPVersion.DEFAULT and not have_doh + ): + if bootstrap_address is None: + resolver = _maybe_get_resolver(resolver) + assert parsed.hostname is not None # pyright: ignore + answers = await resolver.resolve_name( # pyright: ignore + parsed.hostname, family # pyright: ignore + ) + bootstrap_address = random.choice(list(answers.addresses())) + if client and not isinstance( + client, dns.quic.AsyncQuicConnection + ): # pyright: ignore + raise ValueError("client parameter must be a dns.quic.AsyncQuicConnection.") + assert client is None or isinstance(client, dns.quic.AsyncQuicConnection) + return await _http3( + q, + bootstrap_address, + url, + timeout, + port, + source, + source_port, + one_rr_per_rrset, + ignore_trailing, + verify=verify, + post=post, + connection=client, + ) + + if not have_doh: + raise NoDOH # pragma: no cover + # pylint: disable=possibly-used-before-assignment + if client and not isinstance(client, httpx.AsyncClient): # pyright: ignore + raise ValueError("client parameter must be an httpx.AsyncClient") + # pylint: enable=possibly-used-before-assignment + + wire = q.to_wire() + headers = {"accept": "application/dns-message"} + + h1 = http_version in (HTTPVersion.H1, HTTPVersion.DEFAULT) + h2 = http_version in (HTTPVersion.H2, HTTPVersion.DEFAULT) + + backend = dns.asyncbackend.get_default_backend() + + if source is None: + local_address = None + local_port = 0 + else: + local_address = source + local_port = source_port + + if client: + cm: contextlib.AbstractAsyncContextManager = NullContext(client) + else: + transport = backend.get_transport_class()( + local_address=local_address, + http1=h1, + http2=h2, + verify=verify, + local_port=local_port, + bootstrap_address=bootstrap_address, + resolver=resolver, + family=family, + ) + + cm = httpx.AsyncClient( # pyright: ignore + http1=h1, http2=h2, verify=verify, transport=transport # type: ignore + ) + + async with cm as the_client: + # see https://tools.ietf.org/html/rfc8484#section-4.1.1 for DoH + # GET and POST examples + if post: + headers.update( + { + "content-type": "application/dns-message", + "content-length": str(len(wire)), + } + ) + response = await backend.wait_for( + the_client.post( # pyright: ignore + url, + headers=headers, + content=wire, + extensions=extensions, + ), + timeout, + ) + else: + wire = base64.urlsafe_b64encode(wire).rstrip(b"=") + twire = wire.decode() # httpx does a repr() if we give it bytes + response = await backend.wait_for( + the_client.get( # pyright: ignore + url, + headers=headers, + params={"dns": twire}, + extensions=extensions, + ), + timeout, + ) + + # see https://tools.ietf.org/html/rfc8484#section-4.2.1 for info about DoH + # status codes + if response.status_code < 200 or response.status_code > 299: + raise ValueError( + f"{where} responded with status code {response.status_code}" + f"\nResponse body: {response.content!r}" + ) + r = dns.message.from_wire( + response.content, + keyring=q.keyring, + request_mac=q.request_mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + ) + r.time = response.elapsed.total_seconds() + if not q.is_response(r): + raise BadResponse + return r + + +async def _http3( + q: dns.message.Message, + where: str, + url: str, + timeout: float | None = None, + port: int = 443, + source: str | None = None, + source_port: int = 0, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + verify: bool | str | ssl.SSLContext = True, + backend: dns.asyncbackend.Backend | None = None, + post: bool = True, + connection: dns.quic.AsyncQuicConnection | None = None, +) -> dns.message.Message: + if not dns.quic.have_quic: + raise NoDOH("DNS-over-HTTP3 is not available.") # pragma: no cover + + url_parts = urllib.parse.urlparse(url) + hostname = url_parts.hostname + assert hostname is not None + if url_parts.port is not None: + port = url_parts.port + + q.id = 0 + wire = q.to_wire() + the_connection: dns.quic.AsyncQuicConnection + if connection: + cfactory = dns.quic.null_factory + mfactory = dns.quic.null_factory + else: + (cfactory, mfactory) = dns.quic.factories_for_backend(backend) + + async with cfactory() as context: + async with mfactory( + context, verify_mode=verify, server_name=hostname, h3=True + ) as the_manager: + if connection: + the_connection = connection + else: + the_connection = the_manager.connect( # pyright: ignore + where, port, source, source_port + ) + (start, expiration) = _compute_times(timeout) + stream = await the_connection.make_stream(timeout) # pyright: ignore + async with stream: + # note that send_h3() does not need await + stream.send_h3(url, wire, post) + wire = await stream.receive(_remaining(expiration)) + _check_status(stream.headers(), where, wire) + finish = time.time() + r = dns.message.from_wire( + wire, + keyring=q.keyring, + request_mac=q.request_mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + ) + r.time = max(finish - start, 0.0) + if not q.is_response(r): + raise BadResponse + return r + + +async def quic( + q: dns.message.Message, + where: str, + timeout: float | None = None, + port: int = 853, + source: str | None = None, + source_port: int = 0, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + connection: dns.quic.AsyncQuicConnection | None = None, + verify: bool | str = True, + backend: dns.asyncbackend.Backend | None = None, + hostname: str | None = None, + server_hostname: str | None = None, +) -> dns.message.Message: + """Return the response obtained after sending an asynchronous query via + DNS-over-QUIC. + + *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, + the default, then dnspython will use the default backend. + + See :py:func:`dns.query.quic()` for the documentation of the other + parameters, exceptions, and return type of this method. + """ + + if not dns.quic.have_quic: + raise NoDOQ("DNS-over-QUIC is not available.") # pragma: no cover + + if server_hostname is not None and hostname is None: + hostname = server_hostname + + q.id = 0 + wire = q.to_wire() + the_connection: dns.quic.AsyncQuicConnection + if connection: + cfactory = dns.quic.null_factory + mfactory = dns.quic.null_factory + the_connection = connection + else: + (cfactory, mfactory) = dns.quic.factories_for_backend(backend) + + async with cfactory() as context: + async with mfactory( + context, + verify_mode=verify, + server_name=server_hostname, + ) as the_manager: + if not connection: + the_connection = the_manager.connect( # pyright: ignore + where, port, source, source_port + ) + (start, expiration) = _compute_times(timeout) + stream = await the_connection.make_stream(timeout) # pyright: ignore + async with stream: + await stream.send(wire, True) + wire = await stream.receive(_remaining(expiration)) + finish = time.time() + r = dns.message.from_wire( + wire, + keyring=q.keyring, + request_mac=q.request_mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + ) + r.time = max(finish - start, 0.0) + if not q.is_response(r): + raise BadResponse + return r + + +async def _inbound_xfr( + txn_manager: dns.transaction.TransactionManager, + s: dns.asyncbackend.Socket, + query: dns.message.Message, + serial: int | None, + timeout: float | None, + expiration: float, +) -> Any: + """Given a socket, does the zone transfer.""" + rdtype = query.question[0].rdtype + is_ixfr = rdtype == dns.rdatatype.IXFR + origin = txn_manager.from_wire_origin() + wire = query.to_wire() + is_udp = s.type == socket.SOCK_DGRAM + if is_udp: + udp_sock = cast(dns.asyncbackend.DatagramSocket, s) + await udp_sock.sendto(wire, None, _timeout(expiration)) + else: + tcp_sock = cast(dns.asyncbackend.StreamSocket, s) + tcpmsg = struct.pack("!H", len(wire)) + wire + await tcp_sock.sendall(tcpmsg, expiration) + with dns.xfr.Inbound(txn_manager, rdtype, serial, is_udp) as inbound: + done = False + tsig_ctx = None + r: dns.message.Message | None = None + while not done: + (_, mexpiration) = _compute_times(timeout) + if mexpiration is None or ( + expiration is not None and mexpiration > expiration + ): + mexpiration = expiration + if is_udp: + timeout = _timeout(mexpiration) + (rwire, _) = await udp_sock.recvfrom(65535, timeout) # pyright: ignore + else: + ldata = await _read_exactly(tcp_sock, 2, mexpiration) # pyright: ignore + (l,) = struct.unpack("!H", ldata) + rwire = await _read_exactly(tcp_sock, l, mexpiration) # pyright: ignore + r = dns.message.from_wire( + rwire, + keyring=query.keyring, + request_mac=query.mac, + xfr=True, + origin=origin, + tsig_ctx=tsig_ctx, + multi=(not is_udp), + one_rr_per_rrset=is_ixfr, + ) + done = inbound.process_message(r) + yield r + tsig_ctx = r.tsig_ctx + if query.keyring and r is not None and not r.had_tsig: + raise dns.exception.FormError("missing TSIG") + + +async def inbound_xfr( + where: str, + txn_manager: dns.transaction.TransactionManager, + query: dns.message.Message | None = None, + port: int = 53, + timeout: float | None = None, + lifetime: float | None = None, + source: str | None = None, + source_port: int = 0, + udp_mode: UDPMode = UDPMode.NEVER, + backend: dns.asyncbackend.Backend | None = None, +) -> None: + """Conduct an inbound transfer and apply it via a transaction from the + txn_manager. + + *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, + the default, then dnspython will use the default backend. + + See :py:func:`dns.query.inbound_xfr()` for the documentation of + the other parameters, exceptions, and return type of this method. + """ + if query is None: + (query, serial) = dns.xfr.make_query(txn_manager) + else: + serial = dns.xfr.extract_serial_from_query(query) + af = dns.inet.af_for_address(where) + stuple = _source_tuple(af, source, source_port) + dtuple = (where, port) + if not backend: + backend = dns.asyncbackend.get_default_backend() + (_, expiration) = _compute_times(lifetime) + if query.question[0].rdtype == dns.rdatatype.IXFR and udp_mode != UDPMode.NEVER: + s = await backend.make_socket( + af, socket.SOCK_DGRAM, 0, stuple, dtuple, _timeout(expiration) + ) + async with s: + try: + async for _ in _inbound_xfr( # pyright: ignore + txn_manager, + s, + query, + serial, + timeout, + expiration, # pyright: ignore + ): + pass + return + except dns.xfr.UseTCP: + if udp_mode == UDPMode.ONLY: + raise + + s = await backend.make_socket( + af, socket.SOCK_STREAM, 0, stuple, dtuple, _timeout(expiration) + ) + async with s: + async for _ in _inbound_xfr( # pyright: ignore + txn_manager, s, query, serial, timeout, expiration # pyright: ignore + ): + pass diff --git a/venv/lib/python3.12/site-packages/dns/asyncresolver.py b/venv/lib/python3.12/site-packages/dns/asyncresolver.py new file mode 100644 index 0000000..6f8c69f --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/asyncresolver.py @@ -0,0 +1,478 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""Asynchronous DNS stub resolver.""" + +import socket +import time +from typing import Any, Dict, List + +import dns._ddr +import dns.asyncbackend +import dns.asyncquery +import dns.exception +import dns.inet +import dns.name +import dns.nameserver +import dns.query +import dns.rdataclass +import dns.rdatatype +import dns.resolver # lgtm[py/import-and-import-from] +import dns.reversename + +# import some resolver symbols for brevity +from dns.resolver import NXDOMAIN, NoAnswer, NoRootSOA, NotAbsolute + +# for indentation purposes below +_udp = dns.asyncquery.udp +_tcp = dns.asyncquery.tcp + + +class Resolver(dns.resolver.BaseResolver): + """Asynchronous DNS stub resolver.""" + + async def resolve( + self, + qname: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str = dns.rdatatype.A, + rdclass: dns.rdataclass.RdataClass | str = dns.rdataclass.IN, + tcp: bool = False, + source: str | None = None, + raise_on_no_answer: bool = True, + source_port: int = 0, + lifetime: float | None = None, + search: bool | None = None, + backend: dns.asyncbackend.Backend | None = None, + ) -> dns.resolver.Answer: + """Query nameservers asynchronously to find the answer to the question. + + *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, + the default, then dnspython will use the default backend. + + See :py:func:`dns.resolver.Resolver.resolve()` for the + documentation of the other parameters, exceptions, and return + type of this method. + """ + + resolution = dns.resolver._Resolution( + self, qname, rdtype, rdclass, tcp, raise_on_no_answer, search + ) + if not backend: + backend = dns.asyncbackend.get_default_backend() + start = time.time() + while True: + (request, answer) = resolution.next_request() + # Note we need to say "if answer is not None" and not just + # "if answer" because answer implements __len__, and python + # will call that. We want to return if we have an answer + # object, including in cases where its length is 0. + if answer is not None: + # cache hit! + return answer + assert request is not None # needed for type checking + done = False + while not done: + (nameserver, tcp, backoff) = resolution.next_nameserver() + if backoff: + await backend.sleep(backoff) + timeout = self._compute_timeout(start, lifetime, resolution.errors) + try: + response = await nameserver.async_query( + request, + timeout=timeout, + source=source, + source_port=source_port, + max_size=tcp, + backend=backend, + ) + except Exception as ex: + (_, done) = resolution.query_result(None, ex) + continue + (answer, done) = resolution.query_result(response, None) + # Note we need to say "if answer is not None" and not just + # "if answer" because answer implements __len__, and python + # will call that. We want to return if we have an answer + # object, including in cases where its length is 0. + if answer is not None: + return answer + + async def resolve_address( + self, ipaddr: str, *args: Any, **kwargs: Any + ) -> dns.resolver.Answer: + """Use an asynchronous resolver to run a reverse query for PTR + records. + + This utilizes the resolve() method to perform a PTR lookup on the + specified IP address. + + *ipaddr*, a ``str``, the IPv4 or IPv6 address you want to get + the PTR record for. + + All other arguments that can be passed to the resolve() function + except for rdtype and rdclass are also supported by this + function. + + """ + # We make a modified kwargs for type checking happiness, as otherwise + # we get a legit warning about possibly having rdtype and rdclass + # in the kwargs more than once. + modified_kwargs: Dict[str, Any] = {} + modified_kwargs.update(kwargs) + modified_kwargs["rdtype"] = dns.rdatatype.PTR + modified_kwargs["rdclass"] = dns.rdataclass.IN + return await self.resolve( + dns.reversename.from_address(ipaddr), *args, **modified_kwargs + ) + + async def resolve_name( + self, + name: dns.name.Name | str, + family: int = socket.AF_UNSPEC, + **kwargs: Any, + ) -> dns.resolver.HostAnswers: + """Use an asynchronous resolver to query for address records. + + This utilizes the resolve() method to perform A and/or AAAA lookups on + the specified name. + + *qname*, a ``dns.name.Name`` or ``str``, the name to resolve. + + *family*, an ``int``, the address family. If socket.AF_UNSPEC + (the default), both A and AAAA records will be retrieved. + + All other arguments that can be passed to the resolve() function + except for rdtype and rdclass are also supported by this + function. + """ + # We make a modified kwargs for type checking happiness, as otherwise + # we get a legit warning about possibly having rdtype and rdclass + # in the kwargs more than once. + modified_kwargs: Dict[str, Any] = {} + modified_kwargs.update(kwargs) + modified_kwargs.pop("rdtype", None) + modified_kwargs["rdclass"] = dns.rdataclass.IN + + if family == socket.AF_INET: + v4 = await self.resolve(name, dns.rdatatype.A, **modified_kwargs) + return dns.resolver.HostAnswers.make(v4=v4) + elif family == socket.AF_INET6: + v6 = await self.resolve(name, dns.rdatatype.AAAA, **modified_kwargs) + return dns.resolver.HostAnswers.make(v6=v6) + elif family != socket.AF_UNSPEC: + raise NotImplementedError(f"unknown address family {family}") + + raise_on_no_answer = modified_kwargs.pop("raise_on_no_answer", True) + lifetime = modified_kwargs.pop("lifetime", None) + start = time.time() + v6 = await self.resolve( + name, + dns.rdatatype.AAAA, + raise_on_no_answer=False, + lifetime=self._compute_timeout(start, lifetime), + **modified_kwargs, + ) + # Note that setting name ensures we query the same name + # for A as we did for AAAA. (This is just in case search lists + # are active by default in the resolver configuration and + # we might be talking to a server that says NXDOMAIN when it + # wants to say NOERROR no data. + name = v6.qname + v4 = await self.resolve( + name, + dns.rdatatype.A, + raise_on_no_answer=False, + lifetime=self._compute_timeout(start, lifetime), + **modified_kwargs, + ) + answers = dns.resolver.HostAnswers.make( + v6=v6, v4=v4, add_empty=not raise_on_no_answer + ) + if not answers: + raise NoAnswer(response=v6.response) + return answers + + # pylint: disable=redefined-outer-name + + async def canonical_name(self, name: dns.name.Name | str) -> dns.name.Name: + """Determine the canonical name of *name*. + + The canonical name is the name the resolver uses for queries + after all CNAME and DNAME renamings have been applied. + + *name*, a ``dns.name.Name`` or ``str``, the query name. + + This method can raise any exception that ``resolve()`` can + raise, other than ``dns.resolver.NoAnswer`` and + ``dns.resolver.NXDOMAIN``. + + Returns a ``dns.name.Name``. + """ + try: + answer = await self.resolve(name, raise_on_no_answer=False) + canonical_name = answer.canonical_name + except dns.resolver.NXDOMAIN as e: + canonical_name = e.canonical_name + return canonical_name + + async def try_ddr(self, lifetime: float = 5.0) -> None: + """Try to update the resolver's nameservers using Discovery of Designated + Resolvers (DDR). If successful, the resolver will subsequently use + DNS-over-HTTPS or DNS-over-TLS for future queries. + + *lifetime*, a float, is the maximum time to spend attempting DDR. The default + is 5 seconds. + + If the SVCB query is successful and results in a non-empty list of nameservers, + then the resolver's nameservers are set to the returned servers in priority + order. + + The current implementation does not use any address hints from the SVCB record, + nor does it resolve addresses for the SCVB target name, rather it assumes that + the bootstrap nameserver will always be one of the addresses and uses it. + A future revision to the code may offer fuller support. The code verifies that + the bootstrap nameserver is in the Subject Alternative Name field of the + TLS certficate. + """ + try: + expiration = time.time() + lifetime + answer = await self.resolve( + dns._ddr._local_resolver_name, "svcb", lifetime=lifetime + ) + timeout = dns.query._remaining(expiration) + nameservers = await dns._ddr._get_nameservers_async(answer, timeout) + if len(nameservers) > 0: + self.nameservers = nameservers + except Exception: + pass + + +default_resolver = None + + +def get_default_resolver() -> Resolver: + """Get the default asynchronous resolver, initializing it if necessary.""" + if default_resolver is None: + reset_default_resolver() + assert default_resolver is not None + return default_resolver + + +def reset_default_resolver() -> None: + """Re-initialize default asynchronous resolver. + + Note that the resolver configuration (i.e. /etc/resolv.conf on UNIX + systems) will be re-read immediately. + """ + + global default_resolver + default_resolver = Resolver() + + +async def resolve( + qname: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str = dns.rdatatype.A, + rdclass: dns.rdataclass.RdataClass | str = dns.rdataclass.IN, + tcp: bool = False, + source: str | None = None, + raise_on_no_answer: bool = True, + source_port: int = 0, + lifetime: float | None = None, + search: bool | None = None, + backend: dns.asyncbackend.Backend | None = None, +) -> dns.resolver.Answer: + """Query nameservers asynchronously to find the answer to the question. + + This is a convenience function that uses the default resolver + object to make the query. + + See :py:func:`dns.asyncresolver.Resolver.resolve` for more + information on the parameters. + """ + + return await get_default_resolver().resolve( + qname, + rdtype, + rdclass, + tcp, + source, + raise_on_no_answer, + source_port, + lifetime, + search, + backend, + ) + + +async def resolve_address( + ipaddr: str, *args: Any, **kwargs: Any +) -> dns.resolver.Answer: + """Use a resolver to run a reverse query for PTR records. + + See :py:func:`dns.asyncresolver.Resolver.resolve_address` for more + information on the parameters. + """ + + return await get_default_resolver().resolve_address(ipaddr, *args, **kwargs) + + +async def resolve_name( + name: dns.name.Name | str, family: int = socket.AF_UNSPEC, **kwargs: Any +) -> dns.resolver.HostAnswers: + """Use a resolver to asynchronously query for address records. + + See :py:func:`dns.asyncresolver.Resolver.resolve_name` for more + information on the parameters. + """ + + return await get_default_resolver().resolve_name(name, family, **kwargs) + + +async def canonical_name(name: dns.name.Name | str) -> dns.name.Name: + """Determine the canonical name of *name*. + + See :py:func:`dns.resolver.Resolver.canonical_name` for more + information on the parameters and possible exceptions. + """ + + return await get_default_resolver().canonical_name(name) + + +async def try_ddr(timeout: float = 5.0) -> None: + """Try to update the default resolver's nameservers using Discovery of Designated + Resolvers (DDR). If successful, the resolver will subsequently use + DNS-over-HTTPS or DNS-over-TLS for future queries. + + See :py:func:`dns.resolver.Resolver.try_ddr` for more information. + """ + return await get_default_resolver().try_ddr(timeout) + + +async def zone_for_name( + name: dns.name.Name | str, + rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, + tcp: bool = False, + resolver: Resolver | None = None, + backend: dns.asyncbackend.Backend | None = None, +) -> dns.name.Name: + """Find the name of the zone which contains the specified name. + + See :py:func:`dns.resolver.Resolver.zone_for_name` for more + information on the parameters and possible exceptions. + """ + + if isinstance(name, str): + name = dns.name.from_text(name, dns.name.root) + if resolver is None: + resolver = get_default_resolver() + if not name.is_absolute(): + raise NotAbsolute(name) + while True: + try: + answer = await resolver.resolve( + name, dns.rdatatype.SOA, rdclass, tcp, backend=backend + ) + assert answer.rrset is not None + if answer.rrset.name == name: + return name + # otherwise we were CNAMEd or DNAMEd and need to look higher + except (NXDOMAIN, NoAnswer): + pass + try: + name = name.parent() + except dns.name.NoParent: # pragma: no cover + raise NoRootSOA + + +async def make_resolver_at( + where: dns.name.Name | str, + port: int = 53, + family: int = socket.AF_UNSPEC, + resolver: Resolver | None = None, +) -> Resolver: + """Make a stub resolver using the specified destination as the full resolver. + + *where*, a ``dns.name.Name`` or ``str`` the domain name or IP address of the + full resolver. + + *port*, an ``int``, the port to use. If not specified, the default is 53. + + *family*, an ``int``, the address family to use. This parameter is used if + *where* is not an address. The default is ``socket.AF_UNSPEC`` in which case + the first address returned by ``resolve_name()`` will be used, otherwise the + first address of the specified family will be used. + + *resolver*, a ``dns.asyncresolver.Resolver`` or ``None``, the resolver to use for + resolution of hostnames. If not specified, the default resolver will be used. + + Returns a ``dns.resolver.Resolver`` or raises an exception. + """ + if resolver is None: + resolver = get_default_resolver() + nameservers: List[str | dns.nameserver.Nameserver] = [] + if isinstance(where, str) and dns.inet.is_address(where): + nameservers.append(dns.nameserver.Do53Nameserver(where, port)) + else: + answers = await resolver.resolve_name(where, family) + for address in answers.addresses(): + nameservers.append(dns.nameserver.Do53Nameserver(address, port)) + res = Resolver(configure=False) + res.nameservers = nameservers + return res + + +async def resolve_at( + where: dns.name.Name | str, + qname: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str = dns.rdatatype.A, + rdclass: dns.rdataclass.RdataClass | str = dns.rdataclass.IN, + tcp: bool = False, + source: str | None = None, + raise_on_no_answer: bool = True, + source_port: int = 0, + lifetime: float | None = None, + search: bool | None = None, + backend: dns.asyncbackend.Backend | None = None, + port: int = 53, + family: int = socket.AF_UNSPEC, + resolver: Resolver | None = None, +) -> dns.resolver.Answer: + """Query nameservers to find the answer to the question. + + This is a convenience function that calls ``dns.asyncresolver.make_resolver_at()`` + to make a resolver, and then uses it to resolve the query. + + See ``dns.asyncresolver.Resolver.resolve`` for more information on the resolution + parameters, and ``dns.asyncresolver.make_resolver_at`` for information about the + resolver parameters *where*, *port*, *family*, and *resolver*. + + If making more than one query, it is more efficient to call + ``dns.asyncresolver.make_resolver_at()`` and then use that resolver for the queries + instead of calling ``resolve_at()`` multiple times. + """ + res = await make_resolver_at(where, port, family, resolver) + return await res.resolve( + qname, + rdtype, + rdclass, + tcp, + source, + raise_on_no_answer, + source_port, + lifetime, + search, + backend, + ) diff --git a/venv/lib/python3.12/site-packages/dns/btree.py b/venv/lib/python3.12/site-packages/dns/btree.py new file mode 100644 index 0000000..12da9f5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/btree.py @@ -0,0 +1,850 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +""" +A BTree in the style of Cormen, Leiserson, and Rivest's "Algorithms" book, with +copy-on-write node updates, cursors, and optional space optimization for mostly-in-order +insertion. +""" + +from collections.abc import MutableMapping, MutableSet +from typing import Any, Callable, Generic, Optional, Tuple, TypeVar, cast + +DEFAULT_T = 127 + +KT = TypeVar("KT") # the type of a key in Element + + +class Element(Generic[KT]): + """All items stored in the BTree are Elements.""" + + def key(self) -> KT: + """The key for this element; the returned type must implement comparison.""" + raise NotImplementedError # pragma: no cover + + +ET = TypeVar("ET", bound=Element) # the type of a value in a _KV + + +def _MIN(t: int) -> int: + """The minimum number of keys in a non-root node for a BTree with the specified + ``t`` + """ + return t - 1 + + +def _MAX(t: int) -> int: + """The maximum number of keys in node for a BTree with the specified ``t``""" + return 2 * t - 1 + + +class _Creator: + """A _Creator class instance is used as a unique id for the BTree which created + a node. + + We use a dedicated creator rather than just a BTree reference to avoid circularity + that would complicate GC. + """ + + def __str__(self): # pragma: no cover + return f"{id(self):x}" + + +class _Node(Generic[KT, ET]): + """A Node in the BTree. + + A Node (leaf or internal) of the BTree. + """ + + __slots__ = ["t", "creator", "is_leaf", "elts", "children"] + + def __init__(self, t: int, creator: _Creator, is_leaf: bool): + assert t >= 3 + self.t = t + self.creator = creator + self.is_leaf = is_leaf + self.elts: list[ET] = [] + self.children: list[_Node[KT, ET]] = [] + + def is_maximal(self) -> bool: + """Does this node have the maximal number of keys?""" + assert len(self.elts) <= _MAX(self.t) + return len(self.elts) == _MAX(self.t) + + def is_minimal(self) -> bool: + """Does this node have the minimal number of keys?""" + assert len(self.elts) >= _MIN(self.t) + return len(self.elts) == _MIN(self.t) + + def search_in_node(self, key: KT) -> tuple[int, bool]: + """Get the index of the ``Element`` matching ``key`` or the index of its + least successor. + + Returns a tuple of the index and an ``equal`` boolean that is ``True`` iff. + the key was found. + """ + l = len(self.elts) + if l > 0 and key > self.elts[l - 1].key(): + # This is optimizing near in-order insertion. + return l, False + l = 0 + i = len(self.elts) + r = i - 1 + equal = False + while l <= r: + m = (l + r) // 2 + k = self.elts[m].key() + if key == k: + i = m + equal = True + break + elif key < k: + i = m + r = m - 1 + else: + l = m + 1 + return i, equal + + def maybe_cow_child(self, index: int) -> "_Node[KT, ET]": + assert not self.is_leaf + child = self.children[index] + cloned = child.maybe_cow(self.creator) + if cloned: + self.children[index] = cloned + return cloned + else: + return child + + def _get_node(self, key: KT) -> Tuple[Optional["_Node[KT, ET]"], int]: + """Get the node associated with key and its index, doing + copy-on-write if we have to descend. + + Returns a tuple of the node and the index, or the tuple ``(None, 0)`` + if the key was not found. + """ + i, equal = self.search_in_node(key) + if equal: + return (self, i) + elif self.is_leaf: + return (None, 0) + else: + child = self.maybe_cow_child(i) + return child._get_node(key) + + def get(self, key: KT) -> ET | None: + """Get the element associated with *key* or return ``None``""" + i, equal = self.search_in_node(key) + if equal: + return self.elts[i] + elif self.is_leaf: + return None + else: + return self.children[i].get(key) + + def optimize_in_order_insertion(self, index: int) -> None: + """Try to minimize the number of Nodes in a BTree where the insertion + is done in-order or close to it, by stealing as much as we can from our + right sibling. + + If we don't do this, then an in-order insertion will produce a BTree + where most of the nodes are minimal. + """ + if index == 0: + return + left = self.children[index - 1] + if len(left.elts) == _MAX(self.t): + return + left = self.maybe_cow_child(index - 1) + while len(left.elts) < _MAX(self.t): + if not left.try_right_steal(self, index - 1): + break + + def insert_nonfull(self, element: ET, in_order: bool) -> ET | None: + assert not self.is_maximal() + while True: + key = element.key() + i, equal = self.search_in_node(key) + if equal: + # replace + old = self.elts[i] + self.elts[i] = element + return old + elif self.is_leaf: + self.elts.insert(i, element) + return None + else: + child = self.maybe_cow_child(i) + if child.is_maximal(): + self.adopt(*child.split()) + # Splitting might result in our target moving to us, so + # search again. + continue + oelt = child.insert_nonfull(element, in_order) + if in_order: + self.optimize_in_order_insertion(i) + return oelt + + def split(self) -> tuple["_Node[KT, ET]", ET, "_Node[KT, ET]"]: + """Split a maximal node into two minimal ones and a central element.""" + assert self.is_maximal() + right = self.__class__(self.t, self.creator, self.is_leaf) + right.elts = list(self.elts[_MIN(self.t) + 1 :]) + middle = self.elts[_MIN(self.t)] + self.elts = list(self.elts[: _MIN(self.t)]) + if not self.is_leaf: + right.children = list(self.children[_MIN(self.t) + 1 :]) + self.children = list(self.children[: _MIN(self.t) + 1]) + return self, middle, right + + def try_left_steal(self, parent: "_Node[KT, ET]", index: int) -> bool: + """Try to steal from this Node's left sibling for balancing purposes. + + Returns ``True`` if the theft was successful, or ``False`` if not. + """ + if index != 0: + left = parent.children[index - 1] + if not left.is_minimal(): + left = parent.maybe_cow_child(index - 1) + elt = parent.elts[index - 1] + parent.elts[index - 1] = left.elts.pop() + self.elts.insert(0, elt) + if not left.is_leaf: + assert not self.is_leaf + child = left.children.pop() + self.children.insert(0, child) + return True + return False + + def try_right_steal(self, parent: "_Node[KT, ET]", index: int) -> bool: + """Try to steal from this Node's right sibling for balancing purposes. + + Returns ``True`` if the theft was successful, or ``False`` if not. + """ + if index + 1 < len(parent.children): + right = parent.children[index + 1] + if not right.is_minimal(): + right = parent.maybe_cow_child(index + 1) + elt = parent.elts[index] + parent.elts[index] = right.elts.pop(0) + self.elts.append(elt) + if not right.is_leaf: + assert not self.is_leaf + child = right.children.pop(0) + self.children.append(child) + return True + return False + + def adopt(self, left: "_Node[KT, ET]", middle: ET, right: "_Node[KT, ET]") -> None: + """Adopt left, middle, and right into our Node (which must not be maximal, + and which must not be a leaf). In the case were we are not the new root, + then the left child must already be in the Node.""" + assert not self.is_maximal() + assert not self.is_leaf + key = middle.key() + i, equal = self.search_in_node(key) + assert not equal + self.elts.insert(i, middle) + if len(self.children) == 0: + # We are the new root + self.children = [left, right] + else: + assert self.children[i] == left + self.children.insert(i + 1, right) + + def merge(self, parent: "_Node[KT, ET]", index: int) -> None: + """Merge this node's parent and its right sibling into this node.""" + right = parent.children.pop(index + 1) + self.elts.append(parent.elts.pop(index)) + self.elts.extend(right.elts) + if not self.is_leaf: + self.children.extend(right.children) + + def minimum(self) -> ET: + """The least element in this subtree.""" + if self.is_leaf: + return self.elts[0] + else: + return self.children[0].minimum() + + def maximum(self) -> ET: + """The greatest element in this subtree.""" + if self.is_leaf: + return self.elts[-1] + else: + return self.children[-1].maximum() + + def balance(self, parent: "_Node[KT, ET]", index: int) -> None: + """This Node is minimal, and we want to make it non-minimal so we can delete. + We try to steal from our siblings, and if that doesn't work we will merge + with one of them.""" + assert not parent.is_leaf + if self.try_left_steal(parent, index): + return + if self.try_right_steal(parent, index): + return + # Stealing didn't work, so both siblings must be minimal. + if index == 0: + # We are the left-most node so merge with our right sibling. + self.merge(parent, index) + else: + # Have our left sibling merge with us. This lets us only have "merge right" + # code. + left = parent.maybe_cow_child(index - 1) + left.merge(parent, index - 1) + + def delete( + self, key: KT, parent: Optional["_Node[KT, ET]"], exact: ET | None + ) -> ET | None: + """Delete an element matching *key* if it exists. If *exact* is not ``None`` + then it must be an exact match with that element. The Node must not be + minimal unless it is the root.""" + assert parent is None or not self.is_minimal() + i, equal = self.search_in_node(key) + original_key = None + if equal: + # Note we use "is" here as we meant "exactly this object". + if exact is not None and self.elts[i] is not exact: + raise ValueError("exact delete did not match existing elt") + if self.is_leaf: + return self.elts.pop(i) + # Note we need to ensure exact is None going forward as we've + # already checked exactness and are about to change our target key + # to the least successor. + exact = None + original_key = key + least_successor = self.children[i + 1].minimum() + key = least_successor.key() + i = i + 1 + if self.is_leaf: + # No match + if exact is not None: + raise ValueError("exact delete had no match") + return None + # recursively delete in the appropriate child + child = self.maybe_cow_child(i) + if child.is_minimal(): + child.balance(self, i) + # Things may have moved. + i, equal = self.search_in_node(key) + assert not equal + child = self.children[i] + assert not child.is_minimal() + elt = child.delete(key, self, exact) + if original_key is not None: + node, i = self._get_node(original_key) + assert node is not None + assert elt is not None + oelt = node.elts[i] + node.elts[i] = elt + elt = oelt + return elt + + def visit_in_order(self, visit: Callable[[ET], None]) -> None: + """Call *visit* on all of the elements in order.""" + for i, elt in enumerate(self.elts): + if not self.is_leaf: + self.children[i].visit_in_order(visit) + visit(elt) + if not self.is_leaf: + self.children[-1].visit_in_order(visit) + + def _visit_preorder_by_node(self, visit: Callable[["_Node[KT, ET]"], None]) -> None: + """Visit nodes in preorder. This method is only used for testing.""" + visit(self) + if not self.is_leaf: + for child in self.children: + child._visit_preorder_by_node(visit) + + def maybe_cow(self, creator: _Creator) -> Optional["_Node[KT, ET]"]: + """Return a clone of this Node if it was not created by *creator*, or ``None`` + otherwise (i.e. copy for copy-on-write if we haven't already copied it).""" + if self.creator is not creator: + return self.clone(creator) + else: + return None + + def clone(self, creator: _Creator) -> "_Node[KT, ET]": + """Make a shallow-copy duplicate of this node.""" + cloned = self.__class__(self.t, creator, self.is_leaf) + cloned.elts.extend(self.elts) + if not self.is_leaf: + cloned.children.extend(self.children) + return cloned + + def __str__(self): # pragma: no cover + if not self.is_leaf: + children = " " + " ".join([f"{id(c):x}" for c in self.children]) + else: + children = "" + return f"{id(self):x} {self.creator} {self.elts}{children}" + + +class Cursor(Generic[KT, ET]): + """A seekable cursor for a BTree. + + If you are going to use a cursor on a mutable BTree, you should use it + in a ``with`` block so that any mutations of the BTree automatically park + the cursor. + """ + + def __init__(self, btree: "BTree[KT, ET]"): + self.btree = btree + self.current_node: _Node | None = None + # The current index is the element index within the current node, or + # if there is no current node then it is 0 on the left boundary and 1 + # on the right boundary. + self.current_index: int = 0 + self.recurse = False + self.increasing = True + self.parents: list[tuple[_Node, int]] = [] + self.parked = False + self.parking_key: KT | None = None + self.parking_key_read = False + + def _seek_least(self) -> None: + # seek to the least value in the subtree beneath the current index of the + # current node + assert self.current_node is not None + while not self.current_node.is_leaf: + self.parents.append((self.current_node, self.current_index)) + self.current_node = self.current_node.children[self.current_index] + assert self.current_node is not None + self.current_index = 0 + + def _seek_greatest(self) -> None: + # seek to the greatest value in the subtree beneath the current index of the + # current node + assert self.current_node is not None + while not self.current_node.is_leaf: + self.parents.append((self.current_node, self.current_index)) + self.current_node = self.current_node.children[self.current_index] + assert self.current_node is not None + self.current_index = len(self.current_node.elts) + + def park(self): + """Park the cursor. + + A cursor must be "parked" before mutating the BTree to avoid undefined behavior. + Cursors created in a ``with`` block register with their BTree and will park + automatically. Note that a parked cursor may not observe some changes made when + it is parked; for example a cursor being iterated with next() will not see items + inserted before its current position. + """ + if not self.parked: + self.parked = True + + def _maybe_unpark(self): + if self.parked: + if self.parking_key is not None: + # remember our increasing hint, as seeking might change it + increasing = self.increasing + if self.parking_key_read: + # We've already returned the parking key, so we want to be before it + # if decreasing and after it if increasing. + before = not self.increasing + else: + # We haven't returned the parking key, so we've parked right + # after seeking or are on a boundary. Either way, the before + # hint we want is the value of self.increasing. + before = self.increasing + self.seek(self.parking_key, before) + self.increasing = increasing # might have been altered by seek() + self.parked = False + self.parking_key = None + + def prev(self) -> ET | None: + """Get the previous element, or return None if on the left boundary.""" + self._maybe_unpark() + self.parking_key = None + if self.current_node is None: + # on a boundary + if self.current_index == 0: + # left boundary, there is no prev + return None + else: + assert self.current_index == 1 + # right boundary; seek to the actual boundary + # so we can do a prev() + self.current_node = self.btree.root + self.current_index = len(self.btree.root.elts) + self._seek_greatest() + while True: + if self.recurse: + if not self.increasing: + # We only want to recurse if we are continuing in the decreasing + # direction. + self._seek_greatest() + self.recurse = False + self.increasing = False + self.current_index -= 1 + if self.current_index >= 0: + elt = self.current_node.elts[self.current_index] + if not self.current_node.is_leaf: + self.recurse = True + self.parking_key = elt.key() + self.parking_key_read = True + return elt + else: + if len(self.parents) > 0: + self.current_node, self.current_index = self.parents.pop() + else: + self.current_node = None + self.current_index = 0 + return None + + def next(self) -> ET | None: + """Get the next element, or return None if on the right boundary.""" + self._maybe_unpark() + self.parking_key = None + if self.current_node is None: + # on a boundary + if self.current_index == 1: + # right boundary, there is no next + return None + else: + assert self.current_index == 0 + # left boundary; seek to the actual boundary + # so we can do a next() + self.current_node = self.btree.root + self.current_index = 0 + self._seek_least() + while True: + if self.recurse: + if self.increasing: + # We only want to recurse if we are continuing in the increasing + # direction. + self._seek_least() + self.recurse = False + self.increasing = True + if self.current_index < len(self.current_node.elts): + elt = self.current_node.elts[self.current_index] + self.current_index += 1 + if not self.current_node.is_leaf: + self.recurse = True + self.parking_key = elt.key() + self.parking_key_read = True + return elt + else: + if len(self.parents) > 0: + self.current_node, self.current_index = self.parents.pop() + else: + self.current_node = None + self.current_index = 1 + return None + + def _adjust_for_before(self, before: bool, i: int) -> None: + if before: + self.current_index = i + else: + self.current_index = i + 1 + + def seek(self, key: KT, before: bool = True) -> None: + """Seek to the specified key. + + If *before* is ``True`` (the default) then the cursor is positioned just + before *key* if it exists, or before its least successor if it doesn't. A + subsequent next() will retrieve this value. If *before* is ``False``, then + the cursor is positioned just after *key* if it exists, or its greatest + precessessor if it doesn't. A subsequent prev() will return this value. + """ + self.current_node = self.btree.root + assert self.current_node is not None + self.recurse = False + self.parents = [] + self.increasing = before + self.parked = False + self.parking_key = key + self.parking_key_read = False + while not self.current_node.is_leaf: + i, equal = self.current_node.search_in_node(key) + if equal: + self._adjust_for_before(before, i) + if before: + self._seek_greatest() + else: + self._seek_least() + return + self.parents.append((self.current_node, i)) + self.current_node = self.current_node.children[i] + assert self.current_node is not None + i, equal = self.current_node.search_in_node(key) + if equal: + self._adjust_for_before(before, i) + else: + self.current_index = i + + def seek_first(self) -> None: + """Seek to the left boundary (i.e. just before the least element). + + A subsequent next() will return the least element if the BTree isn't empty.""" + self.current_node = None + self.current_index = 0 + self.recurse = False + self.increasing = True + self.parents = [] + self.parked = False + self.parking_key = None + + def seek_last(self) -> None: + """Seek to the right boundary (i.e. just after the greatest element). + + A subsequent prev() will return the greatest element if the BTree isn't empty. + """ + self.current_node = None + self.current_index = 1 + self.recurse = False + self.increasing = False + self.parents = [] + self.parked = False + self.parking_key = None + + def __enter__(self): + self.btree.register_cursor(self) + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.btree.deregister_cursor(self) + return False + + +class Immutable(Exception): + """The BTree is immutable.""" + + +class BTree(Generic[KT, ET]): + """An in-memory BTree with copy-on-write and cursors.""" + + def __init__(self, *, t: int = DEFAULT_T, original: Optional["BTree"] = None): + """Create a BTree. + + If *original* is not ``None``, then the BTree is shallow-cloned from + *original* using copy-on-write. Otherwise a new BTree with the specified + *t* value is created. + + The BTree is not thread-safe. + """ + # We don't use a reference to ourselves as a creator as we don't want + # to prevent GC of old btrees. + self.creator = _Creator() + self._immutable = False + self.t: int + self.root: _Node + self.size: int + self.cursors: set[Cursor] = set() + if original is not None: + if not original._immutable: + raise ValueError("original BTree is not immutable") + self.t = original.t + self.root = original.root + self.size = original.size + else: + if t < 3: + raise ValueError("t must be >= 3") + self.t = t + self.root = _Node(self.t, self.creator, True) + self.size = 0 + + def make_immutable(self): + """Make the BTree immutable. + + Attempts to alter the BTree after making it immutable will raise an + Immutable exception. This operation cannot be undone. + """ + if not self._immutable: + self._immutable = True + + def _check_mutable_and_park(self) -> None: + if self._immutable: + raise Immutable + for cursor in self.cursors: + cursor.park() + + # Note that we don't use insert() and delete() but rather insert_element() and + # delete_key() so that BTreeDict can be a proper MutableMapping and supply the + # rest of the standard mapping API. + + def insert_element(self, elt: ET, in_order: bool = False) -> ET | None: + """Insert the element into the BTree. + + If *in_order* is ``True``, then extra work will be done to make left siblings + full, which optimizes storage space when the the elements are inserted in-order + or close to it. + + Returns the previously existing element at the element's key or ``None``. + """ + self._check_mutable_and_park() + cloned = self.root.maybe_cow(self.creator) + if cloned: + self.root = cloned + if self.root.is_maximal(): + old_root = self.root + self.root = _Node(self.t, self.creator, False) + self.root.adopt(*old_root.split()) + oelt = self.root.insert_nonfull(elt, in_order) + if oelt is None: + # We did not replace, so something was added. + self.size += 1 + return oelt + + def get_element(self, key: KT) -> ET | None: + """Get the element matching *key* from the BTree, or return ``None`` if it + does not exist. + """ + return self.root.get(key) + + def _delete(self, key: KT, exact: ET | None) -> ET | None: + self._check_mutable_and_park() + cloned = self.root.maybe_cow(self.creator) + if cloned: + self.root = cloned + elt = self.root.delete(key, None, exact) + if elt is not None: + # We deleted something + self.size -= 1 + if len(self.root.elts) == 0: + # The root is now empty. If there is a child, then collapse this root + # level and make the child the new root. + if not self.root.is_leaf: + assert len(self.root.children) == 1 + self.root = self.root.children[0] + return elt + + def delete_key(self, key: KT) -> ET | None: + """Delete the element matching *key* from the BTree. + + Returns the matching element or ``None`` if it does not exist. + """ + return self._delete(key, None) + + def delete_exact(self, element: ET) -> ET | None: + """Delete *element* from the BTree. + + Returns the matching element or ``None`` if it was not in the BTree. + """ + delt = self._delete(element.key(), element) + assert delt is element + return delt + + def __len__(self): + return self.size + + def visit_in_order(self, visit: Callable[[ET], None]) -> None: + """Call *visit*(element) on all elements in the tree in sorted order.""" + self.root.visit_in_order(visit) + + def _visit_preorder_by_node(self, visit: Callable[[_Node], None]) -> None: + self.root._visit_preorder_by_node(visit) + + def cursor(self) -> Cursor[KT, ET]: + """Create a cursor.""" + return Cursor(self) + + def register_cursor(self, cursor: Cursor) -> None: + """Register a cursor for the automatic parking service.""" + self.cursors.add(cursor) + + def deregister_cursor(self, cursor: Cursor) -> None: + """Deregister a cursor from the automatic parking service.""" + self.cursors.discard(cursor) + + def __copy__(self): + return self.__class__(original=self) + + def __iter__(self): + with self.cursor() as cursor: + while True: + elt = cursor.next() + if elt is None: + break + yield elt.key() + + +VT = TypeVar("VT") # the type of a value in a BTreeDict + + +class KV(Element, Generic[KT, VT]): + """The BTree element type used in a ``BTreeDict``.""" + + def __init__(self, key: KT, value: VT): + self._key = key + self._value = value + + def key(self) -> KT: + return self._key + + def value(self) -> VT: + return self._value + + def __str__(self): # pragma: no cover + return f"KV({self._key}, {self._value})" + + def __repr__(self): # pragma: no cover + return f"KV({self._key}, {self._value})" + + +class BTreeDict(Generic[KT, VT], BTree[KT, KV[KT, VT]], MutableMapping[KT, VT]): + """A MutableMapping implemented with a BTree. + + Unlike a normal Python dict, the BTreeDict may be mutated while iterating. + """ + + def __init__( + self, + *, + t: int = DEFAULT_T, + original: BTree | None = None, + in_order: bool = False, + ): + super().__init__(t=t, original=original) + self.in_order = in_order + + def __getitem__(self, key: KT) -> VT: + elt = self.get_element(key) + if elt is None: + raise KeyError + else: + return cast(KV, elt).value() + + def __setitem__(self, key: KT, value: VT) -> None: + elt = KV(key, value) + self.insert_element(elt, self.in_order) + + def __delitem__(self, key: KT) -> None: + if self.delete_key(key) is None: + raise KeyError + + +class Member(Element, Generic[KT]): + """The BTree element type used in a ``BTreeSet``.""" + + def __init__(self, key: KT): + self._key = key + + def key(self) -> KT: + return self._key + + +class BTreeSet(BTree, Generic[KT], MutableSet[KT]): + """A MutableSet implemented with a BTree. + + Unlike a normal Python set, the BTreeSet may be mutated while iterating. + """ + + def __init__( + self, + *, + t: int = DEFAULT_T, + original: BTree | None = None, + in_order: bool = False, + ): + super().__init__(t=t, original=original) + self.in_order = in_order + + def __contains__(self, key: Any) -> bool: + return self.get_element(key) is not None + + def add(self, value: KT) -> None: + elt = Member(value) + self.insert_element(elt, self.in_order) + + def discard(self, value: KT) -> None: + self.delete_key(value) diff --git a/venv/lib/python3.12/site-packages/dns/btreezone.py b/venv/lib/python3.12/site-packages/dns/btreezone.py new file mode 100644 index 0000000..27b5bb6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/btreezone.py @@ -0,0 +1,367 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# A derivative of a dnspython VersionedZone and related classes, using a BTreeDict and +# a separate per-version delegation index. These additions let us +# +# 1) Do efficient CoW versioning (useful for future online updates). +# 2) Maintain sort order +# 3) Allow delegations to be found easily +# 4) Handle glue +# 5) Add Node flags ORIGIN, DELEGATION, and GLUE whenever relevant. The ORIGIN +# flag is set at the origin node, the DELEGATION FLAG is set at delegation +# points, and the GLUE flag is set on nodes beneath delegation points. + +import enum +from dataclasses import dataclass +from typing import Callable, MutableMapping, Tuple, cast + +import dns.btree +import dns.immutable +import dns.name +import dns.node +import dns.rdataclass +import dns.rdataset +import dns.rdatatype +import dns.versioned +import dns.zone + + +class NodeFlags(enum.IntFlag): + ORIGIN = 0x01 + DELEGATION = 0x02 + GLUE = 0x04 + + +class Node(dns.node.Node): + __slots__ = ["flags", "id"] + + def __init__(self, flags: NodeFlags | None = None): + super().__init__() + if flags is None: + # We allow optional flags rather than a default + # as pyright doesn't like assigning a literal 0 + # to flags. + flags = NodeFlags(0) + self.flags = flags + self.id = 0 + + def is_delegation(self): + return (self.flags & NodeFlags.DELEGATION) != 0 + + def is_glue(self): + return (self.flags & NodeFlags.GLUE) != 0 + + def is_origin(self): + return (self.flags & NodeFlags.ORIGIN) != 0 + + def is_origin_or_glue(self): + return (self.flags & (NodeFlags.ORIGIN | NodeFlags.GLUE)) != 0 + + +@dns.immutable.immutable +class ImmutableNode(Node): + def __init__(self, node: Node): + super().__init__() + self.id = node.id + self.rdatasets = tuple( # type: ignore + [dns.rdataset.ImmutableRdataset(rds) for rds in node.rdatasets] + ) + self.flags = node.flags + + def find_rdataset( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + create: bool = False, + ) -> dns.rdataset.Rdataset: + if create: + raise TypeError("immutable") + return super().find_rdataset(rdclass, rdtype, covers, False) + + def get_rdataset( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + create: bool = False, + ) -> dns.rdataset.Rdataset | None: + if create: + raise TypeError("immutable") + return super().get_rdataset(rdclass, rdtype, covers, False) + + def delete_rdataset( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + ) -> None: + raise TypeError("immutable") + + def replace_rdataset(self, replacement: dns.rdataset.Rdataset) -> None: + raise TypeError("immutable") + + def is_immutable(self) -> bool: + return True + + +class Delegations(dns.btree.BTreeSet[dns.name.Name]): + def get_delegation(self, name: dns.name.Name) -> Tuple[dns.name.Name | None, bool]: + """Get the delegation applicable to *name*, if it exists. + + If there delegation, then return a tuple consisting of the name of + the delegation point, and a boolean which is `True` if the name is a proper + subdomain of the delegation point, and `False` if it is equal to the delegation + point. + """ + cursor = self.cursor() + cursor.seek(name, before=False) + prev = cursor.prev() + if prev is None: + return None, False + cut = prev.key() + reln, _, _ = name.fullcompare(cut) + is_subdomain = reln == dns.name.NameRelation.SUBDOMAIN + if is_subdomain or reln == dns.name.NameRelation.EQUAL: + return cut, is_subdomain + else: + return None, False + + def is_glue(self, name: dns.name.Name) -> bool: + """Is *name* glue, i.e. is it beneath a delegation?""" + cursor = self.cursor() + cursor.seek(name, before=False) + cut, is_subdomain = self.get_delegation(name) + if cut is None: + return False + return is_subdomain + + +class WritableVersion(dns.zone.WritableVersion): + def __init__(self, zone: dns.zone.Zone, replacement: bool = False): + super().__init__(zone, True) + if not replacement: + assert isinstance(zone, dns.versioned.Zone) + version = zone._versions[-1] + self.nodes: dns.btree.BTreeDict[dns.name.Name, Node] = dns.btree.BTreeDict( + original=version.nodes # type: ignore + ) + self.delegations = Delegations(original=version.delegations) # type: ignore + else: + self.delegations = Delegations() + + def _is_origin(self, name: dns.name.Name) -> bool: + # Assumes name has already been validated (and thus adjusted to the right + # relativity too) + if self.zone.relativize: + return name == dns.name.empty + else: + return name == self.zone.origin + + def _maybe_cow_with_name( + self, name: dns.name.Name + ) -> Tuple[dns.node.Node, dns.name.Name]: + (node, name) = super()._maybe_cow_with_name(name) + node = cast(Node, node) + if self._is_origin(name): + node.flags |= NodeFlags.ORIGIN + elif self.delegations.is_glue(name): + node.flags |= NodeFlags.GLUE + return (node, name) + + def update_glue_flag(self, name: dns.name.Name, is_glue: bool) -> None: + cursor = self.nodes.cursor() # type: ignore + cursor.seek(name, False) + updates = [] + while True: + elt = cursor.next() + if elt is None: + break + ename = elt.key() + if not ename.is_subdomain(name): + break + node = cast(dns.node.Node, elt.value()) + if ename not in self.changed: + new_node = self.zone.node_factory() + new_node.id = self.id # type: ignore + new_node.rdatasets.extend(node.rdatasets) + self.changed.add(ename) + node = new_node + assert isinstance(node, Node) + if is_glue: + node.flags |= NodeFlags.GLUE + else: + node.flags &= ~NodeFlags.GLUE + # We don't update node here as any insertion could disturb the + # btree and invalidate our cursor. We could use the cursor in a + # with block and avoid this, but it would do a lot of parking and + # unparking so the deferred update mode may still be better. + updates.append((ename, node)) + for ename, node in updates: + self.nodes[ename] = node + + def delete_node(self, name: dns.name.Name) -> None: + name = self._validate_name(name) + node = self.nodes.get(name) + if node is not None: + if node.is_delegation(): # type: ignore + self.delegations.discard(name) + self.update_glue_flag(name, False) + del self.nodes[name] + self.changed.add(name) + + def put_rdataset( + self, name: dns.name.Name, rdataset: dns.rdataset.Rdataset + ) -> None: + (node, name) = self._maybe_cow_with_name(name) + if ( + rdataset.rdtype == dns.rdatatype.NS and not node.is_origin_or_glue() # type: ignore + ): + node.flags |= NodeFlags.DELEGATION # type: ignore + if name not in self.delegations: + self.delegations.add(name) + self.update_glue_flag(name, True) + node.replace_rdataset(rdataset) + + def delete_rdataset( + self, + name: dns.name.Name, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType, + ) -> None: + (node, name) = self._maybe_cow_with_name(name) + if rdtype == dns.rdatatype.NS and name in self.delegations: # type: ignore + node.flags &= ~NodeFlags.DELEGATION # type: ignore + self.delegations.discard(name) # type: ignore + self.update_glue_flag(name, False) + node.delete_rdataset(self.zone.rdclass, rdtype, covers) + if len(node) == 0: + del self.nodes[name] + + +@dataclass(frozen=True) +class Bounds: + name: dns.name.Name + left: dns.name.Name + right: dns.name.Name | None + closest_encloser: dns.name.Name + is_equal: bool + is_delegation: bool + + def __str__(self): + if self.is_equal: + op = "=" + else: + op = "<" + if self.is_delegation: + zonecut = " zonecut" + else: + zonecut = "" + return ( + f"{self.left} {op} {self.name} < {self.right}{zonecut}; " + f"{self.closest_encloser}" + ) + + +@dns.immutable.immutable +class ImmutableVersion(dns.zone.Version): + def __init__(self, version: dns.zone.Version): + if not isinstance(version, WritableVersion): + raise ValueError( + "a dns.btreezone.ImmutableVersion requires a " + "dns.btreezone.WritableVersion" + ) + super().__init__(version.zone, True) + self.id = version.id + self.origin = version.origin + for name in version.changed: + node = version.nodes.get(name) + if node: + version.nodes[name] = ImmutableNode(node) + # the cast below is for mypy + self.nodes = cast(MutableMapping[dns.name.Name, dns.node.Node], version.nodes) + self.nodes.make_immutable() # type: ignore + self.delegations = version.delegations + self.delegations.make_immutable() + + def bounds(self, name: dns.name.Name | str) -> Bounds: + """Return the 'bounds' of *name* in its zone. + + The bounds information is useful when making an authoritative response, as + it can be used to determine whether the query name is at or beneath a delegation + point. The other data in the ``Bounds`` object is useful for making on-the-fly + DNSSEC signatures. + + The left bound of *name* is *name* itself if it is in the zone, or the greatest + predecessor which is in the zone. + + The right bound of *name* is the least successor of *name*, or ``None`` if + no name in the zone is greater than *name*. + + The closest encloser of *name* is *name* itself, if *name* is in the zone; + otherwise it is the name with the largest number of labels in common with + *name* that is in the zone, either explicitly or by the implied existence + of empty non-terminals. + + The bounds *is_equal* field is ``True`` if and only if *name* is equal to + its left bound. + + The bounds *is_delegation* field is ``True`` if and only if the left bound is a + delegation point. + """ + assert self.origin is not None + # validate the origin because we may need to relativize + origin = self.zone._validate_name(self.origin) + name = self.zone._validate_name(name) + cut, _ = self.delegations.get_delegation(name) + if cut is not None: + target = cut + is_delegation = True + else: + target = name + is_delegation = False + c = cast(dns.btree.BTreeDict, self.nodes).cursor() + c.seek(target, False) + left = c.prev() + assert left is not None + c.next() # skip over left + while True: + right = c.next() + if right is None or not right.value().is_glue(): + break + left_comparison = left.key().fullcompare(name) + if right is not None: + right_key = right.key() + right_comparison = right_key.fullcompare(name) + else: + right_comparison = ( + dns.name.NAMERELN_COMMONANCESTOR, + -1, + len(origin), + ) + right_key = None + closest_encloser = dns.name.Name( + name[-max(left_comparison[2], right_comparison[2]) :] + ) + return Bounds( + name, + left.key(), + right_key, + closest_encloser, + left_comparison[0] == dns.name.NameRelation.EQUAL, + is_delegation, + ) + + +class Zone(dns.versioned.Zone): + node_factory: Callable[[], dns.node.Node] = Node + map_factory: Callable[[], MutableMapping[dns.name.Name, dns.node.Node]] = cast( + Callable[[], MutableMapping[dns.name.Name, dns.node.Node]], + dns.btree.BTreeDict[dns.name.Name, Node], + ) + writable_version_factory: ( + Callable[[dns.zone.Zone, bool], dns.zone.Version] | None + ) = WritableVersion + immutable_version_factory: Callable[[dns.zone.Version], dns.zone.Version] | None = ( + ImmutableVersion + ) diff --git a/venv/lib/python3.12/site-packages/dns/dnssec.py b/venv/lib/python3.12/site-packages/dns/dnssec.py new file mode 100644 index 0000000..0b2aa70 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/dnssec.py @@ -0,0 +1,1242 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""Common DNSSEC-related functions and constants.""" + +# pylint: disable=unused-import + +import base64 +import contextlib +import functools +import hashlib +import struct +import time +from datetime import datetime +from typing import Callable, Dict, List, Set, Tuple, Union, cast + +import dns._features +import dns.name +import dns.node +import dns.rdata +import dns.rdataclass +import dns.rdataset +import dns.rdatatype +import dns.rrset +import dns.transaction +import dns.zone +from dns.dnssectypes import Algorithm, DSDigest, NSEC3Hash +from dns.exception import AlgorithmKeyMismatch as AlgorithmKeyMismatch +from dns.exception import DeniedByPolicy, UnsupportedAlgorithm, ValidationFailure +from dns.rdtypes.ANY.CDNSKEY import CDNSKEY +from dns.rdtypes.ANY.CDS import CDS +from dns.rdtypes.ANY.DNSKEY import DNSKEY +from dns.rdtypes.ANY.DS import DS +from dns.rdtypes.ANY.NSEC import NSEC, Bitmap +from dns.rdtypes.ANY.NSEC3PARAM import NSEC3PARAM +from dns.rdtypes.ANY.RRSIG import RRSIG, sigtime_to_posixtime +from dns.rdtypes.dnskeybase import Flag + +PublicKey = Union[ + "GenericPublicKey", + "rsa.RSAPublicKey", + "ec.EllipticCurvePublicKey", + "ed25519.Ed25519PublicKey", + "ed448.Ed448PublicKey", +] + +PrivateKey = Union[ + "GenericPrivateKey", + "rsa.RSAPrivateKey", + "ec.EllipticCurvePrivateKey", + "ed25519.Ed25519PrivateKey", + "ed448.Ed448PrivateKey", +] + +RRsetSigner = Callable[[dns.transaction.Transaction, dns.rrset.RRset], None] + + +def algorithm_from_text(text: str) -> Algorithm: + """Convert text into a DNSSEC algorithm value. + + *text*, a ``str``, the text to convert to into an algorithm value. + + Returns an ``int``. + """ + + return Algorithm.from_text(text) + + +def algorithm_to_text(value: Algorithm | int) -> str: + """Convert a DNSSEC algorithm value to text + + *value*, a ``dns.dnssec.Algorithm``. + + Returns a ``str``, the name of a DNSSEC algorithm. + """ + + return Algorithm.to_text(value) + + +def to_timestamp(value: datetime | str | float | int) -> int: + """Convert various format to a timestamp""" + if isinstance(value, datetime): + return int(value.timestamp()) + elif isinstance(value, str): + return sigtime_to_posixtime(value) + elif isinstance(value, float): + return int(value) + elif isinstance(value, int): + return value + else: + raise TypeError("Unsupported timestamp type") + + +def key_id(key: DNSKEY | CDNSKEY) -> int: + """Return the key id (a 16-bit number) for the specified key. + + *key*, a ``dns.rdtypes.ANY.DNSKEY.DNSKEY`` + + Returns an ``int`` between 0 and 65535 + """ + + rdata = key.to_wire() + assert rdata is not None # for mypy + if key.algorithm == Algorithm.RSAMD5: + return (rdata[-3] << 8) + rdata[-2] + else: + total = 0 + for i in range(len(rdata) // 2): + total += (rdata[2 * i] << 8) + rdata[2 * i + 1] + if len(rdata) % 2 != 0: + total += rdata[len(rdata) - 1] << 8 + total += (total >> 16) & 0xFFFF + return total & 0xFFFF + + +class Policy: + def __init__(self): + pass + + def ok_to_sign(self, key: DNSKEY) -> bool: # pragma: no cover + return False + + def ok_to_validate(self, key: DNSKEY) -> bool: # pragma: no cover + return False + + def ok_to_create_ds(self, algorithm: DSDigest) -> bool: # pragma: no cover + return False + + def ok_to_validate_ds(self, algorithm: DSDigest) -> bool: # pragma: no cover + return False + + +class SimpleDeny(Policy): + def __init__(self, deny_sign, deny_validate, deny_create_ds, deny_validate_ds): + super().__init__() + self._deny_sign = deny_sign + self._deny_validate = deny_validate + self._deny_create_ds = deny_create_ds + self._deny_validate_ds = deny_validate_ds + + def ok_to_sign(self, key: DNSKEY) -> bool: + return key.algorithm not in self._deny_sign + + def ok_to_validate(self, key: DNSKEY) -> bool: + return key.algorithm not in self._deny_validate + + def ok_to_create_ds(self, algorithm: DSDigest) -> bool: + return algorithm not in self._deny_create_ds + + def ok_to_validate_ds(self, algorithm: DSDigest) -> bool: + return algorithm not in self._deny_validate_ds + + +rfc_8624_policy = SimpleDeny( + {Algorithm.RSAMD5, Algorithm.DSA, Algorithm.DSANSEC3SHA1, Algorithm.ECCGOST}, + {Algorithm.RSAMD5, Algorithm.DSA, Algorithm.DSANSEC3SHA1}, + {DSDigest.NULL, DSDigest.SHA1, DSDigest.GOST}, + {DSDigest.NULL}, +) + +allow_all_policy = SimpleDeny(set(), set(), set(), set()) + + +default_policy = rfc_8624_policy + + +def make_ds( + name: dns.name.Name | str, + key: dns.rdata.Rdata, + algorithm: DSDigest | str, + origin: dns.name.Name | None = None, + policy: Policy | None = None, + validating: bool = False, +) -> DS: + """Create a DS record for a DNSSEC key. + + *name*, a ``dns.name.Name`` or ``str``, the owner name of the DS record. + + *key*, a ``dns.rdtypes.ANY.DNSKEY.DNSKEY`` or ``dns.rdtypes.ANY.DNSKEY.CDNSKEY``, + the key the DS is about. + + *algorithm*, a ``str`` or ``int`` specifying the hash algorithm. + The currently supported hashes are "SHA1", "SHA256", and "SHA384". Case + does not matter for these strings. + + *origin*, a ``dns.name.Name`` or ``None``. If *key* is a relative name, + then it will be made absolute using the specified origin. + + *policy*, a ``dns.dnssec.Policy`` or ``None``. If ``None``, the default policy, + ``dns.dnssec.default_policy`` is used; this policy defaults to that of RFC 8624. + + *validating*, a ``bool``. If ``True``, then policy is checked in + validating mode, i.e. "Is it ok to validate using this digest algorithm?". + Otherwise the policy is checked in creating mode, i.e. "Is it ok to create a DS with + this digest algorithm?". + + Raises ``UnsupportedAlgorithm`` if the algorithm is unknown. + + Raises ``DeniedByPolicy`` if the algorithm is denied by policy. + + Returns a ``dns.rdtypes.ANY.DS.DS`` + """ + + if policy is None: + policy = default_policy + try: + if isinstance(algorithm, str): + algorithm = DSDigest[algorithm.upper()] + except Exception: + raise UnsupportedAlgorithm(f'unsupported algorithm "{algorithm}"') + if validating: + check = policy.ok_to_validate_ds + else: + check = policy.ok_to_create_ds + if not check(algorithm): + raise DeniedByPolicy + if not isinstance(key, DNSKEY | CDNSKEY): + raise ValueError("key is not a DNSKEY | CDNSKEY") + if algorithm == DSDigest.SHA1: + dshash = hashlib.sha1() + elif algorithm == DSDigest.SHA256: + dshash = hashlib.sha256() + elif algorithm == DSDigest.SHA384: + dshash = hashlib.sha384() + else: + raise UnsupportedAlgorithm(f'unsupported algorithm "{algorithm}"') + + if isinstance(name, str): + name = dns.name.from_text(name, origin) + wire = name.canonicalize().to_wire() + kwire = key.to_wire(origin=origin) + assert wire is not None and kwire is not None # for mypy + dshash.update(wire) + dshash.update(kwire) + digest = dshash.digest() + + dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, algorithm) + digest + ds = dns.rdata.from_wire( + dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0, len(dsrdata) + ) + return cast(DS, ds) + + +def make_cds( + name: dns.name.Name | str, + key: dns.rdata.Rdata, + algorithm: DSDigest | str, + origin: dns.name.Name | None = None, +) -> CDS: + """Create a CDS record for a DNSSEC key. + + *name*, a ``dns.name.Name`` or ``str``, the owner name of the DS record. + + *key*, a ``dns.rdtypes.ANY.DNSKEY.DNSKEY`` or ``dns.rdtypes.ANY.DNSKEY.CDNSKEY``, + the key the DS is about. + + *algorithm*, a ``str`` or ``int`` specifying the hash algorithm. + The currently supported hashes are "SHA1", "SHA256", and "SHA384". Case + does not matter for these strings. + + *origin*, a ``dns.name.Name`` or ``None``. If *key* is a relative name, + then it will be made absolute using the specified origin. + + Raises ``UnsupportedAlgorithm`` if the algorithm is unknown. + + Returns a ``dns.rdtypes.ANY.DS.CDS`` + """ + + ds = make_ds(name, key, algorithm, origin) + return CDS( + rdclass=ds.rdclass, + rdtype=dns.rdatatype.CDS, + key_tag=ds.key_tag, + algorithm=ds.algorithm, + digest_type=ds.digest_type, + digest=ds.digest, + ) + + +def _find_candidate_keys( + keys: Dict[dns.name.Name, dns.rdataset.Rdataset | dns.node.Node], rrsig: RRSIG +) -> List[DNSKEY] | None: + value = keys.get(rrsig.signer) + if isinstance(value, dns.node.Node): + rdataset = value.get_rdataset(dns.rdataclass.IN, dns.rdatatype.DNSKEY) + else: + rdataset = value + if rdataset is None: + return None + return [ + cast(DNSKEY, rd) + for rd in rdataset + if rd.algorithm == rrsig.algorithm + and key_id(rd) == rrsig.key_tag + and (rd.flags & Flag.ZONE) == Flag.ZONE # RFC 4034 2.1.1 + and rd.protocol == 3 # RFC 4034 2.1.2 + ] + + +def _get_rrname_rdataset( + rrset: dns.rrset.RRset | Tuple[dns.name.Name, dns.rdataset.Rdataset], +) -> Tuple[dns.name.Name, dns.rdataset.Rdataset]: + if isinstance(rrset, tuple): + return rrset[0], rrset[1] + else: + return rrset.name, rrset + + +def _validate_signature(sig: bytes, data: bytes, key: DNSKEY) -> None: + # pylint: disable=possibly-used-before-assignment + public_cls = get_algorithm_cls_from_dnskey(key).public_cls + try: + public_key = public_cls.from_dnskey(key) + except ValueError: + raise ValidationFailure("invalid public key") + public_key.verify(sig, data) + + +def _validate_rrsig( + rrset: dns.rrset.RRset | Tuple[dns.name.Name, dns.rdataset.Rdataset], + rrsig: RRSIG, + keys: Dict[dns.name.Name, dns.node.Node | dns.rdataset.Rdataset], + origin: dns.name.Name | None = None, + now: float | None = None, + policy: Policy | None = None, +) -> None: + """Validate an RRset against a single signature rdata, throwing an + exception if validation is not successful. + + *rrset*, the RRset to validate. This can be a + ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) + tuple. + + *rrsig*, a ``dns.rdata.Rdata``, the signature to validate. + + *keys*, the key dictionary, used to find the DNSKEY associated + with a given name. The dictionary is keyed by a + ``dns.name.Name``, and has ``dns.node.Node`` or + ``dns.rdataset.Rdataset`` values. + + *origin*, a ``dns.name.Name`` or ``None``, the origin to use for relative + names. + + *now*, a ``float`` or ``None``, the time, in seconds since the epoch, to + use as the current time when validating. If ``None``, the actual current + time is used. + + *policy*, a ``dns.dnssec.Policy`` or ``None``. If ``None``, the default policy, + ``dns.dnssec.default_policy`` is used; this policy defaults to that of RFC 8624. + + Raises ``ValidationFailure`` if the signature is expired, not yet valid, + the public key is invalid, the algorithm is unknown, the verification + fails, etc. + + Raises ``UnsupportedAlgorithm`` if the algorithm is recognized by + dnspython but not implemented. + """ + + if policy is None: + policy = default_policy + + candidate_keys = _find_candidate_keys(keys, rrsig) + if candidate_keys is None: + raise ValidationFailure("unknown key") + + if now is None: + now = time.time() + if rrsig.expiration < now: + raise ValidationFailure("expired") + if rrsig.inception > now: + raise ValidationFailure("not yet valid") + + data = _make_rrsig_signature_data(rrset, rrsig, origin) + + # pylint: disable=possibly-used-before-assignment + for candidate_key in candidate_keys: + if not policy.ok_to_validate(candidate_key): + continue + try: + _validate_signature(rrsig.signature, data, candidate_key) + return + except (InvalidSignature, ValidationFailure): + # this happens on an individual validation failure + continue + # nothing verified -- raise failure: + raise ValidationFailure("verify failure") + + +def _validate( + rrset: dns.rrset.RRset | Tuple[dns.name.Name, dns.rdataset.Rdataset], + rrsigset: dns.rrset.RRset | Tuple[dns.name.Name, dns.rdataset.Rdataset], + keys: Dict[dns.name.Name, dns.node.Node | dns.rdataset.Rdataset], + origin: dns.name.Name | None = None, + now: float | None = None, + policy: Policy | None = None, +) -> None: + """Validate an RRset against a signature RRset, throwing an exception + if none of the signatures validate. + + *rrset*, the RRset to validate. This can be a + ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) + tuple. + + *rrsigset*, the signature RRset. This can be a + ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) + tuple. + + *keys*, the key dictionary, used to find the DNSKEY associated + with a given name. The dictionary is keyed by a + ``dns.name.Name``, and has ``dns.node.Node`` or + ``dns.rdataset.Rdataset`` values. + + *origin*, a ``dns.name.Name``, the origin to use for relative names; + defaults to None. + + *now*, an ``int`` or ``None``, the time, in seconds since the epoch, to + use as the current time when validating. If ``None``, the actual current + time is used. + + *policy*, a ``dns.dnssec.Policy`` or ``None``. If ``None``, the default policy, + ``dns.dnssec.default_policy`` is used; this policy defaults to that of RFC 8624. + + Raises ``ValidationFailure`` if the signature is expired, not yet valid, + the public key is invalid, the algorithm is unknown, the verification + fails, etc. + """ + + if policy is None: + policy = default_policy + + if isinstance(origin, str): + origin = dns.name.from_text(origin, dns.name.root) + + if isinstance(rrset, tuple): + rrname = rrset[0] + else: + rrname = rrset.name + + if isinstance(rrsigset, tuple): + rrsigname = rrsigset[0] + rrsigrdataset = rrsigset[1] + else: + rrsigname = rrsigset.name + rrsigrdataset = rrsigset + + rrname = rrname.choose_relativity(origin) + rrsigname = rrsigname.choose_relativity(origin) + if rrname != rrsigname: + raise ValidationFailure("owner names do not match") + + for rrsig in rrsigrdataset: + if not isinstance(rrsig, RRSIG): + raise ValidationFailure("expected an RRSIG") + try: + _validate_rrsig(rrset, rrsig, keys, origin, now, policy) + return + except (ValidationFailure, UnsupportedAlgorithm): + pass + raise ValidationFailure("no RRSIGs validated") + + +def _sign( + rrset: dns.rrset.RRset | Tuple[dns.name.Name, dns.rdataset.Rdataset], + private_key: PrivateKey, + signer: dns.name.Name, + dnskey: DNSKEY, + inception: datetime | str | int | float | None = None, + expiration: datetime | str | int | float | None = None, + lifetime: int | None = None, + verify: bool = False, + policy: Policy | None = None, + origin: dns.name.Name | None = None, + deterministic: bool = True, +) -> RRSIG: + """Sign RRset using private key. + + *rrset*, the RRset to validate. This can be a + ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) + tuple. + + *private_key*, the private key to use for signing, a + ``cryptography.hazmat.primitives.asymmetric`` private key class applicable + for DNSSEC. + + *signer*, a ``dns.name.Name``, the Signer's name. + + *dnskey*, a ``DNSKEY`` matching ``private_key``. + + *inception*, a ``datetime``, ``str``, ``int``, ``float`` or ``None``, the + signature inception time. If ``None``, the current time is used. If a ``str``, the + format is "YYYYMMDDHHMMSS" or alternatively the number of seconds since the UNIX + epoch in text form; this is the same the RRSIG rdata's text form. + Values of type `int` or `float` are interpreted as seconds since the UNIX epoch. + + *expiration*, a ``datetime``, ``str``, ``int``, ``float`` or ``None``, the signature + expiration time. If ``None``, the expiration time will be the inception time plus + the value of the *lifetime* parameter. See the description of *inception* above + for how the various parameter types are interpreted. + + *lifetime*, an ``int`` or ``None``, the signature lifetime in seconds. This + parameter is only meaningful if *expiration* is ``None``. + + *verify*, a ``bool``. If set to ``True``, the signer will verify signatures + after they are created; the default is ``False``. + + *policy*, a ``dns.dnssec.Policy`` or ``None``. If ``None``, the default policy, + ``dns.dnssec.default_policy`` is used; this policy defaults to that of RFC 8624. + + *origin*, a ``dns.name.Name`` or ``None``. If ``None``, the default, then all + names in the rrset (including its owner name) must be absolute; otherwise the + specified origin will be used to make names absolute when signing. + + *deterministic*, a ``bool``. If ``True``, the default, use deterministic + (reproducible) signatures when supported by the algorithm used for signing. + Currently, this only affects ECDSA. + + Raises ``DeniedByPolicy`` if the signature is denied by policy. + """ + + if policy is None: + policy = default_policy + if not policy.ok_to_sign(dnskey): + raise DeniedByPolicy + + if isinstance(rrset, tuple): + rdclass = rrset[1].rdclass + rdtype = rrset[1].rdtype + rrname = rrset[0] + original_ttl = rrset[1].ttl + else: + rdclass = rrset.rdclass + rdtype = rrset.rdtype + rrname = rrset.name + original_ttl = rrset.ttl + + if inception is not None: + rrsig_inception = to_timestamp(inception) + else: + rrsig_inception = int(time.time()) + + if expiration is not None: + rrsig_expiration = to_timestamp(expiration) + elif lifetime is not None: + rrsig_expiration = rrsig_inception + lifetime + else: + raise ValueError("expiration or lifetime must be specified") + + # Derelativize now because we need a correct labels length for the + # rrsig_template. + if origin is not None: + rrname = rrname.derelativize(origin) + labels = len(rrname) - 1 + + # Adjust labels appropriately for wildcards. + if rrname.is_wild(): + labels -= 1 + + rrsig_template = RRSIG( + rdclass=rdclass, + rdtype=dns.rdatatype.RRSIG, + type_covered=rdtype, + algorithm=dnskey.algorithm, + labels=labels, + original_ttl=original_ttl, + expiration=rrsig_expiration, + inception=rrsig_inception, + key_tag=key_id(dnskey), + signer=signer, + signature=b"", + ) + + data = _make_rrsig_signature_data(rrset, rrsig_template, origin) + + # pylint: disable=possibly-used-before-assignment + if isinstance(private_key, GenericPrivateKey): + signing_key = private_key + else: + try: + private_cls = get_algorithm_cls_from_dnskey(dnskey) + signing_key = private_cls(key=private_key) + except UnsupportedAlgorithm: + raise TypeError("Unsupported key algorithm") + + signature = signing_key.sign(data, verify, deterministic) + + return cast(RRSIG, rrsig_template.replace(signature=signature)) + + +def _make_rrsig_signature_data( + rrset: dns.rrset.RRset | Tuple[dns.name.Name, dns.rdataset.Rdataset], + rrsig: RRSIG, + origin: dns.name.Name | None = None, +) -> bytes: + """Create signature rdata. + + *rrset*, the RRset to sign/validate. This can be a + ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) + tuple. + + *rrsig*, a ``dns.rdata.Rdata``, the signature to validate, or the + signature template used when signing. + + *origin*, a ``dns.name.Name`` or ``None``, the origin to use for relative + names. + + Raises ``UnsupportedAlgorithm`` if the algorithm is recognized by + dnspython but not implemented. + """ + + if isinstance(origin, str): + origin = dns.name.from_text(origin, dns.name.root) + + signer = rrsig.signer + if not signer.is_absolute(): + if origin is None: + raise ValidationFailure("relative RR name without an origin specified") + signer = signer.derelativize(origin) + + # For convenience, allow the rrset to be specified as a (name, + # rdataset) tuple as well as a proper rrset + rrname, rdataset = _get_rrname_rdataset(rrset) + + data = b"" + wire = rrsig.to_wire(origin=signer) + assert wire is not None # for mypy + data += wire[:18] + data += rrsig.signer.to_digestable(signer) + + # Derelativize the name before considering labels. + if not rrname.is_absolute(): + if origin is None: + raise ValidationFailure("relative RR name without an origin specified") + rrname = rrname.derelativize(origin) + + name_len = len(rrname) + if rrname.is_wild() and rrsig.labels != name_len - 2: + raise ValidationFailure("wild owner name has wrong label length") + if name_len - 1 < rrsig.labels: + raise ValidationFailure("owner name longer than RRSIG labels") + elif rrsig.labels < name_len - 1: + suffix = rrname.split(rrsig.labels + 1)[1] + rrname = dns.name.from_text("*", suffix) + rrnamebuf = rrname.to_digestable() + rrfixed = struct.pack("!HHI", rdataset.rdtype, rdataset.rdclass, rrsig.original_ttl) + rdatas = [rdata.to_digestable(origin) for rdata in rdataset] + for rdata in sorted(rdatas): + data += rrnamebuf + data += rrfixed + rrlen = struct.pack("!H", len(rdata)) + data += rrlen + data += rdata + + return data + + +def _make_dnskey( + public_key: PublicKey, + algorithm: int | str, + flags: int = Flag.ZONE, + protocol: int = 3, +) -> DNSKEY: + """Convert a public key to DNSKEY Rdata + + *public_key*, a ``PublicKey`` (``GenericPublicKey`` or + ``cryptography.hazmat.primitives.asymmetric``) to convert. + + *algorithm*, a ``str`` or ``int`` specifying the DNSKEY algorithm. + + *flags*: DNSKEY flags field as an integer. + + *protocol*: DNSKEY protocol field as an integer. + + Raises ``ValueError`` if the specified key algorithm parameters are not + unsupported, ``TypeError`` if the key type is unsupported, + `UnsupportedAlgorithm` if the algorithm is unknown and + `AlgorithmKeyMismatch` if the algorithm does not match the key type. + + Return DNSKEY ``Rdata``. + """ + + algorithm = Algorithm.make(algorithm) + + # pylint: disable=possibly-used-before-assignment + if isinstance(public_key, GenericPublicKey): + return public_key.to_dnskey(flags=flags, protocol=protocol) + else: + public_cls = get_algorithm_cls(algorithm).public_cls + return public_cls(key=public_key).to_dnskey(flags=flags, protocol=protocol) + + +def _make_cdnskey( + public_key: PublicKey, + algorithm: int | str, + flags: int = Flag.ZONE, + protocol: int = 3, +) -> CDNSKEY: + """Convert a public key to CDNSKEY Rdata + + *public_key*, the public key to convert, a + ``cryptography.hazmat.primitives.asymmetric`` public key class applicable + for DNSSEC. + + *algorithm*, a ``str`` or ``int`` specifying the DNSKEY algorithm. + + *flags*: DNSKEY flags field as an integer. + + *protocol*: DNSKEY protocol field as an integer. + + Raises ``ValueError`` if the specified key algorithm parameters are not + unsupported, ``TypeError`` if the key type is unsupported, + `UnsupportedAlgorithm` if the algorithm is unknown and + `AlgorithmKeyMismatch` if the algorithm does not match the key type. + + Return CDNSKEY ``Rdata``. + """ + + dnskey = _make_dnskey(public_key, algorithm, flags, protocol) + + return CDNSKEY( + rdclass=dnskey.rdclass, + rdtype=dns.rdatatype.CDNSKEY, + flags=dnskey.flags, + protocol=dnskey.protocol, + algorithm=dnskey.algorithm, + key=dnskey.key, + ) + + +def nsec3_hash( + domain: dns.name.Name | str, + salt: str | bytes | None, + iterations: int, + algorithm: int | str, +) -> str: + """ + Calculate the NSEC3 hash, according to + https://tools.ietf.org/html/rfc5155#section-5 + + *domain*, a ``dns.name.Name`` or ``str``, the name to hash. + + *salt*, a ``str``, ``bytes``, or ``None``, the hash salt. If a + string, it is decoded as a hex string. + + *iterations*, an ``int``, the number of iterations. + + *algorithm*, a ``str`` or ``int``, the hash algorithm. + The only defined algorithm is SHA1. + + Returns a ``str``, the encoded NSEC3 hash. + """ + + b32_conversion = str.maketrans( + "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", "0123456789ABCDEFGHIJKLMNOPQRSTUV" + ) + + try: + if isinstance(algorithm, str): + algorithm = NSEC3Hash[algorithm.upper()] + except Exception: + raise ValueError("Wrong hash algorithm (only SHA1 is supported)") + + if algorithm != NSEC3Hash.SHA1: + raise ValueError("Wrong hash algorithm (only SHA1 is supported)") + + if salt is None: + salt_encoded = b"" + elif isinstance(salt, str): + if len(salt) % 2 == 0: + salt_encoded = bytes.fromhex(salt) + else: + raise ValueError("Invalid salt length") + else: + salt_encoded = salt + + if not isinstance(domain, dns.name.Name): + domain = dns.name.from_text(domain) + domain_encoded = domain.canonicalize().to_wire() + assert domain_encoded is not None + + digest = hashlib.sha1(domain_encoded + salt_encoded).digest() + for _ in range(iterations): + digest = hashlib.sha1(digest + salt_encoded).digest() + + output = base64.b32encode(digest).decode("utf-8") + output = output.translate(b32_conversion) + + return output + + +def make_ds_rdataset( + rrset: dns.rrset.RRset | Tuple[dns.name.Name, dns.rdataset.Rdataset], + algorithms: Set[DSDigest | str], + origin: dns.name.Name | None = None, +) -> dns.rdataset.Rdataset: + """Create a DS record from DNSKEY/CDNSKEY/CDS. + + *rrset*, the RRset to create DS Rdataset for. This can be a + ``dns.rrset.RRset`` or a (``dns.name.Name``, ``dns.rdataset.Rdataset``) + tuple. + + *algorithms*, a set of ``str`` or ``int`` specifying the hash algorithms. + The currently supported hashes are "SHA1", "SHA256", and "SHA384". Case + does not matter for these strings. If the RRset is a CDS, only digest + algorithms matching algorithms are accepted. + + *origin*, a ``dns.name.Name`` or ``None``. If `key` is a relative name, + then it will be made absolute using the specified origin. + + Raises ``UnsupportedAlgorithm`` if any of the algorithms are unknown and + ``ValueError`` if the given RRset is not usable. + + Returns a ``dns.rdataset.Rdataset`` + """ + + rrname, rdataset = _get_rrname_rdataset(rrset) + + if rdataset.rdtype not in ( + dns.rdatatype.DNSKEY, + dns.rdatatype.CDNSKEY, + dns.rdatatype.CDS, + ): + raise ValueError("rrset not a DNSKEY/CDNSKEY/CDS") + + _algorithms = set() + for algorithm in algorithms: + try: + if isinstance(algorithm, str): + algorithm = DSDigest[algorithm.upper()] + except Exception: + raise UnsupportedAlgorithm(f'unsupported algorithm "{algorithm}"') + _algorithms.add(algorithm) + + if rdataset.rdtype == dns.rdatatype.CDS: + res = [] + for rdata in cds_rdataset_to_ds_rdataset(rdataset): + if rdata.digest_type in _algorithms: + res.append(rdata) + if len(res) == 0: + raise ValueError("no acceptable CDS rdata found") + return dns.rdataset.from_rdata_list(rdataset.ttl, res) + + res = [] + for algorithm in _algorithms: + res.extend(dnskey_rdataset_to_cds_rdataset(rrname, rdataset, algorithm, origin)) + return dns.rdataset.from_rdata_list(rdataset.ttl, res) + + +def cds_rdataset_to_ds_rdataset( + rdataset: dns.rdataset.Rdataset, +) -> dns.rdataset.Rdataset: + """Create a CDS record from DS. + + *rdataset*, a ``dns.rdataset.Rdataset``, to create DS Rdataset for. + + Raises ``ValueError`` if the rdataset is not CDS. + + Returns a ``dns.rdataset.Rdataset`` + """ + + if rdataset.rdtype != dns.rdatatype.CDS: + raise ValueError("rdataset not a CDS") + res = [] + for rdata in rdataset: + res.append( + CDS( + rdclass=rdata.rdclass, + rdtype=dns.rdatatype.DS, + key_tag=rdata.key_tag, + algorithm=rdata.algorithm, + digest_type=rdata.digest_type, + digest=rdata.digest, + ) + ) + return dns.rdataset.from_rdata_list(rdataset.ttl, res) + + +def dnskey_rdataset_to_cds_rdataset( + name: dns.name.Name | str, + rdataset: dns.rdataset.Rdataset, + algorithm: DSDigest | str, + origin: dns.name.Name | None = None, +) -> dns.rdataset.Rdataset: + """Create a CDS record from DNSKEY/CDNSKEY. + + *name*, a ``dns.name.Name`` or ``str``, the owner name of the CDS record. + + *rdataset*, a ``dns.rdataset.Rdataset``, to create DS Rdataset for. + + *algorithm*, a ``str`` or ``int`` specifying the hash algorithm. + The currently supported hashes are "SHA1", "SHA256", and "SHA384". Case + does not matter for these strings. + + *origin*, a ``dns.name.Name`` or ``None``. If `key` is a relative name, + then it will be made absolute using the specified origin. + + Raises ``UnsupportedAlgorithm`` if the algorithm is unknown or + ``ValueError`` if the rdataset is not DNSKEY/CDNSKEY. + + Returns a ``dns.rdataset.Rdataset`` + """ + + if rdataset.rdtype not in (dns.rdatatype.DNSKEY, dns.rdatatype.CDNSKEY): + raise ValueError("rdataset not a DNSKEY/CDNSKEY") + res = [] + for rdata in rdataset: + res.append(make_cds(name, rdata, algorithm, origin)) + return dns.rdataset.from_rdata_list(rdataset.ttl, res) + + +def dnskey_rdataset_to_cdnskey_rdataset( + rdataset: dns.rdataset.Rdataset, +) -> dns.rdataset.Rdataset: + """Create a CDNSKEY record from DNSKEY. + + *rdataset*, a ``dns.rdataset.Rdataset``, to create CDNSKEY Rdataset for. + + Returns a ``dns.rdataset.Rdataset`` + """ + + if rdataset.rdtype != dns.rdatatype.DNSKEY: + raise ValueError("rdataset not a DNSKEY") + res = [] + for rdata in rdataset: + res.append( + CDNSKEY( + rdclass=rdataset.rdclass, + rdtype=rdataset.rdtype, + flags=rdata.flags, + protocol=rdata.protocol, + algorithm=rdata.algorithm, + key=rdata.key, + ) + ) + return dns.rdataset.from_rdata_list(rdataset.ttl, res) + + +def default_rrset_signer( + txn: dns.transaction.Transaction, + rrset: dns.rrset.RRset, + signer: dns.name.Name, + ksks: List[Tuple[PrivateKey, DNSKEY]], + zsks: List[Tuple[PrivateKey, DNSKEY]], + inception: datetime | str | int | float | None = None, + expiration: datetime | str | int | float | None = None, + lifetime: int | None = None, + policy: Policy | None = None, + origin: dns.name.Name | None = None, + deterministic: bool = True, +) -> None: + """Default RRset signer""" + + if rrset.rdtype in set( + [ + dns.rdatatype.RdataType.DNSKEY, + dns.rdatatype.RdataType.CDS, + dns.rdatatype.RdataType.CDNSKEY, + ] + ): + keys = ksks + else: + keys = zsks + + for private_key, dnskey in keys: + rrsig = sign( + rrset=rrset, + private_key=private_key, + dnskey=dnskey, + inception=inception, + expiration=expiration, + lifetime=lifetime, + signer=signer, + policy=policy, + origin=origin, + deterministic=deterministic, + ) + txn.add(rrset.name, rrset.ttl, rrsig) + + +def sign_zone( + zone: dns.zone.Zone, + txn: dns.transaction.Transaction | None = None, + keys: List[Tuple[PrivateKey, DNSKEY]] | None = None, + add_dnskey: bool = True, + dnskey_ttl: int | None = None, + inception: datetime | str | int | float | None = None, + expiration: datetime | str | int | float | None = None, + lifetime: int | None = None, + nsec3: NSEC3PARAM | None = None, + rrset_signer: RRsetSigner | None = None, + policy: Policy | None = None, + deterministic: bool = True, +) -> None: + """Sign zone. + + *zone*, a ``dns.zone.Zone``, the zone to sign. + + *txn*, a ``dns.transaction.Transaction``, an optional transaction to use for + signing. + + *keys*, a list of (``PrivateKey``, ``DNSKEY``) tuples, to use for signing. KSK/ZSK + roles are assigned automatically if the SEP flag is used, otherwise all RRsets are + signed by all keys. + + *add_dnskey*, a ``bool``. If ``True``, the default, all specified DNSKEYs are + automatically added to the zone on signing. + + *dnskey_ttl*, a``int``, specifies the TTL for DNSKEY RRs. If not specified the TTL + of the existing DNSKEY RRset used or the TTL of the SOA RRset. + + *inception*, a ``datetime``, ``str``, ``int``, ``float`` or ``None``, the signature + inception time. If ``None``, the current time is used. If a ``str``, the format is + "YYYYMMDDHHMMSS" or alternatively the number of seconds since the UNIX epoch in text + form; this is the same the RRSIG rdata's text form. Values of type `int` or `float` + are interpreted as seconds since the UNIX epoch. + + *expiration*, a ``datetime``, ``str``, ``int``, ``float`` or ``None``, the signature + expiration time. If ``None``, the expiration time will be the inception time plus + the value of the *lifetime* parameter. See the description of *inception* above for + how the various parameter types are interpreted. + + *lifetime*, an ``int`` or ``None``, the signature lifetime in seconds. This + parameter is only meaningful if *expiration* is ``None``. + + *nsec3*, a ``NSEC3PARAM`` Rdata, configures signing using NSEC3. Not yet + implemented. + + *rrset_signer*, a ``Callable``, an optional function for signing RRsets. The + function requires two arguments: transaction and RRset. If the not specified, + ``dns.dnssec.default_rrset_signer`` will be used. + + *deterministic*, a ``bool``. If ``True``, the default, use deterministic + (reproducible) signatures when supported by the algorithm used for signing. + Currently, this only affects ECDSA. + + Returns ``None``. + """ + + ksks = [] + zsks = [] + + # if we have both KSKs and ZSKs, split by SEP flag. if not, sign all + # records with all keys + if keys: + for key in keys: + if key[1].flags & Flag.SEP: + ksks.append(key) + else: + zsks.append(key) + if not ksks: + ksks = keys + if not zsks: + zsks = keys + else: + keys = [] + + if txn: + cm: contextlib.AbstractContextManager = contextlib.nullcontext(txn) + else: + cm = zone.writer() + + if zone.origin is None: + raise ValueError("no zone origin") + + with cm as _txn: + if add_dnskey: + if dnskey_ttl is None: + dnskey = _txn.get(zone.origin, dns.rdatatype.DNSKEY) + if dnskey: + dnskey_ttl = dnskey.ttl + else: + soa = _txn.get(zone.origin, dns.rdatatype.SOA) + dnskey_ttl = soa.ttl + for _, dnskey in keys: + _txn.add(zone.origin, dnskey_ttl, dnskey) + + if nsec3: + raise NotImplementedError("Signing with NSEC3 not yet implemented") + else: + _rrset_signer = rrset_signer or functools.partial( + default_rrset_signer, + signer=zone.origin, + ksks=ksks, + zsks=zsks, + inception=inception, + expiration=expiration, + lifetime=lifetime, + policy=policy, + origin=zone.origin, + deterministic=deterministic, + ) + return _sign_zone_nsec(zone, _txn, _rrset_signer) + + +def _sign_zone_nsec( + zone: dns.zone.Zone, + txn: dns.transaction.Transaction, + rrset_signer: RRsetSigner | None = None, +) -> None: + """NSEC zone signer""" + + def _txn_add_nsec( + txn: dns.transaction.Transaction, + name: dns.name.Name, + next_secure: dns.name.Name | None, + rdclass: dns.rdataclass.RdataClass, + ttl: int, + rrset_signer: RRsetSigner | None = None, + ) -> None: + """NSEC zone signer helper""" + mandatory_types = set( + [dns.rdatatype.RdataType.RRSIG, dns.rdatatype.RdataType.NSEC] + ) + node = txn.get_node(name) + if node and next_secure: + types = ( + set([rdataset.rdtype for rdataset in node.rdatasets]) | mandatory_types + ) + windows = Bitmap.from_rdtypes(list(types)) + rrset = dns.rrset.from_rdata( + name, + ttl, + NSEC( + rdclass=rdclass, + rdtype=dns.rdatatype.RdataType.NSEC, + next=next_secure, + windows=windows, + ), + ) + txn.add(rrset) + if rrset_signer: + rrset_signer(txn, rrset) + + rrsig_ttl = zone.get_soa(txn).minimum + delegation = None + last_secure = None + + for name in sorted(txn.iterate_names()): + if delegation and name.is_subdomain(delegation): + # names below delegations are not secure + continue + elif txn.get(name, dns.rdatatype.NS) and name != zone.origin: + # inside delegation + delegation = name + else: + # outside delegation + delegation = None + + if rrset_signer: + node = txn.get_node(name) + if node: + for rdataset in node.rdatasets: + if rdataset.rdtype == dns.rdatatype.RRSIG: + # do not sign RRSIGs + continue + elif delegation and rdataset.rdtype != dns.rdatatype.DS: + # do not sign delegations except DS records + continue + else: + rrset = dns.rrset.from_rdata(name, rdataset.ttl, *rdataset) + rrset_signer(txn, rrset) + + # We need "is not None" as the empty name is False because its length is 0. + if last_secure is not None: + _txn_add_nsec(txn, last_secure, name, zone.rdclass, rrsig_ttl, rrset_signer) + last_secure = name + + if last_secure: + _txn_add_nsec( + txn, last_secure, zone.origin, zone.rdclass, rrsig_ttl, rrset_signer + ) + + +def _need_pyca(*args, **kwargs): + raise ImportError( + "DNSSEC validation requires python cryptography" + ) # pragma: no cover + + +if dns._features.have("dnssec"): + from cryptography.exceptions import InvalidSignature + from cryptography.hazmat.primitives.asymmetric import ec # pylint: disable=W0611 + from cryptography.hazmat.primitives.asymmetric import ed448 # pylint: disable=W0611 + from cryptography.hazmat.primitives.asymmetric import rsa # pylint: disable=W0611 + from cryptography.hazmat.primitives.asymmetric import ( # pylint: disable=W0611 + ed25519, + ) + + from dns.dnssecalgs import ( # pylint: disable=C0412 + get_algorithm_cls, + get_algorithm_cls_from_dnskey, + ) + from dns.dnssecalgs.base import GenericPrivateKey, GenericPublicKey + + validate = _validate # type: ignore + validate_rrsig = _validate_rrsig # type: ignore + sign = _sign + make_dnskey = _make_dnskey + make_cdnskey = _make_cdnskey + _have_pyca = True +else: # pragma: no cover + validate = _need_pyca + validate_rrsig = _need_pyca + sign = _need_pyca + make_dnskey = _need_pyca + make_cdnskey = _need_pyca + _have_pyca = False + +### BEGIN generated Algorithm constants + +RSAMD5 = Algorithm.RSAMD5 +DH = Algorithm.DH +DSA = Algorithm.DSA +ECC = Algorithm.ECC +RSASHA1 = Algorithm.RSASHA1 +DSANSEC3SHA1 = Algorithm.DSANSEC3SHA1 +RSASHA1NSEC3SHA1 = Algorithm.RSASHA1NSEC3SHA1 +RSASHA256 = Algorithm.RSASHA256 +RSASHA512 = Algorithm.RSASHA512 +ECCGOST = Algorithm.ECCGOST +ECDSAP256SHA256 = Algorithm.ECDSAP256SHA256 +ECDSAP384SHA384 = Algorithm.ECDSAP384SHA384 +ED25519 = Algorithm.ED25519 +ED448 = Algorithm.ED448 +INDIRECT = Algorithm.INDIRECT +PRIVATEDNS = Algorithm.PRIVATEDNS +PRIVATEOID = Algorithm.PRIVATEOID + +### END generated Algorithm constants diff --git a/venv/lib/python3.12/site-packages/dns/dnssecalgs/__init__.py b/venv/lib/python3.12/site-packages/dns/dnssecalgs/__init__.py new file mode 100644 index 0000000..0810b19 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/dnssecalgs/__init__.py @@ -0,0 +1,124 @@ +from typing import Dict, Tuple, Type + +import dns._features +import dns.name +from dns.dnssecalgs.base import GenericPrivateKey +from dns.dnssectypes import Algorithm +from dns.exception import UnsupportedAlgorithm +from dns.rdtypes.ANY.DNSKEY import DNSKEY + +# pyright: reportPossiblyUnboundVariable=false + +if dns._features.have("dnssec"): + from dns.dnssecalgs.dsa import PrivateDSA, PrivateDSANSEC3SHA1 + from dns.dnssecalgs.ecdsa import PrivateECDSAP256SHA256, PrivateECDSAP384SHA384 + from dns.dnssecalgs.eddsa import PrivateED448, PrivateED25519 + from dns.dnssecalgs.rsa import ( + PrivateRSAMD5, + PrivateRSASHA1, + PrivateRSASHA1NSEC3SHA1, + PrivateRSASHA256, + PrivateRSASHA512, + ) + + _have_cryptography = True +else: + _have_cryptography = False + +AlgorithmPrefix = bytes | dns.name.Name | None + +algorithms: Dict[Tuple[Algorithm, AlgorithmPrefix], Type[GenericPrivateKey]] = {} +if _have_cryptography: + # pylint: disable=possibly-used-before-assignment + algorithms.update( + { + (Algorithm.RSAMD5, None): PrivateRSAMD5, + (Algorithm.DSA, None): PrivateDSA, + (Algorithm.RSASHA1, None): PrivateRSASHA1, + (Algorithm.DSANSEC3SHA1, None): PrivateDSANSEC3SHA1, + (Algorithm.RSASHA1NSEC3SHA1, None): PrivateRSASHA1NSEC3SHA1, + (Algorithm.RSASHA256, None): PrivateRSASHA256, + (Algorithm.RSASHA512, None): PrivateRSASHA512, + (Algorithm.ECDSAP256SHA256, None): PrivateECDSAP256SHA256, + (Algorithm.ECDSAP384SHA384, None): PrivateECDSAP384SHA384, + (Algorithm.ED25519, None): PrivateED25519, + (Algorithm.ED448, None): PrivateED448, + } + ) + + +def get_algorithm_cls( + algorithm: int | str, prefix: AlgorithmPrefix = None +) -> Type[GenericPrivateKey]: + """Get Private Key class from Algorithm. + + *algorithm*, a ``str`` or ``int`` specifying the DNSKEY algorithm. + + Raises ``UnsupportedAlgorithm`` if the algorithm is unknown. + + Returns a ``dns.dnssecalgs.GenericPrivateKey`` + """ + algorithm = Algorithm.make(algorithm) + cls = algorithms.get((algorithm, prefix)) + if cls: + return cls + raise UnsupportedAlgorithm( + f'algorithm "{Algorithm.to_text(algorithm)}" not supported by dnspython' + ) + + +def get_algorithm_cls_from_dnskey(dnskey: DNSKEY) -> Type[GenericPrivateKey]: + """Get Private Key class from DNSKEY. + + *dnskey*, a ``DNSKEY`` to get Algorithm class for. + + Raises ``UnsupportedAlgorithm`` if the algorithm is unknown. + + Returns a ``dns.dnssecalgs.GenericPrivateKey`` + """ + prefix: AlgorithmPrefix = None + if dnskey.algorithm == Algorithm.PRIVATEDNS: + prefix, _ = dns.name.from_wire(dnskey.key, 0) + elif dnskey.algorithm == Algorithm.PRIVATEOID: + length = int(dnskey.key[0]) + prefix = dnskey.key[0 : length + 1] + return get_algorithm_cls(dnskey.algorithm, prefix) + + +def register_algorithm_cls( + algorithm: int | str, + algorithm_cls: Type[GenericPrivateKey], + name: dns.name.Name | str | None = None, + oid: bytes | None = None, +) -> None: + """Register Algorithm Private Key class. + + *algorithm*, a ``str`` or ``int`` specifying the DNSKEY algorithm. + + *algorithm_cls*: A `GenericPrivateKey` class. + + *name*, an optional ``dns.name.Name`` or ``str``, for for PRIVATEDNS algorithms. + + *oid*: an optional BER-encoded `bytes` for PRIVATEOID algorithms. + + Raises ``ValueError`` if a name or oid is specified incorrectly. + """ + if not issubclass(algorithm_cls, GenericPrivateKey): + raise TypeError("Invalid algorithm class") + algorithm = Algorithm.make(algorithm) + prefix: AlgorithmPrefix = None + if algorithm == Algorithm.PRIVATEDNS: + if name is None: + raise ValueError("Name required for PRIVATEDNS algorithms") + if isinstance(name, str): + name = dns.name.from_text(name) + prefix = name + elif algorithm == Algorithm.PRIVATEOID: + if oid is None: + raise ValueError("OID required for PRIVATEOID algorithms") + prefix = bytes([len(oid)]) + oid + elif name: + raise ValueError("Name only supported for PRIVATEDNS algorithm") + elif oid: + raise ValueError("OID only supported for PRIVATEOID algorithm") + algorithms[(algorithm, prefix)] = algorithm_cls diff --git a/venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ade2bf7fdc1a36cf5fcd893420def7a2e890e483 GIT binary patch literal 5474 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 WiS>*?@$4tUgs`wJ{hL6+WAh&({TK8A literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/cryptography.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/cryptography.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..841feff624221b34f98deb3862bf776fa9ec2479 GIT binary patch literal 3789 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/ecdsa.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/ecdsa.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be66e00b9ff6e624d58c2fdd384e5f294508fe0a GIT binary patch literal 6083 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/eddsa.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/eddsa.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12d9934709ee33f84dc9049d1f17086de429a7b0 GIT binary patch literal 4188 zcmcgv&2JmW6`$oU$z6UeDT$HXS~4ZembbD=BcgLV;L9RuEA46?~SM z6}c`JB+H-mTY+qVx1~bRlC!d3(^gLLId!%j<=FMR)S0qULk`)SRB4af8&ncH_p?AAseR+pN6qlvbF&zFK~ZD0_sGtYDI?&lIR= z`rr{wX*po}A4yrs6sfrEGXtReX}}E9AiO2Zk|{rup10g&<+7wHZul)ehq>9=#apgo z=U0llv%#n% zE&x*Ey8ugI6A)c4YoaUJw6M&g(2K=D*ytyfN`3kB%xcM^8KYEKDrbyU-Eqp9O#g%vwPP1DxX>t)slUMqcFYfpO< zh~Os&Kx~t`9C;!?mY=AP)en{Z=|9VtnqVh}fU;FOQ2Y)I-vkN=NAj}P-q*GP>NUBm z(Neoc@O(Vkqt}Sv3vI9$iRPUtXXMW)z4UtDnm%Oz&%juv1E_^KQ6#^;vG4z(FB#Fe>k>?_{Wf8|CzVDEUt1lA9I zY{V_a5L-9SUDF)Q!1Gc$+r1U1->NwLM#eBqqUiP)tP&>nYXJmZ#P5|ztAMUc&I^qN{A(4JfFYOia=-+f&|86c?%T2f!*7S zJ)mp}&s$ccq%Dm2ZXaGJveqWUR@)odYkS+?V1rtI7s)fXCQ?Xc-x1;!!Sj; z2!mq34&o7M_yoVU?{5&Oo<88!M@|!4jez72?L#0y)rgQlqTvJW!{r7Vo(_M3RZ|4} z=VW35|E8hxw;4^NovCeN5TqOc&Cf~X7!j6+dGKSx#`(et(F$;O1)7yaH>PQHnF6!@Du`{;h>_%( zy}P@2_ukljV}JZ`VB)vRr{P3B5`XgM<2Mf@!?nnGH8Q^c@FVF(V1@Hw=%P=CiCIaWSYf;OW$gML z5ZmPMYTu5%_x|qtwZX~i;N;=p)C)CzAgB3C;NZP;AHIcw#Bh)nW*1Puh++Z-h7->- zSIy=0VzK0K=CN}*b_rg&(o(5Z;Ak@qyW*_a_fT-E<++NUxqD1*`#y-@ka~Pz=NHxZ z=%F;)2$7-T`pDSlO2~iavsiz_4|O9z63IpoHJJ>ZZQwiT7*d;I)P^8ezkqblw6Yd7 z?}PX)1D;O71z>5u1L9=X_Bt-xIZVY~Z?gd!T#IvmCqz~$`{33RZ4)WK|M5s(&_bsn z#iQPh&v}`)rxFiccq697YETUuUndxDCr-DDdfo1pqKoRYne_5e~rl zYV16Q#o2aPVCMyGzzsO%bvU*-|G6VZ*WbAGs$PM6a2k#?EAQkt+qYls>pP`&yKKRg zhQYy&Yhs)Rz6)2dG(4@pCh2Y;43Alce*#?fxEst1zQ`%!6|^#wThg}{9;Utji{6bX zf`d8p^2Fgu!m;AGoMh-P<(uBs>?dph5GQ$&HsQt H%v15dsj5@z literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/rsa.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/dnssecalgs/__pycache__/rsa.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a4c89bf3f0364c95f1d2ebaf562e0dcc138fc93 GIT binary patch literal 7249 zcmcf`S!^4}b#}QVmp7^V7A4CE9ZQOQq;~8kkbK0JYT1tD)F#p}6n7<2=9Sr%8wxU@ z6fPj;$5K;3G7!KrP}DMPAj((#8T2b?f5b3_*p1z$Xp6vqN~Heer+sghOHm4K!$r~o z_RY+jH#2YEHE+m2xm*zT*A^*URQOxcj ziPwo_lO6X=`rbh=!`cDsknIJmk71pFb;;fWwv1ujfc40}0=ArCy@2%<(N-{Q8DPtc zXe$}E0Wa0c>p%Z4JZL0k*z~ww7TV0NYqZTgR|XfNd_Ktxt0S z|6@FUe}L0Gk~|z9kEx-daO8@dkN{;F4J)Iv65us!DlAFS#E531N*MZ8$7wpHCP!#E zHJUz8qgTVK+$X2C$}RZup;$BmXjsbD9UDo~s5%5P}ZL zBHM>~$p&qw?2!HV2~t=MYZfX)FVP>#n;%;iWq|$lcLB_kNixRJTftcIjQr3fH_5Ap z6Ol1PmPHc{zG2N0(!3|=q#Q|;0M{QdQx_mK%T<|1htm{S*2MFD#|OHub{(P~=mt!h zpvbXd>V*#V0Z@=Hzu*5|Z|7(-E_X(fv7vNlWHhX*>CUTi;%a9sI@Fm;tE0)pfsU^I zok~=d+f!g{;SpKsloISy#+DNTq27tU2K=2)RRNs2sQ34Pc7oMCT20M!eyf z@hsRD58kg{4IIe?j${Kz9}KSk^lav*Xa6kruLCKGKxioxJQ0)hCI0y&ZOHK&_jGxy z1u2g?3cD5>liZlI&?z+Tw zvx@3srIcq#mP!5|NCnFTeAk3T)iY*TH)mWEfFIP_wo*z8hAbuV3hbve{tq*4>`V@| z+SpFA8*=*_bsCl;50_HBn85kOf7vC@LL~sG5L2t8od<{zf=9E}x_{Ng-lX zA@h7Hp@|{+-4w2%nV_NBu{SD*;RFi09fSzKpQ6Gvugs(q0-@-8Ow?_<`t~tgUwH&z zihSkr-MBP!Y1P%1akXV#yQcYv<<;{?=8k+gaOz4+X4`*ABWonPj z^!~H9`BweS`o+Pe!`a${)4e%|>&C&EgEwBCc{S^3SWrH9v@Q}>W0(fyCVND2tcfs>2N6bJf$9hZS}6XXnl?wk=tf-dXl7IkFW8XKd3|IbZbxx4_TVO#ZOOWJ9^hmywCl$yJlwW!{%)5 z-mLqD4Xfa_uMxp!XZ{xOLeL5&!f`ni((Iv7JSmOGun6Z| zgLs`QWGDh(p%|XKejQLhMEV{SB|d1gLHLs#k_i%YL3Ti*t|#ZBWW8=?$+=ia&LLel z{1lRNd%&sL^A8iy7;}RtM>suKWzyGNlB~)!9!*3QH5#G#N{FDwj8aGvR{-;T>G;zl zq`_zDWV}@DDGBis=ATLEqe0pb=n(KP^%692dY?>avuDEcez zDH+Ovj)17g69Jzy>{&o7feXzDK4|zDo9R&mm`ddn1NK7DZN49e@YS#_8=&U_H{sdT zSqE-{3n{Mym?CR#Qdv8HdhRsFm7LT2+uf^Wt(mgcY+2hPyMtGEb!T>UgSS`Or)}R@ zh_ei+Zk(GrmvuI-I@>bNw#CcK!WYg%;3F$y4YOoTM2A(P6Y`cz7c()8qU#cXdC0Qh za>CfoN~!A=R8nj@dCEyn#oUV(Lp}F7Lz(yaqHDy|4^JvjK8QogluXD8mEy@YYyL$} zUxHph(Bn|Wh@6lqJi9}A#o=9|RU41^?F->y0zAbhFI!YWV=(eC(O7Guyh!{8@(fTa zodBlD-}3=?PsY0^=ijmFKbr9$&DFL%tl6Ed2|!f!xi?G}46T-YXzdU1rv?LF+JhS( zM{oiGyVYD6N(;Xo&{H_@8UhyXE@12D2#^6y7)mB%%tmN0BJl#Gmk{(Jz_dw^m1nVq z;1GPpv9c~>tIG;?ELJvW+|3XxeRWg4xuzW}Lj9VZ)HLRrTEDS5&9z^-%h$|MuUUw% za?Oe@k<>J-;TRCwJ?jo^HLzG|HQN_WYXqt#=eJnpVPmna%By(yXN=K?fqcidwu#rk$B5N4UbtzcP~C@P+%;( zHm!~EY~Ic@IZ+k#SW1gXC;_reG5hSZUG%UPc#b@!&!b+*4JcE&_0 zu@5762#}&6_9jE@II@Km9uQc5SWBy-g1Wd-(HP4{m=P0SlLmn^Fq7f+Ps=D=1{Qp%BDw;pxXw zh^JT~`wzYJBu}vEngEJT6=)XF4Pr`pLmZEXAPL`kQ4HFV@$Nt^G=n#^&$WNhX?Oz? zZ#nU^wBX&M1;+gM#Lw6$C4P$hAyAP&BMkWs@#DxAX8sT2Kh(AV8R7?uZQ@5vDe;fv z@ette(KLmC-Lr_=a<1nSGGp<3LS~zl5;8t_D1vL)P!KRK5EW>kZHQn`=7vbopAsFT z;R*O@t0SK~DeO_KHI*Vs(P%uXMz6|>ekBH;PSg=r(s6jMQFt@p6G2aA>fycI0bl+H z?2h6!C|U*;0l>d31u`|Af*sklT!mOFF-%8y|K$$-4-)+~$g-icxB|Nl-^SJu0v3%~ zXkcN0St+wfCQ2r@KBOz;M&F6Y^r!7BbP5JgYsxZ!H512iUlPZc#Pu(7_z~&)8|nF) zTzo`!J|ck)%TCU|;@!DHpx!voH9*g|dxJo|@e0p1&mP$zP;J;bu4%S+gFv+*@|=J6 u4C4KWZ=MYzzM0_{E*J277RC_22l1`5QN*_*eaq|w;#*4K|BZNdqW=N{@uw~T literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/dnssecalgs/base.py b/venv/lib/python3.12/site-packages/dns/dnssecalgs/base.py new file mode 100644 index 0000000..0334fe6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/dnssecalgs/base.py @@ -0,0 +1,89 @@ +from abc import ABC, abstractmethod # pylint: disable=no-name-in-module +from typing import Any, Type + +import dns.rdataclass +import dns.rdatatype +from dns.dnssectypes import Algorithm +from dns.exception import AlgorithmKeyMismatch +from dns.rdtypes.ANY.DNSKEY import DNSKEY +from dns.rdtypes.dnskeybase import Flag + + +class GenericPublicKey(ABC): + algorithm: Algorithm + + @abstractmethod + def __init__(self, key: Any) -> None: + pass + + @abstractmethod + def verify(self, signature: bytes, data: bytes) -> None: + """Verify signed DNSSEC data""" + + @abstractmethod + def encode_key_bytes(self) -> bytes: + """Encode key as bytes for DNSKEY""" + + @classmethod + def _ensure_algorithm_key_combination(cls, key: DNSKEY) -> None: + if key.algorithm != cls.algorithm: + raise AlgorithmKeyMismatch + + def to_dnskey(self, flags: int = Flag.ZONE, protocol: int = 3) -> DNSKEY: + """Return public key as DNSKEY""" + return DNSKEY( + rdclass=dns.rdataclass.IN, + rdtype=dns.rdatatype.DNSKEY, + flags=flags, + protocol=protocol, + algorithm=self.algorithm, + key=self.encode_key_bytes(), + ) + + @classmethod + @abstractmethod + def from_dnskey(cls, key: DNSKEY) -> "GenericPublicKey": + """Create public key from DNSKEY""" + + @classmethod + @abstractmethod + def from_pem(cls, public_pem: bytes) -> "GenericPublicKey": + """Create public key from PEM-encoded SubjectPublicKeyInfo as specified + in RFC 5280""" + + @abstractmethod + def to_pem(self) -> bytes: + """Return public-key as PEM-encoded SubjectPublicKeyInfo as specified + in RFC 5280""" + + +class GenericPrivateKey(ABC): + public_cls: Type[GenericPublicKey] + + @abstractmethod + def __init__(self, key: Any) -> None: + pass + + @abstractmethod + def sign( + self, + data: bytes, + verify: bool = False, + deterministic: bool = True, + ) -> bytes: + """Sign DNSSEC data""" + + @abstractmethod + def public_key(self) -> "GenericPublicKey": + """Return public key instance""" + + @classmethod + @abstractmethod + def from_pem( + cls, private_pem: bytes, password: bytes | None = None + ) -> "GenericPrivateKey": + """Create private key from PEM-encoded PKCS#8""" + + @abstractmethod + def to_pem(self, password: bytes | None = None) -> bytes: + """Return private key as PEM-encoded PKCS#8""" diff --git a/venv/lib/python3.12/site-packages/dns/dnssecalgs/cryptography.py b/venv/lib/python3.12/site-packages/dns/dnssecalgs/cryptography.py new file mode 100644 index 0000000..a5dde6a --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/dnssecalgs/cryptography.py @@ -0,0 +1,68 @@ +from typing import Any, Type + +from cryptography.hazmat.primitives import serialization + +from dns.dnssecalgs.base import GenericPrivateKey, GenericPublicKey +from dns.exception import AlgorithmKeyMismatch + + +class CryptographyPublicKey(GenericPublicKey): + key: Any = None + key_cls: Any = None + + def __init__(self, key: Any) -> None: # pylint: disable=super-init-not-called + if self.key_cls is None: + raise TypeError("Undefined private key class") + if not isinstance( # pylint: disable=isinstance-second-argument-not-valid-type + key, self.key_cls + ): + raise AlgorithmKeyMismatch + self.key = key + + @classmethod + def from_pem(cls, public_pem: bytes) -> "GenericPublicKey": + key = serialization.load_pem_public_key(public_pem) + return cls(key=key) + + def to_pem(self) -> bytes: + return self.key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + + +class CryptographyPrivateKey(GenericPrivateKey): + key: Any = None + key_cls: Any = None + public_cls: Type[CryptographyPublicKey] # pyright: ignore + + def __init__(self, key: Any) -> None: # pylint: disable=super-init-not-called + if self.key_cls is None: + raise TypeError("Undefined private key class") + if not isinstance( # pylint: disable=isinstance-second-argument-not-valid-type + key, self.key_cls + ): + raise AlgorithmKeyMismatch + self.key = key + + def public_key(self) -> "CryptographyPublicKey": + return self.public_cls(key=self.key.public_key()) + + @classmethod + def from_pem( + cls, private_pem: bytes, password: bytes | None = None + ) -> "GenericPrivateKey": + key = serialization.load_pem_private_key(private_pem, password=password) + return cls(key=key) + + def to_pem(self, password: bytes | None = None) -> bytes: + encryption_algorithm: serialization.KeySerializationEncryption + if password: + encryption_algorithm = serialization.BestAvailableEncryption(password) + else: + encryption_algorithm = serialization.NoEncryption() + return self.key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=encryption_algorithm, + ) diff --git a/venv/lib/python3.12/site-packages/dns/dnssecalgs/dsa.py b/venv/lib/python3.12/site-packages/dns/dnssecalgs/dsa.py new file mode 100644 index 0000000..a4eb987 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/dnssecalgs/dsa.py @@ -0,0 +1,108 @@ +import struct + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import dsa, utils + +from dns.dnssecalgs.cryptography import CryptographyPrivateKey, CryptographyPublicKey +from dns.dnssectypes import Algorithm +from dns.rdtypes.ANY.DNSKEY import DNSKEY + + +class PublicDSA(CryptographyPublicKey): + key: dsa.DSAPublicKey + key_cls = dsa.DSAPublicKey + algorithm = Algorithm.DSA + chosen_hash = hashes.SHA1() + + def verify(self, signature: bytes, data: bytes) -> None: + sig_r = signature[1:21] + sig_s = signature[21:] + sig = utils.encode_dss_signature( + int.from_bytes(sig_r, "big"), int.from_bytes(sig_s, "big") + ) + self.key.verify(sig, data, self.chosen_hash) + + def encode_key_bytes(self) -> bytes: + """Encode a public key per RFC 2536, section 2.""" + pn = self.key.public_numbers() + dsa_t = (self.key.key_size // 8 - 64) // 8 + if dsa_t > 8: + raise ValueError("unsupported DSA key size") + octets = 64 + dsa_t * 8 + res = struct.pack("!B", dsa_t) + res += pn.parameter_numbers.q.to_bytes(20, "big") + res += pn.parameter_numbers.p.to_bytes(octets, "big") + res += pn.parameter_numbers.g.to_bytes(octets, "big") + res += pn.y.to_bytes(octets, "big") + return res + + @classmethod + def from_dnskey(cls, key: DNSKEY) -> "PublicDSA": + cls._ensure_algorithm_key_combination(key) + keyptr = key.key + (t,) = struct.unpack("!B", keyptr[0:1]) + keyptr = keyptr[1:] + octets = 64 + t * 8 + dsa_q = keyptr[0:20] + keyptr = keyptr[20:] + dsa_p = keyptr[0:octets] + keyptr = keyptr[octets:] + dsa_g = keyptr[0:octets] + keyptr = keyptr[octets:] + dsa_y = keyptr[0:octets] + return cls( + key=dsa.DSAPublicNumbers( # type: ignore + int.from_bytes(dsa_y, "big"), + dsa.DSAParameterNumbers( + int.from_bytes(dsa_p, "big"), + int.from_bytes(dsa_q, "big"), + int.from_bytes(dsa_g, "big"), + ), + ).public_key(default_backend()), + ) + + +class PrivateDSA(CryptographyPrivateKey): + key: dsa.DSAPrivateKey + key_cls = dsa.DSAPrivateKey + public_cls = PublicDSA + + def sign( + self, + data: bytes, + verify: bool = False, + deterministic: bool = True, + ) -> bytes: + """Sign using a private key per RFC 2536, section 3.""" + public_dsa_key = self.key.public_key() + if public_dsa_key.key_size > 1024: + raise ValueError("DSA key size overflow") + der_signature = self.key.sign( + data, self.public_cls.chosen_hash # pyright: ignore + ) + dsa_r, dsa_s = utils.decode_dss_signature(der_signature) + dsa_t = (public_dsa_key.key_size // 8 - 64) // 8 + octets = 20 + signature = ( + struct.pack("!B", dsa_t) + + int.to_bytes(dsa_r, length=octets, byteorder="big") + + int.to_bytes(dsa_s, length=octets, byteorder="big") + ) + if verify: + self.public_key().verify(signature, data) + return signature + + @classmethod + def generate(cls, key_size: int) -> "PrivateDSA": + return cls( + key=dsa.generate_private_key(key_size=key_size), + ) + + +class PublicDSANSEC3SHA1(PublicDSA): + algorithm = Algorithm.DSANSEC3SHA1 + + +class PrivateDSANSEC3SHA1(PrivateDSA): + public_cls = PublicDSANSEC3SHA1 diff --git a/venv/lib/python3.12/site-packages/dns/dnssecalgs/ecdsa.py b/venv/lib/python3.12/site-packages/dns/dnssecalgs/ecdsa.py new file mode 100644 index 0000000..e3f3f06 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/dnssecalgs/ecdsa.py @@ -0,0 +1,100 @@ +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec, utils + +from dns.dnssecalgs.cryptography import CryptographyPrivateKey, CryptographyPublicKey +from dns.dnssectypes import Algorithm +from dns.rdtypes.ANY.DNSKEY import DNSKEY + + +class PublicECDSA(CryptographyPublicKey): + key: ec.EllipticCurvePublicKey + key_cls = ec.EllipticCurvePublicKey + algorithm: Algorithm + chosen_hash: hashes.HashAlgorithm + curve: ec.EllipticCurve + octets: int + + def verify(self, signature: bytes, data: bytes) -> None: + sig_r = signature[0 : self.octets] + sig_s = signature[self.octets :] + sig = utils.encode_dss_signature( + int.from_bytes(sig_r, "big"), int.from_bytes(sig_s, "big") + ) + self.key.verify(sig, data, ec.ECDSA(self.chosen_hash)) + + def encode_key_bytes(self) -> bytes: + """Encode a public key per RFC 6605, section 4.""" + pn = self.key.public_numbers() + return pn.x.to_bytes(self.octets, "big") + pn.y.to_bytes(self.octets, "big") + + @classmethod + def from_dnskey(cls, key: DNSKEY) -> "PublicECDSA": + cls._ensure_algorithm_key_combination(key) + ecdsa_x = key.key[0 : cls.octets] + ecdsa_y = key.key[cls.octets : cls.octets * 2] + return cls( + key=ec.EllipticCurvePublicNumbers( + curve=cls.curve, + x=int.from_bytes(ecdsa_x, "big"), + y=int.from_bytes(ecdsa_y, "big"), + ).public_key(default_backend()), + ) + + +class PrivateECDSA(CryptographyPrivateKey): + key: ec.EllipticCurvePrivateKey + key_cls = ec.EllipticCurvePrivateKey + public_cls = PublicECDSA + + def sign( + self, + data: bytes, + verify: bool = False, + deterministic: bool = True, + ) -> bytes: + """Sign using a private key per RFC 6605, section 4.""" + algorithm = ec.ECDSA( + self.public_cls.chosen_hash, # pyright: ignore + deterministic_signing=deterministic, + ) + der_signature = self.key.sign(data, algorithm) + dsa_r, dsa_s = utils.decode_dss_signature(der_signature) + signature = int.to_bytes( + dsa_r, length=self.public_cls.octets, byteorder="big" # pyright: ignore + ) + int.to_bytes( + dsa_s, length=self.public_cls.octets, byteorder="big" # pyright: ignore + ) + if verify: + self.public_key().verify(signature, data) + return signature + + @classmethod + def generate(cls) -> "PrivateECDSA": + return cls( + key=ec.generate_private_key( + curve=cls.public_cls.curve, backend=default_backend() # pyright: ignore + ), + ) + + +class PublicECDSAP256SHA256(PublicECDSA): + algorithm = Algorithm.ECDSAP256SHA256 + chosen_hash = hashes.SHA256() + curve = ec.SECP256R1() + octets = 32 + + +class PrivateECDSAP256SHA256(PrivateECDSA): + public_cls = PublicECDSAP256SHA256 + + +class PublicECDSAP384SHA384(PublicECDSA): + algorithm = Algorithm.ECDSAP384SHA384 + chosen_hash = hashes.SHA384() + curve = ec.SECP384R1() + octets = 48 + + +class PrivateECDSAP384SHA384(PrivateECDSA): + public_cls = PublicECDSAP384SHA384 diff --git a/venv/lib/python3.12/site-packages/dns/dnssecalgs/eddsa.py b/venv/lib/python3.12/site-packages/dns/dnssecalgs/eddsa.py new file mode 100644 index 0000000..1cbb407 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/dnssecalgs/eddsa.py @@ -0,0 +1,70 @@ +from typing import Type + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ed448, ed25519 + +from dns.dnssecalgs.cryptography import CryptographyPrivateKey, CryptographyPublicKey +from dns.dnssectypes import Algorithm +from dns.rdtypes.ANY.DNSKEY import DNSKEY + + +class PublicEDDSA(CryptographyPublicKey): + def verify(self, signature: bytes, data: bytes) -> None: + self.key.verify(signature, data) + + def encode_key_bytes(self) -> bytes: + """Encode a public key per RFC 8080, section 3.""" + return self.key.public_bytes( + encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw + ) + + @classmethod + def from_dnskey(cls, key: DNSKEY) -> "PublicEDDSA": + cls._ensure_algorithm_key_combination(key) + return cls( + key=cls.key_cls.from_public_bytes(key.key), + ) + + +class PrivateEDDSA(CryptographyPrivateKey): + public_cls: Type[PublicEDDSA] # pyright: ignore + + def sign( + self, + data: bytes, + verify: bool = False, + deterministic: bool = True, + ) -> bytes: + """Sign using a private key per RFC 8080, section 4.""" + signature = self.key.sign(data) + if verify: + self.public_key().verify(signature, data) + return signature + + @classmethod + def generate(cls) -> "PrivateEDDSA": + return cls(key=cls.key_cls.generate()) + + +class PublicED25519(PublicEDDSA): + key: ed25519.Ed25519PublicKey + key_cls = ed25519.Ed25519PublicKey + algorithm = Algorithm.ED25519 + + +class PrivateED25519(PrivateEDDSA): + key: ed25519.Ed25519PrivateKey + key_cls = ed25519.Ed25519PrivateKey + public_cls = PublicED25519 + + +class PublicED448(PublicEDDSA): + key: ed448.Ed448PublicKey + key_cls = ed448.Ed448PublicKey + algorithm = Algorithm.ED448 + + +class PrivateED448(PrivateEDDSA): + key: ed448.Ed448PrivateKey + key_cls = ed448.Ed448PrivateKey + public_cls = PublicED448 diff --git a/venv/lib/python3.12/site-packages/dns/dnssecalgs/rsa.py b/venv/lib/python3.12/site-packages/dns/dnssecalgs/rsa.py new file mode 100644 index 0000000..de9160b --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/dnssecalgs/rsa.py @@ -0,0 +1,126 @@ +import math +import struct + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding, rsa + +from dns.dnssecalgs.cryptography import CryptographyPrivateKey, CryptographyPublicKey +from dns.dnssectypes import Algorithm +from dns.rdtypes.ANY.DNSKEY import DNSKEY + + +class PublicRSA(CryptographyPublicKey): + key: rsa.RSAPublicKey + key_cls = rsa.RSAPublicKey + algorithm: Algorithm + chosen_hash: hashes.HashAlgorithm + + def verify(self, signature: bytes, data: bytes) -> None: + self.key.verify(signature, data, padding.PKCS1v15(), self.chosen_hash) + + def encode_key_bytes(self) -> bytes: + """Encode a public key per RFC 3110, section 2.""" + pn = self.key.public_numbers() + _exp_len = math.ceil(int.bit_length(pn.e) / 8) + exp = int.to_bytes(pn.e, length=_exp_len, byteorder="big") + if _exp_len > 255: + exp_header = b"\0" + struct.pack("!H", _exp_len) + else: + exp_header = struct.pack("!B", _exp_len) + if pn.n.bit_length() < 512 or pn.n.bit_length() > 4096: + raise ValueError("unsupported RSA key length") + return exp_header + exp + pn.n.to_bytes((pn.n.bit_length() + 7) // 8, "big") + + @classmethod + def from_dnskey(cls, key: DNSKEY) -> "PublicRSA": + cls._ensure_algorithm_key_combination(key) + keyptr = key.key + (bytes_,) = struct.unpack("!B", keyptr[0:1]) + keyptr = keyptr[1:] + if bytes_ == 0: + (bytes_,) = struct.unpack("!H", keyptr[0:2]) + keyptr = keyptr[2:] + rsa_e = keyptr[0:bytes_] + rsa_n = keyptr[bytes_:] + return cls( + key=rsa.RSAPublicNumbers( + int.from_bytes(rsa_e, "big"), int.from_bytes(rsa_n, "big") + ).public_key(default_backend()) + ) + + +class PrivateRSA(CryptographyPrivateKey): + key: rsa.RSAPrivateKey + key_cls = rsa.RSAPrivateKey + public_cls = PublicRSA + default_public_exponent = 65537 + + def sign( + self, + data: bytes, + verify: bool = False, + deterministic: bool = True, + ) -> bytes: + """Sign using a private key per RFC 3110, section 3.""" + signature = self.key.sign( + data, padding.PKCS1v15(), self.public_cls.chosen_hash # pyright: ignore + ) + if verify: + self.public_key().verify(signature, data) + return signature + + @classmethod + def generate(cls, key_size: int) -> "PrivateRSA": + return cls( + key=rsa.generate_private_key( + public_exponent=cls.default_public_exponent, + key_size=key_size, + backend=default_backend(), + ) + ) + + +class PublicRSAMD5(PublicRSA): + algorithm = Algorithm.RSAMD5 + chosen_hash = hashes.MD5() + + +class PrivateRSAMD5(PrivateRSA): + public_cls = PublicRSAMD5 + + +class PublicRSASHA1(PublicRSA): + algorithm = Algorithm.RSASHA1 + chosen_hash = hashes.SHA1() + + +class PrivateRSASHA1(PrivateRSA): + public_cls = PublicRSASHA1 + + +class PublicRSASHA1NSEC3SHA1(PublicRSA): + algorithm = Algorithm.RSASHA1NSEC3SHA1 + chosen_hash = hashes.SHA1() + + +class PrivateRSASHA1NSEC3SHA1(PrivateRSA): + public_cls = PublicRSASHA1NSEC3SHA1 + + +class PublicRSASHA256(PublicRSA): + algorithm = Algorithm.RSASHA256 + chosen_hash = hashes.SHA256() + + +class PrivateRSASHA256(PrivateRSA): + public_cls = PublicRSASHA256 + + +class PublicRSASHA512(PublicRSA): + algorithm = Algorithm.RSASHA512 + chosen_hash = hashes.SHA512() + + +class PrivateRSASHA512(PrivateRSA): + public_cls = PublicRSASHA512 diff --git a/venv/lib/python3.12/site-packages/dns/dnssectypes.py b/venv/lib/python3.12/site-packages/dns/dnssectypes.py new file mode 100644 index 0000000..02131e0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/dnssectypes.py @@ -0,0 +1,71 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""Common DNSSEC-related types.""" + +# This is a separate file to avoid import circularity between dns.dnssec and +# the implementations of the DS and DNSKEY types. + +import dns.enum + + +class Algorithm(dns.enum.IntEnum): + RSAMD5 = 1 + DH = 2 + DSA = 3 + ECC = 4 + RSASHA1 = 5 + DSANSEC3SHA1 = 6 + RSASHA1NSEC3SHA1 = 7 + RSASHA256 = 8 + RSASHA512 = 10 + ECCGOST = 12 + ECDSAP256SHA256 = 13 + ECDSAP384SHA384 = 14 + ED25519 = 15 + ED448 = 16 + INDIRECT = 252 + PRIVATEDNS = 253 + PRIVATEOID = 254 + + @classmethod + def _maximum(cls): + return 255 + + +class DSDigest(dns.enum.IntEnum): + """DNSSEC Delegation Signer Digest Algorithm""" + + NULL = 0 + SHA1 = 1 + SHA256 = 2 + GOST = 3 + SHA384 = 4 + + @classmethod + def _maximum(cls): + return 255 + + +class NSEC3Hash(dns.enum.IntEnum): + """NSEC3 hash algorithm""" + + SHA1 = 1 + + @classmethod + def _maximum(cls): + return 255 diff --git a/venv/lib/python3.12/site-packages/dns/e164.py b/venv/lib/python3.12/site-packages/dns/e164.py new file mode 100644 index 0000000..942d2c0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/e164.py @@ -0,0 +1,116 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2006-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS E.164 helpers.""" + +from typing import Iterable + +import dns.exception +import dns.name +import dns.resolver + +#: The public E.164 domain. +public_enum_domain = dns.name.from_text("e164.arpa.") + + +def from_e164( + text: str, origin: dns.name.Name | None = public_enum_domain +) -> dns.name.Name: + """Convert an E.164 number in textual form into a Name object whose + value is the ENUM domain name for that number. + + Non-digits in the text are ignored, i.e. "16505551212", + "+1.650.555.1212" and "1 (650) 555-1212" are all the same. + + *text*, a ``str``, is an E.164 number in textual form. + + *origin*, a ``dns.name.Name``, the domain in which the number + should be constructed. The default is ``e164.arpa.``. + + Returns a ``dns.name.Name``. + """ + + parts = [d for d in text if d.isdigit()] + parts.reverse() + return dns.name.from_text(".".join(parts), origin=origin) + + +def to_e164( + name: dns.name.Name, + origin: dns.name.Name | None = public_enum_domain, + want_plus_prefix: bool = True, +) -> str: + """Convert an ENUM domain name into an E.164 number. + + Note that dnspython does not have any information about preferred + number formats within national numbering plans, so all numbers are + emitted as a simple string of digits, prefixed by a '+' (unless + *want_plus_prefix* is ``False``). + + *name* is a ``dns.name.Name``, the ENUM domain name. + + *origin* is a ``dns.name.Name``, a domain containing the ENUM + domain name. The name is relativized to this domain before being + converted to text. If ``None``, no relativization is done. + + *want_plus_prefix* is a ``bool``. If True, add a '+' to the beginning of + the returned number. + + Returns a ``str``. + + """ + if origin is not None: + name = name.relativize(origin) + dlabels = [d for d in name.labels if d.isdigit() and len(d) == 1] + if len(dlabels) != len(name.labels): + raise dns.exception.SyntaxError("non-digit labels in ENUM domain name") + dlabels.reverse() + text = b"".join(dlabels) + if want_plus_prefix: + text = b"+" + text + return text.decode() + + +def query( + number: str, + domains: Iterable[dns.name.Name | str], + resolver: dns.resolver.Resolver | None = None, +) -> dns.resolver.Answer: + """Look for NAPTR RRs for the specified number in the specified domains. + + e.g. lookup('16505551212', ['e164.dnspython.org.', 'e164.arpa.']) + + *number*, a ``str`` is the number to look for. + + *domains* is an iterable containing ``dns.name.Name`` values. + + *resolver*, a ``dns.resolver.Resolver``, is the resolver to use. If + ``None``, the default resolver is used. + """ + + if resolver is None: + resolver = dns.resolver.get_default_resolver() + e_nx = dns.resolver.NXDOMAIN() + for domain in domains: + if isinstance(domain, str): + domain = dns.name.from_text(domain) + qname = from_e164(number, domain) + try: + return resolver.resolve(qname, "NAPTR") + except dns.resolver.NXDOMAIN as e: + e_nx += e + raise e_nx diff --git a/venv/lib/python3.12/site-packages/dns/edns.py b/venv/lib/python3.12/site-packages/dns/edns.py new file mode 100644 index 0000000..eb98548 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/edns.py @@ -0,0 +1,591 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2009-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""EDNS Options""" + +import binascii +import math +import socket +import struct +from typing import Any, Dict + +import dns.enum +import dns.inet +import dns.ipv4 +import dns.ipv6 +import dns.name +import dns.rdata +import dns.wire + + +class OptionType(dns.enum.IntEnum): + """EDNS option type codes""" + + #: NSID + NSID = 3 + #: DAU + DAU = 5 + #: DHU + DHU = 6 + #: N3U + N3U = 7 + #: ECS (client-subnet) + ECS = 8 + #: EXPIRE + EXPIRE = 9 + #: COOKIE + COOKIE = 10 + #: KEEPALIVE + KEEPALIVE = 11 + #: PADDING + PADDING = 12 + #: CHAIN + CHAIN = 13 + #: EDE (extended-dns-error) + EDE = 15 + #: REPORTCHANNEL + REPORTCHANNEL = 18 + + @classmethod + def _maximum(cls): + return 65535 + + +class Option: + """Base class for all EDNS option types.""" + + def __init__(self, otype: OptionType | str): + """Initialize an option. + + *otype*, a ``dns.edns.OptionType``, is the option type. + """ + self.otype = OptionType.make(otype) + + def to_wire(self, file: Any | None = None) -> bytes | None: + """Convert an option to wire format. + + Returns a ``bytes`` or ``None``. + + """ + raise NotImplementedError # pragma: no cover + + def to_text(self) -> str: + raise NotImplementedError # pragma: no cover + + def to_generic(self) -> "GenericOption": + """Creates a dns.edns.GenericOption equivalent of this rdata. + + Returns a ``dns.edns.GenericOption``. + """ + wire = self.to_wire() + assert wire is not None # for mypy + return GenericOption(self.otype, wire) + + @classmethod + def from_wire_parser(cls, otype: OptionType, parser: "dns.wire.Parser") -> "Option": + """Build an EDNS option object from wire format. + + *otype*, a ``dns.edns.OptionType``, is the option type. + + *parser*, a ``dns.wire.Parser``, the parser, which should be + restructed to the option length. + + Returns a ``dns.edns.Option``. + """ + raise NotImplementedError # pragma: no cover + + def _cmp(self, other): + """Compare an EDNS option with another option of the same type. + + Returns < 0 if < *other*, 0 if == *other*, and > 0 if > *other*. + """ + wire = self.to_wire() + owire = other.to_wire() + if wire == owire: + return 0 + if wire > owire: + return 1 + return -1 + + def __eq__(self, other): + if not isinstance(other, Option): + return False + if self.otype != other.otype: + return False + return self._cmp(other) == 0 + + def __ne__(self, other): + if not isinstance(other, Option): + return True + if self.otype != other.otype: + return True + return self._cmp(other) != 0 + + def __lt__(self, other): + if not isinstance(other, Option) or self.otype != other.otype: + return NotImplemented + return self._cmp(other) < 0 + + def __le__(self, other): + if not isinstance(other, Option) or self.otype != other.otype: + return NotImplemented + return self._cmp(other) <= 0 + + def __ge__(self, other): + if not isinstance(other, Option) or self.otype != other.otype: + return NotImplemented + return self._cmp(other) >= 0 + + def __gt__(self, other): + if not isinstance(other, Option) or self.otype != other.otype: + return NotImplemented + return self._cmp(other) > 0 + + def __str__(self): + return self.to_text() + + +class GenericOption(Option): # lgtm[py/missing-equals] + """Generic Option Class + + This class is used for EDNS option types for which we have no better + implementation. + """ + + def __init__(self, otype: OptionType | str, data: bytes | str): + super().__init__(otype) + self.data = dns.rdata.Rdata._as_bytes(data, True) + + def to_wire(self, file: Any | None = None) -> bytes | None: + if file: + file.write(self.data) + return None + else: + return self.data + + def to_text(self) -> str: + return f"Generic {self.otype}" + + def to_generic(self) -> "GenericOption": + return self + + @classmethod + def from_wire_parser( + cls, otype: OptionType | str, parser: "dns.wire.Parser" + ) -> Option: + return cls(otype, parser.get_remaining()) + + +class ECSOption(Option): # lgtm[py/missing-equals] + """EDNS Client Subnet (ECS, RFC7871)""" + + def __init__(self, address: str, srclen: int | None = None, scopelen: int = 0): + """*address*, a ``str``, is the client address information. + + *srclen*, an ``int``, the source prefix length, which is the + leftmost number of bits of the address to be used for the + lookup. The default is 24 for IPv4 and 56 for IPv6. + + *scopelen*, an ``int``, the scope prefix length. This value + must be 0 in queries, and should be set in responses. + """ + + super().__init__(OptionType.ECS) + af = dns.inet.af_for_address(address) + + if af == socket.AF_INET6: + self.family = 2 + if srclen is None: + srclen = 56 + address = dns.rdata.Rdata._as_ipv6_address(address) + srclen = dns.rdata.Rdata._as_int(srclen, 0, 128) + scopelen = dns.rdata.Rdata._as_int(scopelen, 0, 128) + elif af == socket.AF_INET: + self.family = 1 + if srclen is None: + srclen = 24 + address = dns.rdata.Rdata._as_ipv4_address(address) + srclen = dns.rdata.Rdata._as_int(srclen, 0, 32) + scopelen = dns.rdata.Rdata._as_int(scopelen, 0, 32) + else: # pragma: no cover (this will never happen) + raise ValueError("Bad address family") + + assert srclen is not None + self.address = address + self.srclen = srclen + self.scopelen = scopelen + + addrdata = dns.inet.inet_pton(af, address) + nbytes = int(math.ceil(srclen / 8.0)) + + # Truncate to srclen and pad to the end of the last octet needed + # See RFC section 6 + self.addrdata = addrdata[:nbytes] + nbits = srclen % 8 + if nbits != 0: + last = struct.pack("B", ord(self.addrdata[-1:]) & (0xFF << (8 - nbits))) + self.addrdata = self.addrdata[:-1] + last + + def to_text(self) -> str: + return f"ECS {self.address}/{self.srclen} scope/{self.scopelen}" + + @staticmethod + def from_text(text: str) -> Option: + """Convert a string into a `dns.edns.ECSOption` + + *text*, a `str`, the text form of the option. + + Returns a `dns.edns.ECSOption`. + + Examples: + + >>> import dns.edns + >>> + >>> # basic example + >>> dns.edns.ECSOption.from_text('1.2.3.4/24') + >>> + >>> # also understands scope + >>> dns.edns.ECSOption.from_text('1.2.3.4/24/32') + >>> + >>> # IPv6 + >>> dns.edns.ECSOption.from_text('2001:4b98::1/64/64') + >>> + >>> # it understands results from `dns.edns.ECSOption.to_text()` + >>> dns.edns.ECSOption.from_text('ECS 1.2.3.4/24/32') + """ + optional_prefix = "ECS" + tokens = text.split() + ecs_text = None + if len(tokens) == 1: + ecs_text = tokens[0] + elif len(tokens) == 2: + if tokens[0] != optional_prefix: + raise ValueError(f'could not parse ECS from "{text}"') + ecs_text = tokens[1] + else: + raise ValueError(f'could not parse ECS from "{text}"') + n_slashes = ecs_text.count("/") + if n_slashes == 1: + address, tsrclen = ecs_text.split("/") + tscope = "0" + elif n_slashes == 2: + address, tsrclen, tscope = ecs_text.split("/") + else: + raise ValueError(f'could not parse ECS from "{text}"') + try: + scope = int(tscope) + except ValueError: + raise ValueError("invalid scope " + f'"{tscope}": scope must be an integer') + try: + srclen = int(tsrclen) + except ValueError: + raise ValueError( + "invalid srclen " + f'"{tsrclen}": srclen must be an integer' + ) + return ECSOption(address, srclen, scope) + + def to_wire(self, file: Any | None = None) -> bytes | None: + value = ( + struct.pack("!HBB", self.family, self.srclen, self.scopelen) + self.addrdata + ) + if file: + file.write(value) + return None + else: + return value + + @classmethod + def from_wire_parser( + cls, otype: OptionType | str, parser: "dns.wire.Parser" + ) -> Option: + family, src, scope = parser.get_struct("!HBB") + addrlen = int(math.ceil(src / 8.0)) + prefix = parser.get_bytes(addrlen) + if family == 1: + pad = 4 - addrlen + addr = dns.ipv4.inet_ntoa(prefix + b"\x00" * pad) + elif family == 2: + pad = 16 - addrlen + addr = dns.ipv6.inet_ntoa(prefix + b"\x00" * pad) + else: + raise ValueError("unsupported family") + + return cls(addr, src, scope) + + +class EDECode(dns.enum.IntEnum): + """Extended DNS Error (EDE) codes""" + + OTHER = 0 + UNSUPPORTED_DNSKEY_ALGORITHM = 1 + UNSUPPORTED_DS_DIGEST_TYPE = 2 + STALE_ANSWER = 3 + FORGED_ANSWER = 4 + DNSSEC_INDETERMINATE = 5 + DNSSEC_BOGUS = 6 + SIGNATURE_EXPIRED = 7 + SIGNATURE_NOT_YET_VALID = 8 + DNSKEY_MISSING = 9 + RRSIGS_MISSING = 10 + NO_ZONE_KEY_BIT_SET = 11 + NSEC_MISSING = 12 + CACHED_ERROR = 13 + NOT_READY = 14 + BLOCKED = 15 + CENSORED = 16 + FILTERED = 17 + PROHIBITED = 18 + STALE_NXDOMAIN_ANSWER = 19 + NOT_AUTHORITATIVE = 20 + NOT_SUPPORTED = 21 + NO_REACHABLE_AUTHORITY = 22 + NETWORK_ERROR = 23 + INVALID_DATA = 24 + + @classmethod + def _maximum(cls): + return 65535 + + +class EDEOption(Option): # lgtm[py/missing-equals] + """Extended DNS Error (EDE, RFC8914)""" + + _preserve_case = {"DNSKEY", "DS", "DNSSEC", "RRSIGs", "NSEC", "NXDOMAIN"} + + def __init__(self, code: EDECode | str, text: str | None = None): + """*code*, a ``dns.edns.EDECode`` or ``str``, the info code of the + extended error. + + *text*, a ``str`` or ``None``, specifying additional information about + the error. + """ + + super().__init__(OptionType.EDE) + + self.code = EDECode.make(code) + if text is not None and not isinstance(text, str): + raise ValueError("text must be string or None") + self.text = text + + def to_text(self) -> str: + output = f"EDE {self.code}" + if self.code in EDECode: + desc = EDECode.to_text(self.code) + desc = " ".join( + word if word in self._preserve_case else word.title() + for word in desc.split("_") + ) + output += f" ({desc})" + if self.text is not None: + output += f": {self.text}" + return output + + def to_wire(self, file: Any | None = None) -> bytes | None: + value = struct.pack("!H", self.code) + if self.text is not None: + value += self.text.encode("utf8") + + if file: + file.write(value) + return None + else: + return value + + @classmethod + def from_wire_parser( + cls, otype: OptionType | str, parser: "dns.wire.Parser" + ) -> Option: + code = EDECode.make(parser.get_uint16()) + text = parser.get_remaining() + + if text: + if text[-1] == 0: # text MAY be null-terminated + text = text[:-1] + btext = text.decode("utf8") + else: + btext = None + + return cls(code, btext) + + +class NSIDOption(Option): + def __init__(self, nsid: bytes): + super().__init__(OptionType.NSID) + self.nsid = nsid + + def to_wire(self, file: Any = None) -> bytes | None: + if file: + file.write(self.nsid) + return None + else: + return self.nsid + + def to_text(self) -> str: + if all(c >= 0x20 and c <= 0x7E for c in self.nsid): + # All ASCII printable, so it's probably a string. + value = self.nsid.decode() + else: + value = binascii.hexlify(self.nsid).decode() + return f"NSID {value}" + + @classmethod + def from_wire_parser( + cls, otype: OptionType | str, parser: dns.wire.Parser + ) -> Option: + return cls(parser.get_remaining()) + + +class CookieOption(Option): + def __init__(self, client: bytes, server: bytes): + super().__init__(OptionType.COOKIE) + self.client = client + self.server = server + if len(client) != 8: + raise ValueError("client cookie must be 8 bytes") + if len(server) != 0 and (len(server) < 8 or len(server) > 32): + raise ValueError("server cookie must be empty or between 8 and 32 bytes") + + def to_wire(self, file: Any = None) -> bytes | None: + if file: + file.write(self.client) + if len(self.server) > 0: + file.write(self.server) + return None + else: + return self.client + self.server + + def to_text(self) -> str: + client = binascii.hexlify(self.client).decode() + if len(self.server) > 0: + server = binascii.hexlify(self.server).decode() + else: + server = "" + return f"COOKIE {client}{server}" + + @classmethod + def from_wire_parser( + cls, otype: OptionType | str, parser: dns.wire.Parser + ) -> Option: + return cls(parser.get_bytes(8), parser.get_remaining()) + + +class ReportChannelOption(Option): + # RFC 9567 + def __init__(self, agent_domain: dns.name.Name): + super().__init__(OptionType.REPORTCHANNEL) + self.agent_domain = agent_domain + + def to_wire(self, file: Any = None) -> bytes | None: + return self.agent_domain.to_wire(file) + + def to_text(self) -> str: + return "REPORTCHANNEL " + self.agent_domain.to_text() + + @classmethod + def from_wire_parser( + cls, otype: OptionType | str, parser: dns.wire.Parser + ) -> Option: + return cls(parser.get_name()) + + +_type_to_class: Dict[OptionType, Any] = { + OptionType.ECS: ECSOption, + OptionType.EDE: EDEOption, + OptionType.NSID: NSIDOption, + OptionType.COOKIE: CookieOption, + OptionType.REPORTCHANNEL: ReportChannelOption, +} + + +def get_option_class(otype: OptionType) -> Any: + """Return the class for the specified option type. + + The GenericOption class is used if a more specific class is not + known. + """ + + cls = _type_to_class.get(otype) + if cls is None: + cls = GenericOption + return cls + + +def option_from_wire_parser( + otype: OptionType | str, parser: "dns.wire.Parser" +) -> Option: + """Build an EDNS option object from wire format. + + *otype*, an ``int``, is the option type. + + *parser*, a ``dns.wire.Parser``, the parser, which should be + restricted to the option length. + + Returns an instance of a subclass of ``dns.edns.Option``. + """ + otype = OptionType.make(otype) + cls = get_option_class(otype) + return cls.from_wire_parser(otype, parser) + + +def option_from_wire( + otype: OptionType | str, wire: bytes, current: int, olen: int +) -> Option: + """Build an EDNS option object from wire format. + + *otype*, an ``int``, is the option type. + + *wire*, a ``bytes``, is the wire-format message. + + *current*, an ``int``, is the offset in *wire* of the beginning + of the rdata. + + *olen*, an ``int``, is the length of the wire-format option data + + Returns an instance of a subclass of ``dns.edns.Option``. + """ + parser = dns.wire.Parser(wire, current) + with parser.restrict_to(olen): + return option_from_wire_parser(otype, parser) + + +def register_type(implementation: Any, otype: OptionType) -> None: + """Register the implementation of an option type. + + *implementation*, a ``class``, is a subclass of ``dns.edns.Option``. + + *otype*, an ``int``, is the option type. + """ + + _type_to_class[otype] = implementation + + +### BEGIN generated OptionType constants + +NSID = OptionType.NSID +DAU = OptionType.DAU +DHU = OptionType.DHU +N3U = OptionType.N3U +ECS = OptionType.ECS +EXPIRE = OptionType.EXPIRE +COOKIE = OptionType.COOKIE +KEEPALIVE = OptionType.KEEPALIVE +PADDING = OptionType.PADDING +CHAIN = OptionType.CHAIN +EDE = OptionType.EDE +REPORTCHANNEL = OptionType.REPORTCHANNEL + +### END generated OptionType constants diff --git a/venv/lib/python3.12/site-packages/dns/entropy.py b/venv/lib/python3.12/site-packages/dns/entropy.py new file mode 100644 index 0000000..6430926 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/entropy.py @@ -0,0 +1,130 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2009-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import hashlib +import os +import random +import threading +import time +from typing import Any + + +class EntropyPool: + # This is an entropy pool for Python implementations that do not + # have a working SystemRandom. I'm not sure there are any, but + # leaving this code doesn't hurt anything as the library code + # is used if present. + + def __init__(self, seed: bytes | None = None): + self.pool_index = 0 + self.digest: bytearray | None = None + self.next_byte = 0 + self.lock = threading.Lock() + self.hash = hashlib.sha1() + self.hash_len = 20 + self.pool = bytearray(b"\0" * self.hash_len) + if seed is not None: + self._stir(seed) + self.seeded = True + self.seed_pid = os.getpid() + else: + self.seeded = False + self.seed_pid = 0 + + def _stir(self, entropy: bytes | bytearray) -> None: + for c in entropy: + if self.pool_index == self.hash_len: + self.pool_index = 0 + b = c & 0xFF + self.pool[self.pool_index] ^= b + self.pool_index += 1 + + def stir(self, entropy: bytes | bytearray) -> None: + with self.lock: + self._stir(entropy) + + def _maybe_seed(self) -> None: + if not self.seeded or self.seed_pid != os.getpid(): + try: + seed = os.urandom(16) + except Exception: # pragma: no cover + try: + with open("/dev/urandom", "rb", 0) as r: + seed = r.read(16) + except Exception: + seed = str(time.time()).encode() + self.seeded = True + self.seed_pid = os.getpid() + self.digest = None + seed = bytearray(seed) + self._stir(seed) + + def random_8(self) -> int: + with self.lock: + self._maybe_seed() + if self.digest is None or self.next_byte == self.hash_len: + self.hash.update(bytes(self.pool)) + self.digest = bytearray(self.hash.digest()) + self._stir(self.digest) + self.next_byte = 0 + value = self.digest[self.next_byte] + self.next_byte += 1 + return value + + def random_16(self) -> int: + return self.random_8() * 256 + self.random_8() + + def random_32(self) -> int: + return self.random_16() * 65536 + self.random_16() + + def random_between(self, first: int, last: int) -> int: + size = last - first + 1 + if size > 4294967296: + raise ValueError("too big") + if size > 65536: + rand = self.random_32 + max = 4294967295 + elif size > 256: + rand = self.random_16 + max = 65535 + else: + rand = self.random_8 + max = 255 + return first + size * rand() // (max + 1) + + +pool = EntropyPool() + +system_random: Any | None +try: + system_random = random.SystemRandom() +except Exception: # pragma: no cover + system_random = None + + +def random_16() -> int: + if system_random is not None: + return system_random.randrange(0, 65536) + else: + return pool.random_16() + + +def between(first: int, last: int) -> int: + if system_random is not None: + return system_random.randrange(first, last + 1) + else: + return pool.random_between(first, last) diff --git a/venv/lib/python3.12/site-packages/dns/enum.py b/venv/lib/python3.12/site-packages/dns/enum.py new file mode 100644 index 0000000..822c995 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/enum.py @@ -0,0 +1,113 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import enum +from typing import Any, Type, TypeVar + +TIntEnum = TypeVar("TIntEnum", bound="IntEnum") + + +class IntEnum(enum.IntEnum): + @classmethod + def _missing_(cls, value): + cls._check_value(value) + val = int.__new__(cls, value) # pyright: ignore + val._name_ = cls._extra_to_text(value, None) or f"{cls._prefix()}{value}" + val._value_ = value # pyright: ignore + return val + + @classmethod + def _check_value(cls, value): + max = cls._maximum() + if not isinstance(value, int): + raise TypeError + if value < 0 or value > max: + name = cls._short_name() + raise ValueError(f"{name} must be an int between >= 0 and <= {max}") + + @classmethod + def from_text(cls: Type[TIntEnum], text: str) -> TIntEnum: + text = text.upper() + try: + return cls[text] + except KeyError: + pass + value = cls._extra_from_text(text) + if value: + return value + prefix = cls._prefix() + if text.startswith(prefix) and text[len(prefix) :].isdigit(): + value = int(text[len(prefix) :]) + cls._check_value(value) + return cls(value) + raise cls._unknown_exception_class() + + @classmethod + def to_text(cls: Type[TIntEnum], value: int) -> str: + cls._check_value(value) + try: + text = cls(value).name + except ValueError: + text = None + text = cls._extra_to_text(value, text) + if text is None: + text = f"{cls._prefix()}{value}" + return text + + @classmethod + def make(cls: Type[TIntEnum], value: int | str) -> TIntEnum: + """Convert text or a value into an enumerated type, if possible. + + *value*, the ``int`` or ``str`` to convert. + + Raises a class-specific exception if a ``str`` is provided that + cannot be converted. + + Raises ``ValueError`` if the value is out of range. + + Returns an enumeration from the calling class corresponding to the + value, if one is defined, or an ``int`` otherwise. + """ + + if isinstance(value, str): + return cls.from_text(value) + cls._check_value(value) + return cls(value) + + @classmethod + def _maximum(cls): + raise NotImplementedError # pragma: no cover + + @classmethod + def _short_name(cls): + return cls.__name__.lower() + + @classmethod + def _prefix(cls) -> str: + return "" + + @classmethod + def _extra_from_text(cls, text: str) -> Any | None: # pylint: disable=W0613 + return None + + @classmethod + def _extra_to_text(cls, value, current_text): # pylint: disable=W0613 + return current_text + + @classmethod + def _unknown_exception_class(cls) -> Type[Exception]: + return ValueError diff --git a/venv/lib/python3.12/site-packages/dns/exception.py b/venv/lib/python3.12/site-packages/dns/exception.py new file mode 100644 index 0000000..c3d42ff --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/exception.py @@ -0,0 +1,169 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""Common DNS Exceptions. + +Dnspython modules may also define their own exceptions, which will +always be subclasses of ``DNSException``. +""" + + +from typing import Set + + +class DNSException(Exception): + """Abstract base class shared by all dnspython exceptions. + + It supports two basic modes of operation: + + a) Old/compatible mode is used if ``__init__`` was called with + empty *kwargs*. In compatible mode all *args* are passed + to the standard Python Exception class as before and all *args* are + printed by the standard ``__str__`` implementation. Class variable + ``msg`` (or doc string if ``msg`` is ``None``) is returned from ``str()`` + if *args* is empty. + + b) New/parametrized mode is used if ``__init__`` was called with + non-empty *kwargs*. + In the new mode *args* must be empty and all kwargs must match + those set in class variable ``supp_kwargs``. All kwargs are stored inside + ``self.kwargs`` and used in a new ``__str__`` implementation to construct + a formatted message based on the ``fmt`` class variable, a ``string``. + + In the simplest case it is enough to override the ``supp_kwargs`` + and ``fmt`` class variables to get nice parametrized messages. + """ + + msg: str | None = None # non-parametrized message + supp_kwargs: Set[str] = set() # accepted parameters for _fmt_kwargs (sanity check) + fmt: str | None = None # message parametrized with results from _fmt_kwargs + + def __init__(self, *args, **kwargs): + self._check_params(*args, **kwargs) + if kwargs: + # This call to a virtual method from __init__ is ok in our usage + self.kwargs = self._check_kwargs(**kwargs) # lgtm[py/init-calls-subclass] + self.msg = str(self) + else: + self.kwargs = dict() # defined but empty for old mode exceptions + if self.msg is None: + # doc string is better implicit message than empty string + self.msg = self.__doc__ + if args: + super().__init__(*args) + else: + super().__init__(self.msg) + + def _check_params(self, *args, **kwargs): + """Old exceptions supported only args and not kwargs. + + For sanity we do not allow to mix old and new behavior.""" + if args or kwargs: + assert bool(args) != bool( + kwargs + ), "keyword arguments are mutually exclusive with positional args" + + def _check_kwargs(self, **kwargs): + if kwargs: + assert ( + set(kwargs.keys()) == self.supp_kwargs + ), f"following set of keyword args is required: {self.supp_kwargs}" + return kwargs + + def _fmt_kwargs(self, **kwargs): + """Format kwargs before printing them. + + Resulting dictionary has to have keys necessary for str.format call + on fmt class variable. + """ + fmtargs = {} + for kw, data in kwargs.items(): + if isinstance(data, list | set): + # convert list of to list of str() + fmtargs[kw] = list(map(str, data)) + if len(fmtargs[kw]) == 1: + # remove list brackets [] from single-item lists + fmtargs[kw] = fmtargs[kw].pop() + else: + fmtargs[kw] = data + return fmtargs + + def __str__(self): + if self.kwargs and self.fmt: + # provide custom message constructed from keyword arguments + fmtargs = self._fmt_kwargs(**self.kwargs) + return self.fmt.format(**fmtargs) + else: + # print *args directly in the same way as old DNSException + return super().__str__() + + +class FormError(DNSException): + """DNS message is malformed.""" + + +class SyntaxError(DNSException): + """Text input is malformed.""" + + +class UnexpectedEnd(SyntaxError): + """Text input ended unexpectedly.""" + + +class TooBig(DNSException): + """The DNS message is too big.""" + + +class Timeout(DNSException): + """The DNS operation timed out.""" + + supp_kwargs = {"timeout"} + fmt = "The DNS operation timed out after {timeout:.3f} seconds" + + # We do this as otherwise mypy complains about unexpected keyword argument + # idna_exception + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class UnsupportedAlgorithm(DNSException): + """The DNSSEC algorithm is not supported.""" + + +class AlgorithmKeyMismatch(UnsupportedAlgorithm): + """The DNSSEC algorithm is not supported for the given key type.""" + + +class ValidationFailure(DNSException): + """The DNSSEC signature is invalid.""" + + +class DeniedByPolicy(DNSException): + """Denied by DNSSEC policy.""" + + +class ExceptionWrapper: + def __init__(self, exception_class): + self.exception_class = exception_class + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is not None and not isinstance(exc_val, self.exception_class): + raise self.exception_class(str(exc_val)) from exc_val + return False diff --git a/venv/lib/python3.12/site-packages/dns/flags.py b/venv/lib/python3.12/site-packages/dns/flags.py new file mode 100644 index 0000000..4c60be1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/flags.py @@ -0,0 +1,123 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS Message Flags.""" + +import enum +from typing import Any + +# Standard DNS flags + + +class Flag(enum.IntFlag): + #: Query Response + QR = 0x8000 + #: Authoritative Answer + AA = 0x0400 + #: Truncated Response + TC = 0x0200 + #: Recursion Desired + RD = 0x0100 + #: Recursion Available + RA = 0x0080 + #: Authentic Data + AD = 0x0020 + #: Checking Disabled + CD = 0x0010 + + +# EDNS flags + + +class EDNSFlag(enum.IntFlag): + #: DNSSEC answer OK + DO = 0x8000 + + +def _from_text(text: str, enum_class: Any) -> int: + flags = 0 + tokens = text.split() + for t in tokens: + flags |= enum_class[t.upper()] + return flags + + +def _to_text(flags: int, enum_class: Any) -> str: + text_flags = [] + for k, v in enum_class.__members__.items(): + if flags & v != 0: + text_flags.append(k) + return " ".join(text_flags) + + +def from_text(text: str) -> int: + """Convert a space-separated list of flag text values into a flags + value. + + Returns an ``int`` + """ + + return _from_text(text, Flag) + + +def to_text(flags: int) -> str: + """Convert a flags value into a space-separated list of flag text + values. + + Returns a ``str``. + """ + + return _to_text(flags, Flag) + + +def edns_from_text(text: str) -> int: + """Convert a space-separated list of EDNS flag text values into a EDNS + flags value. + + Returns an ``int`` + """ + + return _from_text(text, EDNSFlag) + + +def edns_to_text(flags: int) -> str: + """Convert an EDNS flags value into a space-separated list of EDNS flag + text values. + + Returns a ``str``. + """ + + return _to_text(flags, EDNSFlag) + + +### BEGIN generated Flag constants + +QR = Flag.QR +AA = Flag.AA +TC = Flag.TC +RD = Flag.RD +RA = Flag.RA +AD = Flag.AD +CD = Flag.CD + +### END generated Flag constants + +### BEGIN generated EDNSFlag constants + +DO = EDNSFlag.DO + +### END generated EDNSFlag constants diff --git a/venv/lib/python3.12/site-packages/dns/grange.py b/venv/lib/python3.12/site-packages/dns/grange.py new file mode 100644 index 0000000..8d366dc --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/grange.py @@ -0,0 +1,72 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2012-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS GENERATE range conversion.""" + +from typing import Tuple + +import dns.exception + + +def from_text(text: str) -> Tuple[int, int, int]: + """Convert the text form of a range in a ``$GENERATE`` statement to an + integer. + + *text*, a ``str``, the textual range in ``$GENERATE`` form. + + Returns a tuple of three ``int`` values ``(start, stop, step)``. + """ + + start = -1 + stop = -1 + step = 1 + cur = "" + state = 0 + # state 0 1 2 + # x - y / z + + if text and text[0] == "-": + raise dns.exception.SyntaxError("Start cannot be a negative number") + + for c in text: + if c == "-" and state == 0: + start = int(cur) + cur = "" + state = 1 + elif c == "/": + stop = int(cur) + cur = "" + state = 2 + elif c.isdigit(): + cur += c + else: + raise dns.exception.SyntaxError(f"Could not parse {c}") + + if state == 0: + raise dns.exception.SyntaxError("no stop value specified") + elif state == 1: + stop = int(cur) + else: + assert state == 2 + step = int(cur) + + assert step >= 1 + assert start >= 0 + if start > stop: + raise dns.exception.SyntaxError("start must be <= stop") + + return (start, stop, step) diff --git a/venv/lib/python3.12/site-packages/dns/immutable.py b/venv/lib/python3.12/site-packages/dns/immutable.py new file mode 100644 index 0000000..36b0362 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/immutable.py @@ -0,0 +1,68 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import collections.abc +from typing import Any, Callable + +from dns._immutable_ctx import immutable + + +@immutable +class Dict(collections.abc.Mapping): # lgtm[py/missing-equals] + def __init__( + self, + dictionary: Any, + no_copy: bool = False, + map_factory: Callable[[], collections.abc.MutableMapping] = dict, + ): + """Make an immutable dictionary from the specified dictionary. + + If *no_copy* is `True`, then *dictionary* will be wrapped instead + of copied. Only set this if you are sure there will be no external + references to the dictionary. + """ + if no_copy and isinstance(dictionary, collections.abc.MutableMapping): + self._odict = dictionary + else: + self._odict = map_factory() + self._odict.update(dictionary) + self._hash = None + + def __getitem__(self, key): + return self._odict.__getitem__(key) + + def __hash__(self): # pylint: disable=invalid-hash-returned + if self._hash is None: + h = 0 + for key in sorted(self._odict.keys()): + h ^= hash(key) + object.__setattr__(self, "_hash", h) + # this does return an int, but pylint doesn't figure that out + return self._hash + + def __len__(self): + return len(self._odict) + + def __iter__(self): + return iter(self._odict) + + +def constify(o: Any) -> Any: + """ + Convert mutable types to immutable types. + """ + if isinstance(o, bytearray): + return bytes(o) + if isinstance(o, tuple): + try: + hash(o) + return o + except Exception: + return tuple(constify(elt) for elt in o) + if isinstance(o, list): + return tuple(constify(elt) for elt in o) + if isinstance(o, dict): + cdict = dict() + for k, v in o.items(): + cdict[k] = constify(v) + return Dict(cdict, True) + return o diff --git a/venv/lib/python3.12/site-packages/dns/inet.py b/venv/lib/python3.12/site-packages/dns/inet.py new file mode 100644 index 0000000..765203b --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/inet.py @@ -0,0 +1,195 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""Generic Internet address helper functions.""" + +import socket +from typing import Any, Tuple + +import dns.ipv4 +import dns.ipv6 + +# We assume that AF_INET and AF_INET6 are always defined. We keep +# these here for the benefit of any old code (unlikely though that +# is!). +AF_INET = socket.AF_INET +AF_INET6 = socket.AF_INET6 + + +def inet_pton(family: int, text: str) -> bytes: + """Convert the textual form of a network address into its binary form. + + *family* is an ``int``, the address family. + + *text* is a ``str``, the textual address. + + Raises ``NotImplementedError`` if the address family specified is not + implemented. + + Returns a ``bytes``. + """ + + if family == AF_INET: + return dns.ipv4.inet_aton(text) + elif family == AF_INET6: + return dns.ipv6.inet_aton(text, True) + else: + raise NotImplementedError + + +def inet_ntop(family: int, address: bytes) -> str: + """Convert the binary form of a network address into its textual form. + + *family* is an ``int``, the address family. + + *address* is a ``bytes``, the network address in binary form. + + Raises ``NotImplementedError`` if the address family specified is not + implemented. + + Returns a ``str``. + """ + + if family == AF_INET: + return dns.ipv4.inet_ntoa(address) + elif family == AF_INET6: + return dns.ipv6.inet_ntoa(address) + else: + raise NotImplementedError + + +def af_for_address(text: str) -> int: + """Determine the address family of a textual-form network address. + + *text*, a ``str``, the textual address. + + Raises ``ValueError`` if the address family cannot be determined + from the input. + + Returns an ``int``. + """ + + try: + dns.ipv4.inet_aton(text) + return AF_INET + except Exception: + try: + dns.ipv6.inet_aton(text, True) + return AF_INET6 + except Exception: + raise ValueError + + +def is_multicast(text: str) -> bool: + """Is the textual-form network address a multicast address? + + *text*, a ``str``, the textual address. + + Raises ``ValueError`` if the address family cannot be determined + from the input. + + Returns a ``bool``. + """ + + try: + first = dns.ipv4.inet_aton(text)[0] + return first >= 224 and first <= 239 + except Exception: + try: + first = dns.ipv6.inet_aton(text, True)[0] + return first == 255 + except Exception: + raise ValueError + + +def is_address(text: str) -> bool: + """Is the specified string an IPv4 or IPv6 address? + + *text*, a ``str``, the textual address. + + Returns a ``bool``. + """ + + try: + dns.ipv4.inet_aton(text) + return True + except Exception: + try: + dns.ipv6.inet_aton(text, True) + return True + except Exception: + return False + + +def low_level_address_tuple(high_tuple: Tuple[str, int], af: int | None = None) -> Any: + """Given a "high-level" address tuple, i.e. + an (address, port) return the appropriate "low-level" address tuple + suitable for use in socket calls. + + If an *af* other than ``None`` is provided, it is assumed the + address in the high-level tuple is valid and has that af. If af + is ``None``, then af_for_address will be called. + """ + address, port = high_tuple + if af is None: + af = af_for_address(address) + if af == AF_INET: + return (address, port) + elif af == AF_INET6: + i = address.find("%") + if i < 0: + # no scope, shortcut! + return (address, port, 0, 0) + # try to avoid getaddrinfo() + addrpart = address[:i] + scope = address[i + 1 :] + if scope.isdigit(): + return (addrpart, port, 0, int(scope)) + try: + return (addrpart, port, 0, socket.if_nametoindex(scope)) + except AttributeError: # pragma: no cover (we can't really test this) + ai_flags = socket.AI_NUMERICHOST + ((*_, tup), *_) = socket.getaddrinfo(address, port, flags=ai_flags) + return tup + else: + raise NotImplementedError(f"unknown address family {af}") + + +def any_for_af(af): + """Return the 'any' address for the specified address family.""" + if af == socket.AF_INET: + return "0.0.0.0" + elif af == socket.AF_INET6: + return "::" + raise NotImplementedError(f"unknown address family {af}") + + +def canonicalize(text: str) -> str: + """Verify that *address* is a valid text form IPv4 or IPv6 address and return its + canonical text form. IPv6 addresses with scopes are rejected. + + *text*, a ``str``, the address in textual form. + + Raises ``ValueError`` if the text is not valid. + """ + try: + return dns.ipv6.canonicalize(text) + except Exception: + try: + return dns.ipv4.canonicalize(text) + except Exception: + raise ValueError diff --git a/venv/lib/python3.12/site-packages/dns/ipv4.py b/venv/lib/python3.12/site-packages/dns/ipv4.py new file mode 100644 index 0000000..a7161bc --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/ipv4.py @@ -0,0 +1,76 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""IPv4 helper functions.""" + +import struct + +import dns.exception + + +def inet_ntoa(address: bytes) -> str: + """Convert an IPv4 address in binary form to text form. + + *address*, a ``bytes``, the IPv4 address in binary form. + + Returns a ``str``. + """ + + if len(address) != 4: + raise dns.exception.SyntaxError + return f"{address[0]}.{address[1]}.{address[2]}.{address[3]}" + + +def inet_aton(text: str | bytes) -> bytes: + """Convert an IPv4 address in text form to binary form. + + *text*, a ``str`` or ``bytes``, the IPv4 address in textual form. + + Returns a ``bytes``. + """ + + if not isinstance(text, bytes): + btext = text.encode() + else: + btext = text + parts = btext.split(b".") + if len(parts) != 4: + raise dns.exception.SyntaxError + for part in parts: + if not part.isdigit(): + raise dns.exception.SyntaxError + if len(part) > 1 and part[0] == ord("0"): + # No leading zeros + raise dns.exception.SyntaxError + try: + b = [int(part) for part in parts] + return struct.pack("BBBB", *b) + except Exception: + raise dns.exception.SyntaxError + + +def canonicalize(text: str | bytes) -> str: + """Verify that *address* is a valid text form IPv4 address and return its + canonical text form. + + *text*, a ``str`` or ``bytes``, the IPv4 address in textual form. + + Raises ``dns.exception.SyntaxError`` if the text is not valid. + """ + # Note that inet_aton() only accepts canonial form, but we still run through + # inet_ntoa() to ensure the output is a str. + return inet_ntoa(inet_aton(text)) diff --git a/venv/lib/python3.12/site-packages/dns/ipv6.py b/venv/lib/python3.12/site-packages/dns/ipv6.py new file mode 100644 index 0000000..eaa0f6c --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/ipv6.py @@ -0,0 +1,217 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""IPv6 helper functions.""" + +import binascii +import re +from typing import List + +import dns.exception +import dns.ipv4 + +_leading_zero = re.compile(r"0+([0-9a-f]+)") + + +def inet_ntoa(address: bytes) -> str: + """Convert an IPv6 address in binary form to text form. + + *address*, a ``bytes``, the IPv6 address in binary form. + + Raises ``ValueError`` if the address isn't 16 bytes long. + Returns a ``str``. + """ + + if len(address) != 16: + raise ValueError("IPv6 addresses are 16 bytes long") + hex = binascii.hexlify(address) + chunks = [] + i = 0 + l = len(hex) + while i < l: + chunk = hex[i : i + 4].decode() + # strip leading zeros. we do this with an re instead of + # with lstrip() because lstrip() didn't support chars until + # python 2.2.2 + m = _leading_zero.match(chunk) + if m is not None: + chunk = m.group(1) + chunks.append(chunk) + i += 4 + # + # Compress the longest subsequence of 0-value chunks to :: + # + best_start = 0 + best_len = 0 + start = -1 + last_was_zero = False + for i in range(8): + if chunks[i] != "0": + if last_was_zero: + end = i + current_len = end - start + if current_len > best_len: + best_start = start + best_len = current_len + last_was_zero = False + elif not last_was_zero: + start = i + last_was_zero = True + if last_was_zero: + end = 8 + current_len = end - start + if current_len > best_len: + best_start = start + best_len = current_len + if best_len > 1: + if best_start == 0 and (best_len == 6 or best_len == 5 and chunks[5] == "ffff"): + # We have an embedded IPv4 address + if best_len == 6: + prefix = "::" + else: + prefix = "::ffff:" + thex = prefix + dns.ipv4.inet_ntoa(address[12:]) + else: + thex = ( + ":".join(chunks[:best_start]) + + "::" + + ":".join(chunks[best_start + best_len :]) + ) + else: + thex = ":".join(chunks) + return thex + + +_v4_ending = re.compile(rb"(.*):(\d+\.\d+\.\d+\.\d+)$") +_colon_colon_start = re.compile(rb"::.*") +_colon_colon_end = re.compile(rb".*::$") + + +def inet_aton(text: str | bytes, ignore_scope: bool = False) -> bytes: + """Convert an IPv6 address in text form to binary form. + + *text*, a ``str`` or ``bytes``, the IPv6 address in textual form. + + *ignore_scope*, a ``bool``. If ``True``, a scope will be ignored. + If ``False``, the default, it is an error for a scope to be present. + + Returns a ``bytes``. + """ + + # + # Our aim here is not something fast; we just want something that works. + # + if not isinstance(text, bytes): + btext = text.encode() + else: + btext = text + + if ignore_scope: + parts = btext.split(b"%") + l = len(parts) + if l == 2: + btext = parts[0] + elif l > 2: + raise dns.exception.SyntaxError + + if btext == b"": + raise dns.exception.SyntaxError + elif btext.endswith(b":") and not btext.endswith(b"::"): + raise dns.exception.SyntaxError + elif btext.startswith(b":") and not btext.startswith(b"::"): + raise dns.exception.SyntaxError + elif btext == b"::": + btext = b"0::" + # + # Get rid of the icky dot-quad syntax if we have it. + # + m = _v4_ending.match(btext) + if m is not None: + b = dns.ipv4.inet_aton(m.group(2)) + btext = ( + f"{m.group(1).decode()}:{b[0]:02x}{b[1]:02x}:{b[2]:02x}{b[3]:02x}" + ).encode() + # + # Try to turn '::' into ':'; if no match try to + # turn '::' into ':' + # + m = _colon_colon_start.match(btext) + if m is not None: + btext = btext[1:] + else: + m = _colon_colon_end.match(btext) + if m is not None: + btext = btext[:-1] + # + # Now canonicalize into 8 chunks of 4 hex digits each + # + chunks = btext.split(b":") + l = len(chunks) + if l > 8: + raise dns.exception.SyntaxError + seen_empty = False + canonical: List[bytes] = [] + for c in chunks: + if c == b"": + if seen_empty: + raise dns.exception.SyntaxError + seen_empty = True + for _ in range(0, 8 - l + 1): + canonical.append(b"0000") + else: + lc = len(c) + if lc > 4: + raise dns.exception.SyntaxError + if lc != 4: + c = (b"0" * (4 - lc)) + c + canonical.append(c) + if l < 8 and not seen_empty: + raise dns.exception.SyntaxError + btext = b"".join(canonical) + + # + # Finally we can go to binary. + # + try: + return binascii.unhexlify(btext) + except (binascii.Error, TypeError): + raise dns.exception.SyntaxError + + +_mapped_prefix = b"\x00" * 10 + b"\xff\xff" + + +def is_mapped(address: bytes) -> bool: + """Is the specified address a mapped IPv4 address? + + *address*, a ``bytes`` is an IPv6 address in binary form. + + Returns a ``bool``. + """ + + return address.startswith(_mapped_prefix) + + +def canonicalize(text: str | bytes) -> str: + """Verify that *address* is a valid text form IPv6 address and return its + canonical text form. Addresses with scopes are rejected. + + *text*, a ``str`` or ``bytes``, the IPv6 address in textual form. + + Raises ``dns.exception.SyntaxError`` if the text is not valid. + """ + return inet_ntoa(inet_aton(text)) diff --git a/venv/lib/python3.12/site-packages/dns/message.py b/venv/lib/python3.12/site-packages/dns/message.py new file mode 100644 index 0000000..bbfccfc --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/message.py @@ -0,0 +1,1954 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS Messages""" + +import contextlib +import enum +import io +import time +from typing import Any, Dict, List, Tuple, cast + +import dns.edns +import dns.entropy +import dns.enum +import dns.exception +import dns.flags +import dns.name +import dns.opcode +import dns.rcode +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.rdtypes.ANY.OPT +import dns.rdtypes.ANY.SOA +import dns.rdtypes.ANY.TSIG +import dns.renderer +import dns.rrset +import dns.tokenizer +import dns.tsig +import dns.ttl +import dns.wire + + +class ShortHeader(dns.exception.FormError): + """The DNS packet passed to from_wire() is too short.""" + + +class TrailingJunk(dns.exception.FormError): + """The DNS packet passed to from_wire() has extra junk at the end of it.""" + + +class UnknownHeaderField(dns.exception.DNSException): + """The header field name was not recognized when converting from text + into a message.""" + + +class BadEDNS(dns.exception.FormError): + """An OPT record occurred somewhere other than + the additional data section.""" + + +class BadTSIG(dns.exception.FormError): + """A TSIG record occurred somewhere other than the end of + the additional data section.""" + + +class UnknownTSIGKey(dns.exception.DNSException): + """A TSIG with an unknown key was received.""" + + +class Truncated(dns.exception.DNSException): + """The truncated flag is set.""" + + supp_kwargs = {"message"} + + # We do this as otherwise mypy complains about unexpected keyword argument + # idna_exception + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def message(self): + """As much of the message as could be processed. + + Returns a ``dns.message.Message``. + """ + return self.kwargs["message"] + + +class NotQueryResponse(dns.exception.DNSException): + """Message is not a response to a query.""" + + +class ChainTooLong(dns.exception.DNSException): + """The CNAME chain is too long.""" + + +class AnswerForNXDOMAIN(dns.exception.DNSException): + """The rcode is NXDOMAIN but an answer was found.""" + + +class NoPreviousName(dns.exception.SyntaxError): + """No previous name was known.""" + + +class MessageSection(dns.enum.IntEnum): + """Message sections""" + + QUESTION = 0 + ANSWER = 1 + AUTHORITY = 2 + ADDITIONAL = 3 + + @classmethod + def _maximum(cls): + return 3 + + +class MessageError: + def __init__(self, exception: Exception, offset: int): + self.exception = exception + self.offset = offset + + +DEFAULT_EDNS_PAYLOAD = 1232 +MAX_CHAIN = 16 + +IndexKeyType = Tuple[ + int, + dns.name.Name, + dns.rdataclass.RdataClass, + dns.rdatatype.RdataType, + dns.rdatatype.RdataType | None, + dns.rdataclass.RdataClass | None, +] +IndexType = Dict[IndexKeyType, dns.rrset.RRset] +SectionType = int | str | List[dns.rrset.RRset] + + +class Message: + """A DNS message.""" + + _section_enum = MessageSection + + def __init__(self, id: int | None = None): + if id is None: + self.id = dns.entropy.random_16() + else: + self.id = id + self.flags = 0 + self.sections: List[List[dns.rrset.RRset]] = [[], [], [], []] + self.opt: dns.rrset.RRset | None = None + self.request_payload = 0 + self.pad = 0 + self.keyring: Any = None + self.tsig: dns.rrset.RRset | None = None + self.want_tsig_sign = False + self.request_mac = b"" + self.xfr = False + self.origin: dns.name.Name | None = None + self.tsig_ctx: Any | None = None + self.index: IndexType = {} + self.errors: List[MessageError] = [] + self.time = 0.0 + self.wire: bytes | None = None + + @property + def question(self) -> List[dns.rrset.RRset]: + """The question section.""" + return self.sections[0] + + @question.setter + def question(self, v): + self.sections[0] = v + + @property + def answer(self) -> List[dns.rrset.RRset]: + """The answer section.""" + return self.sections[1] + + @answer.setter + def answer(self, v): + self.sections[1] = v + + @property + def authority(self) -> List[dns.rrset.RRset]: + """The authority section.""" + return self.sections[2] + + @authority.setter + def authority(self, v): + self.sections[2] = v + + @property + def additional(self) -> List[dns.rrset.RRset]: + """The additional data section.""" + return self.sections[3] + + @additional.setter + def additional(self, v): + self.sections[3] = v + + def __repr__(self): + return "" + + def __str__(self): + return self.to_text() + + def to_text( + self, + origin: dns.name.Name | None = None, + relativize: bool = True, + **kw: Dict[str, Any], + ) -> str: + """Convert the message to text. + + The *origin*, *relativize*, and any other keyword + arguments are passed to the RRset ``to_wire()`` method. + + Returns a ``str``. + """ + + s = io.StringIO() + s.write(f"id {self.id}\n") + s.write(f"opcode {dns.opcode.to_text(self.opcode())}\n") + s.write(f"rcode {dns.rcode.to_text(self.rcode())}\n") + s.write(f"flags {dns.flags.to_text(self.flags)}\n") + if self.edns >= 0: + s.write(f"edns {self.edns}\n") + if self.ednsflags != 0: + s.write(f"eflags {dns.flags.edns_to_text(self.ednsflags)}\n") + s.write(f"payload {self.payload}\n") + for opt in self.options: + s.write(f"option {opt.to_text()}\n") + for name, which in self._section_enum.__members__.items(): + s.write(f";{name}\n") + for rrset in self.section_from_number(which): + s.write(rrset.to_text(origin, relativize, **kw)) + s.write("\n") + if self.tsig is not None: + s.write(self.tsig.to_text(origin, relativize, **kw)) + s.write("\n") + # + # We strip off the final \n so the caller can print the result without + # doing weird things to get around eccentricities in Python print + # formatting + # + return s.getvalue()[:-1] + + def __eq__(self, other): + """Two messages are equal if they have the same content in the + header, question, answer, and authority sections. + + Returns a ``bool``. + """ + + if not isinstance(other, Message): + return False + if self.id != other.id: + return False + if self.flags != other.flags: + return False + for i, section in enumerate(self.sections): + other_section = other.sections[i] + for n in section: + if n not in other_section: + return False + for n in other_section: + if n not in section: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def is_response(self, other: "Message") -> bool: + """Is *other*, also a ``dns.message.Message``, a response to this + message? + + Returns a ``bool``. + """ + + if ( + other.flags & dns.flags.QR == 0 + or self.id != other.id + or dns.opcode.from_flags(self.flags) != dns.opcode.from_flags(other.flags) + ): + return False + if other.rcode() in { + dns.rcode.FORMERR, + dns.rcode.SERVFAIL, + dns.rcode.NOTIMP, + dns.rcode.REFUSED, + }: + # We don't check the question section in these cases if + # the other question section is empty, even though they + # still really ought to have a question section. + if len(other.question) == 0: + return True + if dns.opcode.is_update(self.flags): + # This is assuming the "sender doesn't include anything + # from the update", but we don't care to check the other + # case, which is that all the sections are returned and + # identical. + return True + for n in self.question: + if n not in other.question: + return False + for n in other.question: + if n not in self.question: + return False + return True + + def section_number(self, section: List[dns.rrset.RRset]) -> int: + """Return the "section number" of the specified section for use + in indexing. + + *section* is one of the section attributes of this message. + + Raises ``ValueError`` if the section isn't known. + + Returns an ``int``. + """ + + for i, our_section in enumerate(self.sections): + if section is our_section: + return self._section_enum(i) + raise ValueError("unknown section") + + def section_from_number(self, number: int) -> List[dns.rrset.RRset]: + """Return the section list associated with the specified section + number. + + *number* is a section number `int` or the text form of a section + name. + + Raises ``ValueError`` if the section isn't known. + + Returns a ``list``. + """ + + section = self._section_enum.make(number) + return self.sections[section] + + def find_rrset( + self, + section: SectionType, + name: dns.name.Name, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + deleting: dns.rdataclass.RdataClass | None = None, + create: bool = False, + force_unique: bool = False, + idna_codec: dns.name.IDNACodec | None = None, + ) -> dns.rrset.RRset: + """Find the RRset with the given attributes in the specified section. + + *section*, an ``int`` section number, a ``str`` section name, or one of + the section attributes of this message. This specifies the + the section of the message to search. For example:: + + my_message.find_rrset(my_message.answer, name, rdclass, rdtype) + my_message.find_rrset(dns.message.ANSWER, name, rdclass, rdtype) + my_message.find_rrset("ANSWER", name, rdclass, rdtype) + + *name*, a ``dns.name.Name`` or ``str``, the name of the RRset. + + *rdclass*, an ``int`` or ``str``, the class of the RRset. + + *rdtype*, an ``int`` or ``str``, the type of the RRset. + + *covers*, an ``int`` or ``str``, the covers value of the RRset. + The default is ``dns.rdatatype.NONE``. + + *deleting*, an ``int``, ``str``, or ``None``, the deleting value of the + RRset. The default is ``None``. + + *create*, a ``bool``. If ``True``, create the RRset if it is not found. + The created RRset is appended to *section*. + + *force_unique*, a ``bool``. If ``True`` and *create* is also ``True``, + create a new RRset regardless of whether a matching RRset exists + already. The default is ``False``. This is useful when creating + DDNS Update messages, as order matters for them. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder + is used. + + Raises ``KeyError`` if the RRset was not found and create was + ``False``. + + Returns a ``dns.rrset.RRset object``. + """ + + if isinstance(section, int): + section_number = section + section = self.section_from_number(section_number) + elif isinstance(section, str): + section_number = self._section_enum.from_text(section) + section = self.section_from_number(section_number) + else: + section_number = self.section_number(section) + if isinstance(name, str): + name = dns.name.from_text(name, idna_codec=idna_codec) + rdtype = dns.rdatatype.RdataType.make(rdtype) + rdclass = dns.rdataclass.RdataClass.make(rdclass) + covers = dns.rdatatype.RdataType.make(covers) + if deleting is not None: + deleting = dns.rdataclass.RdataClass.make(deleting) + key = (section_number, name, rdclass, rdtype, covers, deleting) + if not force_unique: + if self.index is not None: + rrset = self.index.get(key) + if rrset is not None: + return rrset + else: + for rrset in section: + if rrset.full_match(name, rdclass, rdtype, covers, deleting): + return rrset + if not create: + raise KeyError + rrset = dns.rrset.RRset(name, rdclass, rdtype, covers, deleting) + section.append(rrset) + if self.index is not None: + self.index[key] = rrset + return rrset + + def get_rrset( + self, + section: SectionType, + name: dns.name.Name, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + deleting: dns.rdataclass.RdataClass | None = None, + create: bool = False, + force_unique: bool = False, + idna_codec: dns.name.IDNACodec | None = None, + ) -> dns.rrset.RRset | None: + """Get the RRset with the given attributes in the specified section. + + If the RRset is not found, None is returned. + + *section*, an ``int`` section number, a ``str`` section name, or one of + the section attributes of this message. This specifies the + the section of the message to search. For example:: + + my_message.get_rrset(my_message.answer, name, rdclass, rdtype) + my_message.get_rrset(dns.message.ANSWER, name, rdclass, rdtype) + my_message.get_rrset("ANSWER", name, rdclass, rdtype) + + *name*, a ``dns.name.Name`` or ``str``, the name of the RRset. + + *rdclass*, an ``int`` or ``str``, the class of the RRset. + + *rdtype*, an ``int`` or ``str``, the type of the RRset. + + *covers*, an ``int`` or ``str``, the covers value of the RRset. + The default is ``dns.rdatatype.NONE``. + + *deleting*, an ``int``, ``str``, or ``None``, the deleting value of the + RRset. The default is ``None``. + + *create*, a ``bool``. If ``True``, create the RRset if it is not found. + The created RRset is appended to *section*. + + *force_unique*, a ``bool``. If ``True`` and *create* is also ``True``, + create a new RRset regardless of whether a matching RRset exists + already. The default is ``False``. This is useful when creating + DDNS Update messages, as order matters for them. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder + is used. + + Returns a ``dns.rrset.RRset object`` or ``None``. + """ + + try: + rrset = self.find_rrset( + section, + name, + rdclass, + rdtype, + covers, + deleting, + create, + force_unique, + idna_codec, + ) + except KeyError: + rrset = None + return rrset + + def section_count(self, section: SectionType) -> int: + """Returns the number of records in the specified section. + + *section*, an ``int`` section number, a ``str`` section name, or one of + the section attributes of this message. This specifies the + the section of the message to count. For example:: + + my_message.section_count(my_message.answer) + my_message.section_count(dns.message.ANSWER) + my_message.section_count("ANSWER") + """ + + if isinstance(section, int): + section_number = section + section = self.section_from_number(section_number) + elif isinstance(section, str): + section_number = self._section_enum.from_text(section) + section = self.section_from_number(section_number) + else: + section_number = self.section_number(section) + count = sum(max(1, len(rrs)) for rrs in section) + if section_number == MessageSection.ADDITIONAL: + if self.opt is not None: + count += 1 + if self.tsig is not None: + count += 1 + return count + + def _compute_opt_reserve(self) -> int: + """Compute the size required for the OPT RR, padding excluded""" + if not self.opt: + return 0 + # 1 byte for the root name, 10 for the standard RR fields + size = 11 + # This would be more efficient if options had a size() method, but we won't + # worry about that for now. We also don't worry if there is an existing padding + # option, as it is unlikely and probably harmless, as the worst case is that we + # may add another, and this seems to be legal. + opt_rdata = cast(dns.rdtypes.ANY.OPT.OPT, self.opt[0]) + for option in opt_rdata.options: + wire = option.to_wire() + # We add 4 here to account for the option type and length + size += len(wire) + 4 + if self.pad: + # Padding will be added, so again add the option type and length. + size += 4 + return size + + def _compute_tsig_reserve(self) -> int: + """Compute the size required for the TSIG RR""" + # This would be more efficient if TSIGs had a size method, but we won't + # worry about for now. Also, we can't really cope with the potential + # compressibility of the TSIG owner name, so we estimate with the uncompressed + # size. We will disable compression when TSIG and padding are both is active + # so that the padding comes out right. + if not self.tsig: + return 0 + f = io.BytesIO() + self.tsig.to_wire(f) + return len(f.getvalue()) + + def to_wire( + self, + origin: dns.name.Name | None = None, + max_size: int = 0, + multi: bool = False, + tsig_ctx: Any | None = None, + prepend_length: bool = False, + prefer_truncation: bool = False, + **kw: Dict[str, Any], + ) -> bytes: + """Return a string containing the message in DNS compressed wire + format. + + Additional keyword arguments are passed to the RRset ``to_wire()`` + method. + + *origin*, a ``dns.name.Name`` or ``None``, the origin to be appended + to any relative names. If ``None``, and the message has an origin + attribute that is not ``None``, then it will be used. + + *max_size*, an ``int``, the maximum size of the wire format + output; default is 0, which means "the message's request + payload, if nonzero, or 65535". + + *multi*, a ``bool``, should be set to ``True`` if this message is + part of a multiple message sequence. + + *tsig_ctx*, a ``dns.tsig.HMACTSig`` or ``dns.tsig.GSSTSig`` object, the + ongoing TSIG context, used when signing zone transfers. + + *prepend_length*, a ``bool``, should be set to ``True`` if the caller + wants the message length prepended to the message itself. This is + useful for messages sent over TCP, TLS (DoT), or QUIC (DoQ). + + *prefer_truncation*, a ``bool``, should be set to ``True`` if the caller + wants the message to be truncated if it would otherwise exceed the + maximum length. If the truncation occurs before the additional section, + the TC bit will be set. + + Raises ``dns.exception.TooBig`` if *max_size* was exceeded. + + Returns a ``bytes``. + """ + + if origin is None and self.origin is not None: + origin = self.origin + if max_size == 0: + if self.request_payload != 0: + max_size = self.request_payload + else: + max_size = 65535 + if max_size < 512: + max_size = 512 + elif max_size > 65535: + max_size = 65535 + r = dns.renderer.Renderer(self.id, self.flags, max_size, origin) + opt_reserve = self._compute_opt_reserve() + r.reserve(opt_reserve) + tsig_reserve = self._compute_tsig_reserve() + r.reserve(tsig_reserve) + try: + for rrset in self.question: + r.add_question(rrset.name, rrset.rdtype, rrset.rdclass) + for rrset in self.answer: + r.add_rrset(dns.renderer.ANSWER, rrset, **kw) + for rrset in self.authority: + r.add_rrset(dns.renderer.AUTHORITY, rrset, **kw) + for rrset in self.additional: + r.add_rrset(dns.renderer.ADDITIONAL, rrset, **kw) + except dns.exception.TooBig: + if prefer_truncation: + if r.section < dns.renderer.ADDITIONAL: + r.flags |= dns.flags.TC + else: + raise + r.release_reserved() + if self.opt is not None: + r.add_opt(self.opt, self.pad, opt_reserve, tsig_reserve) + r.write_header() + if self.tsig is not None: + if self.want_tsig_sign: + (new_tsig, ctx) = dns.tsig.sign( + r.get_wire(), + self.keyring, + self.tsig[0], + int(time.time()), + self.request_mac, + tsig_ctx, + multi, + ) + self.tsig.clear() + self.tsig.add(new_tsig) + if multi: + self.tsig_ctx = ctx + r.add_rrset(dns.renderer.ADDITIONAL, self.tsig) + r.write_header() + wire = r.get_wire() + self.wire = wire + if prepend_length: + wire = len(wire).to_bytes(2, "big") + wire + return wire + + @staticmethod + def _make_tsig( + keyname, algorithm, time_signed, fudge, mac, original_id, error, other + ): + tsig = dns.rdtypes.ANY.TSIG.TSIG( + dns.rdataclass.ANY, + dns.rdatatype.TSIG, + algorithm, + time_signed, + fudge, + mac, + original_id, + error, + other, + ) + return dns.rrset.from_rdata(keyname, 0, tsig) + + def use_tsig( + self, + keyring: Any, + keyname: dns.name.Name | str | None = None, + fudge: int = 300, + original_id: int | None = None, + tsig_error: int = 0, + other_data: bytes = b"", + algorithm: dns.name.Name | str = dns.tsig.default_algorithm, + ) -> None: + """When sending, a TSIG signature using the specified key + should be added. + + *keyring*, a ``dict``, ``callable`` or ``dns.tsig.Key``, is either + the TSIG keyring or key to use. + + The format of a keyring dict is a mapping from TSIG key name, as + ``dns.name.Name`` to ``dns.tsig.Key`` or a TSIG secret, a ``bytes``. + If a ``dict`` *keyring* is specified but a *keyname* is not, the key + used will be the first key in the *keyring*. Note that the order of + keys in a dictionary is not defined, so applications should supply a + keyname when a ``dict`` keyring is used, unless they know the keyring + contains only one key. If a ``callable`` keyring is specified, the + callable will be called with the message and the keyname, and is + expected to return a key. + + *keyname*, a ``dns.name.Name``, ``str`` or ``None``, the name of + this TSIG key to use; defaults to ``None``. If *keyring* is a + ``dict``, the key must be defined in it. If *keyring* is a + ``dns.tsig.Key``, this is ignored. + + *fudge*, an ``int``, the TSIG time fudge. + + *original_id*, an ``int``, the TSIG original id. If ``None``, + the message's id is used. + + *tsig_error*, an ``int``, the TSIG error code. + + *other_data*, a ``bytes``, the TSIG other data. + + *algorithm*, a ``dns.name.Name`` or ``str``, the TSIG algorithm to use. This is + only used if *keyring* is a ``dict``, and the key entry is a ``bytes``. + """ + + if isinstance(keyring, dns.tsig.Key): + key = keyring + keyname = key.name + elif callable(keyring): + key = keyring(self, keyname) + else: + if isinstance(keyname, str): + keyname = dns.name.from_text(keyname) + if keyname is None: + keyname = next(iter(keyring)) + key = keyring[keyname] + if isinstance(key, bytes): + key = dns.tsig.Key(keyname, key, algorithm) + self.keyring = key + if original_id is None: + original_id = self.id + self.tsig = self._make_tsig( + keyname, + self.keyring.algorithm, + 0, + fudge, + b"\x00" * dns.tsig.mac_sizes[self.keyring.algorithm], + original_id, + tsig_error, + other_data, + ) + self.want_tsig_sign = True + + @property + def keyname(self) -> dns.name.Name | None: + if self.tsig: + return self.tsig.name + else: + return None + + @property + def keyalgorithm(self) -> dns.name.Name | None: + if self.tsig: + rdata = cast(dns.rdtypes.ANY.TSIG.TSIG, self.tsig[0]) + return rdata.algorithm + else: + return None + + @property + def mac(self) -> bytes | None: + if self.tsig: + rdata = cast(dns.rdtypes.ANY.TSIG.TSIG, self.tsig[0]) + return rdata.mac + else: + return None + + @property + def tsig_error(self) -> int | None: + if self.tsig: + rdata = cast(dns.rdtypes.ANY.TSIG.TSIG, self.tsig[0]) + return rdata.error + else: + return None + + @property + def had_tsig(self) -> bool: + return bool(self.tsig) + + @staticmethod + def _make_opt(flags=0, payload=DEFAULT_EDNS_PAYLOAD, options=None): + opt = dns.rdtypes.ANY.OPT.OPT(payload, dns.rdatatype.OPT, options or ()) + return dns.rrset.from_rdata(dns.name.root, int(flags), opt) + + def use_edns( + self, + edns: int | bool | None = 0, + ednsflags: int = 0, + payload: int = DEFAULT_EDNS_PAYLOAD, + request_payload: int | None = None, + options: List[dns.edns.Option] | None = None, + pad: int = 0, + ) -> None: + """Configure EDNS behavior. + + *edns*, an ``int``, is the EDNS level to use. Specifying ``None``, ``False``, + or ``-1`` means "do not use EDNS", and in this case the other parameters are + ignored. Specifying ``True`` is equivalent to specifying 0, i.e. "use EDNS0". + + *ednsflags*, an ``int``, the EDNS flag values. + + *payload*, an ``int``, is the EDNS sender's payload field, which is the maximum + size of UDP datagram the sender can handle. I.e. how big a response to this + message can be. + + *request_payload*, an ``int``, is the EDNS payload size to use when sending this + message. If not specified, defaults to the value of *payload*. + + *options*, a list of ``dns.edns.Option`` objects or ``None``, the EDNS options. + + *pad*, a non-negative ``int``. If 0, the default, do not pad; otherwise add + padding bytes to make the message size a multiple of *pad*. Note that if + padding is non-zero, an EDNS PADDING option will always be added to the + message. + """ + + if edns is None or edns is False: + edns = -1 + elif edns is True: + edns = 0 + if edns < 0: + self.opt = None + self.request_payload = 0 + else: + # make sure the EDNS version in ednsflags agrees with edns + ednsflags &= 0xFF00FFFF + ednsflags |= edns << 16 + if options is None: + options = [] + self.opt = self._make_opt(ednsflags, payload, options) + if request_payload is None: + request_payload = payload + self.request_payload = request_payload + if pad < 0: + raise ValueError("pad must be non-negative") + self.pad = pad + + @property + def edns(self) -> int: + if self.opt: + return (self.ednsflags & 0xFF0000) >> 16 + else: + return -1 + + @property + def ednsflags(self) -> int: + if self.opt: + return self.opt.ttl + else: + return 0 + + @ednsflags.setter + def ednsflags(self, v): + if self.opt: + self.opt.ttl = v + elif v: + self.opt = self._make_opt(v) + + @property + def payload(self) -> int: + if self.opt: + rdata = cast(dns.rdtypes.ANY.OPT.OPT, self.opt[0]) + return rdata.payload + else: + return 0 + + @property + def options(self) -> Tuple: + if self.opt: + rdata = cast(dns.rdtypes.ANY.OPT.OPT, self.opt[0]) + return rdata.options + else: + return () + + def want_dnssec(self, wanted: bool = True) -> None: + """Enable or disable 'DNSSEC desired' flag in requests. + + *wanted*, a ``bool``. If ``True``, then DNSSEC data is + desired in the response, EDNS is enabled if required, and then + the DO bit is set. If ``False``, the DO bit is cleared if + EDNS is enabled. + """ + + if wanted: + self.ednsflags |= dns.flags.DO + elif self.opt: + self.ednsflags &= ~int(dns.flags.DO) + + def rcode(self) -> dns.rcode.Rcode: + """Return the rcode. + + Returns a ``dns.rcode.Rcode``. + """ + return dns.rcode.from_flags(int(self.flags), int(self.ednsflags)) + + def set_rcode(self, rcode: dns.rcode.Rcode) -> None: + """Set the rcode. + + *rcode*, a ``dns.rcode.Rcode``, is the rcode to set. + """ + (value, evalue) = dns.rcode.to_flags(rcode) + self.flags &= 0xFFF0 + self.flags |= value + self.ednsflags &= 0x00FFFFFF + self.ednsflags |= evalue + + def opcode(self) -> dns.opcode.Opcode: + """Return the opcode. + + Returns a ``dns.opcode.Opcode``. + """ + return dns.opcode.from_flags(int(self.flags)) + + def set_opcode(self, opcode: dns.opcode.Opcode) -> None: + """Set the opcode. + + *opcode*, a ``dns.opcode.Opcode``, is the opcode to set. + """ + self.flags &= 0x87FF + self.flags |= dns.opcode.to_flags(opcode) + + def get_options(self, otype: dns.edns.OptionType) -> List[dns.edns.Option]: + """Return the list of options of the specified type.""" + return [option for option in self.options if option.otype == otype] + + def extended_errors(self) -> List[dns.edns.EDEOption]: + """Return the list of Extended DNS Error (EDE) options in the message""" + return cast(List[dns.edns.EDEOption], self.get_options(dns.edns.OptionType.EDE)) + + def _get_one_rr_per_rrset(self, value): + # What the caller picked is fine. + return value + + # pylint: disable=unused-argument + + def _parse_rr_header(self, section, name, rdclass, rdtype): + return (rdclass, rdtype, None, False) + + # pylint: enable=unused-argument + + def _parse_special_rr_header(self, section, count, position, name, rdclass, rdtype): + if rdtype == dns.rdatatype.OPT: + if ( + section != MessageSection.ADDITIONAL + or self.opt + or name != dns.name.root + ): + raise BadEDNS + elif rdtype == dns.rdatatype.TSIG: + if ( + section != MessageSection.ADDITIONAL + or rdclass != dns.rdatatype.ANY + or position != count - 1 + ): + raise BadTSIG + return (rdclass, rdtype, None, False) + + +class ChainingResult: + """The result of a call to dns.message.QueryMessage.resolve_chaining(). + + The ``answer`` attribute is the answer RRSet, or ``None`` if it doesn't + exist. + + The ``canonical_name`` attribute is the canonical name after all + chaining has been applied (this is the same name as ``rrset.name`` in cases + where rrset is not ``None``). + + The ``minimum_ttl`` attribute is the minimum TTL, i.e. the TTL to + use if caching the data. It is the smallest of all the CNAME TTLs + and either the answer TTL if it exists or the SOA TTL and SOA + minimum values for negative answers. + + The ``cnames`` attribute is a list of all the CNAME RRSets followed to + get to the canonical name. + """ + + def __init__( + self, + canonical_name: dns.name.Name, + answer: dns.rrset.RRset | None, + minimum_ttl: int, + cnames: List[dns.rrset.RRset], + ): + self.canonical_name = canonical_name + self.answer = answer + self.minimum_ttl = minimum_ttl + self.cnames = cnames + + +class QueryMessage(Message): + def resolve_chaining(self) -> ChainingResult: + """Follow the CNAME chain in the response to determine the answer + RRset. + + Raises ``dns.message.NotQueryResponse`` if the message is not + a response. + + Raises ``dns.message.ChainTooLong`` if the CNAME chain is too long. + + Raises ``dns.message.AnswerForNXDOMAIN`` if the rcode is NXDOMAIN + but an answer was found. + + Raises ``dns.exception.FormError`` if the question count is not 1. + + Returns a ChainingResult object. + """ + if self.flags & dns.flags.QR == 0: + raise NotQueryResponse + if len(self.question) != 1: + raise dns.exception.FormError + question = self.question[0] + qname = question.name + min_ttl = dns.ttl.MAX_TTL + answer = None + count = 0 + cnames = [] + while count < MAX_CHAIN: + try: + answer = self.find_rrset( + self.answer, qname, question.rdclass, question.rdtype + ) + min_ttl = min(min_ttl, answer.ttl) + break + except KeyError: + if question.rdtype != dns.rdatatype.CNAME: + try: + crrset = self.find_rrset( + self.answer, qname, question.rdclass, dns.rdatatype.CNAME + ) + cnames.append(crrset) + min_ttl = min(min_ttl, crrset.ttl) + for rd in crrset: + qname = rd.target + break + count += 1 + continue + except KeyError: + # Exit the chaining loop + break + else: + # Exit the chaining loop + break + if count >= MAX_CHAIN: + raise ChainTooLong + if self.rcode() == dns.rcode.NXDOMAIN and answer is not None: + raise AnswerForNXDOMAIN + if answer is None: + # Further minimize the TTL with NCACHE. + auname = qname + while True: + # Look for an SOA RR whose owner name is a superdomain + # of qname. + try: + srrset = self.find_rrset( + self.authority, auname, question.rdclass, dns.rdatatype.SOA + ) + srdata = cast(dns.rdtypes.ANY.SOA.SOA, srrset[0]) + min_ttl = min(min_ttl, srrset.ttl, srdata.minimum) + break + except KeyError: + try: + auname = auname.parent() + except dns.name.NoParent: + break + return ChainingResult(qname, answer, min_ttl, cnames) + + def canonical_name(self) -> dns.name.Name: + """Return the canonical name of the first name in the question + section. + + Raises ``dns.message.NotQueryResponse`` if the message is not + a response. + + Raises ``dns.message.ChainTooLong`` if the CNAME chain is too long. + + Raises ``dns.message.AnswerForNXDOMAIN`` if the rcode is NXDOMAIN + but an answer was found. + + Raises ``dns.exception.FormError`` if the question count is not 1. + """ + return self.resolve_chaining().canonical_name + + +def _maybe_import_update(): + # We avoid circular imports by doing this here. We do it in another + # function as doing it in _message_factory_from_opcode() makes "dns" + # a local symbol, and the first line fails :) + + # pylint: disable=redefined-outer-name,import-outside-toplevel,unused-import + import dns.update # noqa: F401 + + +def _message_factory_from_opcode(opcode): + if opcode == dns.opcode.QUERY: + return QueryMessage + elif opcode == dns.opcode.UPDATE: + _maybe_import_update() + return dns.update.UpdateMessage # pyright: ignore + else: + return Message + + +class _WireReader: + """Wire format reader. + + parser: the binary parser + message: The message object being built + initialize_message: Callback to set message parsing options + question_only: Are we only reading the question? + one_rr_per_rrset: Put each RR into its own RRset? + keyring: TSIG keyring + ignore_trailing: Ignore trailing junk at end of request? + multi: Is this message part of a multi-message sequence? + DNS dynamic updates. + continue_on_error: try to extract as much information as possible from + the message, accumulating MessageErrors in the *errors* attribute instead of + raising them. + """ + + def __init__( + self, + wire, + initialize_message, + question_only=False, + one_rr_per_rrset=False, + ignore_trailing=False, + keyring=None, + multi=False, + continue_on_error=False, + ): + self.parser = dns.wire.Parser(wire) + self.message = None + self.initialize_message = initialize_message + self.question_only = question_only + self.one_rr_per_rrset = one_rr_per_rrset + self.ignore_trailing = ignore_trailing + self.keyring = keyring + self.multi = multi + self.continue_on_error = continue_on_error + self.errors = [] + + def _get_question(self, section_number, qcount): + """Read the next *qcount* records from the wire data and add them to + the question section. + """ + assert self.message is not None + section = self.message.sections[section_number] + for _ in range(qcount): + qname = self.parser.get_name(self.message.origin) + (rdtype, rdclass) = self.parser.get_struct("!HH") + (rdclass, rdtype, _, _) = self.message._parse_rr_header( + section_number, qname, rdclass, rdtype + ) + self.message.find_rrset( + section, qname, rdclass, rdtype, create=True, force_unique=True + ) + + def _add_error(self, e): + self.errors.append(MessageError(e, self.parser.current)) + + def _get_section(self, section_number, count): + """Read the next I{count} records from the wire data and add them to + the specified section. + + section_number: the section of the message to which to add records + count: the number of records to read + """ + assert self.message is not None + section = self.message.sections[section_number] + force_unique = self.one_rr_per_rrset + for i in range(count): + rr_start = self.parser.current + absolute_name = self.parser.get_name() + if self.message.origin is not None: + name = absolute_name.relativize(self.message.origin) + else: + name = absolute_name + (rdtype, rdclass, ttl, rdlen) = self.parser.get_struct("!HHIH") + if rdtype in (dns.rdatatype.OPT, dns.rdatatype.TSIG): + ( + rdclass, + rdtype, + deleting, + empty, + ) = self.message._parse_special_rr_header( + section_number, count, i, name, rdclass, rdtype + ) + else: + (rdclass, rdtype, deleting, empty) = self.message._parse_rr_header( + section_number, name, rdclass, rdtype + ) + rdata_start = self.parser.current + try: + if empty: + if rdlen > 0: + raise dns.exception.FormError + rd = None + covers = dns.rdatatype.NONE + else: + with self.parser.restrict_to(rdlen): + rd = dns.rdata.from_wire_parser( + rdclass, # pyright: ignore + rdtype, + self.parser, + self.message.origin, + ) + covers = rd.covers() + if self.message.xfr and rdtype == dns.rdatatype.SOA: + force_unique = True + if rdtype == dns.rdatatype.OPT: + self.message.opt = dns.rrset.from_rdata(name, ttl, rd) + elif rdtype == dns.rdatatype.TSIG: + trd = cast(dns.rdtypes.ANY.TSIG.TSIG, rd) + if self.keyring is None or self.keyring is True: + raise UnknownTSIGKey("got signed message without keyring") + elif isinstance(self.keyring, dict): + key = self.keyring.get(absolute_name) + if isinstance(key, bytes): + key = dns.tsig.Key(absolute_name, key, trd.algorithm) + elif callable(self.keyring): + key = self.keyring(self.message, absolute_name) + else: + key = self.keyring + if key is None: + raise UnknownTSIGKey(f"key '{name}' unknown") + if key: + self.message.keyring = key + self.message.tsig_ctx = dns.tsig.validate( + self.parser.wire, + key, + absolute_name, + rd, + int(time.time()), + self.message.request_mac, + rr_start, + self.message.tsig_ctx, + self.multi, + ) + self.message.tsig = dns.rrset.from_rdata(absolute_name, 0, rd) + else: + rrset = self.message.find_rrset( + section, + name, + rdclass, # pyright: ignore + rdtype, + covers, + deleting, + True, + force_unique, + ) + if rd is not None: + if ttl > 0x7FFFFFFF: + ttl = 0 + rrset.add(rd, ttl) + except Exception as e: + if self.continue_on_error: + self._add_error(e) + self.parser.seek(rdata_start + rdlen) + else: + raise + + def read(self): + """Read a wire format DNS message and build a dns.message.Message + object.""" + + if self.parser.remaining() < 12: + raise ShortHeader + (id, flags, qcount, ancount, aucount, adcount) = self.parser.get_struct( + "!HHHHHH" + ) + factory = _message_factory_from_opcode(dns.opcode.from_flags(flags)) + self.message = factory(id=id) + self.message.flags = dns.flags.Flag(flags) + self.message.wire = self.parser.wire + self.initialize_message(self.message) + self.one_rr_per_rrset = self.message._get_one_rr_per_rrset( + self.one_rr_per_rrset + ) + try: + self._get_question(MessageSection.QUESTION, qcount) + if self.question_only: + return self.message + self._get_section(MessageSection.ANSWER, ancount) + self._get_section(MessageSection.AUTHORITY, aucount) + self._get_section(MessageSection.ADDITIONAL, adcount) + if not self.ignore_trailing and self.parser.remaining() != 0: + raise TrailingJunk + if self.multi and self.message.tsig_ctx and not self.message.had_tsig: + self.message.tsig_ctx.update(self.parser.wire) + except Exception as e: + if self.continue_on_error: + self._add_error(e) + else: + raise + return self.message + + +def from_wire( + wire: bytes, + keyring: Any | None = None, + request_mac: bytes | None = b"", + xfr: bool = False, + origin: dns.name.Name | None = None, + tsig_ctx: dns.tsig.HMACTSig | dns.tsig.GSSTSig | None = None, + multi: bool = False, + question_only: bool = False, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + raise_on_truncation: bool = False, + continue_on_error: bool = False, +) -> Message: + """Convert a DNS wire format message into a message object. + + *keyring*, a ``dns.tsig.Key``, ``dict``, ``bool``, or ``None``, the key or keyring + to use if the message is signed. If ``None`` or ``True``, then trying to decode + a message with a TSIG will fail as it cannot be validated. If ``False``, then + TSIG validation is disabled. + + *request_mac*, a ``bytes`` or ``None``. If the message is a response to a + TSIG-signed request, *request_mac* should be set to the MAC of that request. + + *xfr*, a ``bool``, should be set to ``True`` if this message is part of a zone + transfer. + + *origin*, a ``dns.name.Name`` or ``None``. If the message is part of a zone + transfer, *origin* should be the origin name of the zone. If not ``None``, names + will be relativized to the origin. + + *tsig_ctx*, a ``dns.tsig.HMACTSig`` or ``dns.tsig.GSSTSig`` object, the ongoing TSIG + context, used when validating zone transfers. + + *multi*, a ``bool``, should be set to ``True`` if this message is part of a multiple + message sequence. + + *question_only*, a ``bool``. If ``True``, read only up to the end of the question + section. + + *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own RRset. + + *ignore_trailing*, a ``bool``. If ``True``, ignore trailing junk at end of the + message. + + *raise_on_truncation*, a ``bool``. If ``True``, raise an exception if the TC bit is + set. + + *continue_on_error*, a ``bool``. If ``True``, try to continue parsing even if + errors occur. Erroneous rdata will be ignored. Errors will be accumulated as a + list of MessageError objects in the message's ``errors`` attribute. This option is + recommended only for DNS analysis tools, or for use in a server as part of an error + handling path. The default is ``False``. + + Raises ``dns.message.ShortHeader`` if the message is less than 12 octets long. + + Raises ``dns.message.TrailingJunk`` if there were octets in the message past the end + of the proper DNS message, and *ignore_trailing* is ``False``. + + Raises ``dns.message.BadEDNS`` if an OPT record was in the wrong section, or + occurred more than once. + + Raises ``dns.message.BadTSIG`` if a TSIG record was not the last record of the + additional data section. + + Raises ``dns.message.Truncated`` if the TC flag is set and *raise_on_truncation* is + ``True``. + + Returns a ``dns.message.Message``. + """ + + # We permit None for request_mac solely for backwards compatibility + if request_mac is None: + request_mac = b"" + + def initialize_message(message): + message.request_mac = request_mac + message.xfr = xfr + message.origin = origin + message.tsig_ctx = tsig_ctx + + reader = _WireReader( + wire, + initialize_message, + question_only, + one_rr_per_rrset, + ignore_trailing, + keyring, + multi, + continue_on_error, + ) + try: + m = reader.read() + except dns.exception.FormError: + if ( + reader.message + and (reader.message.flags & dns.flags.TC) + and raise_on_truncation + ): + raise Truncated(message=reader.message) + else: + raise + # Reading a truncated message might not have any errors, so we + # have to do this check here too. + if m.flags & dns.flags.TC and raise_on_truncation: + raise Truncated(message=m) + if continue_on_error: + m.errors = reader.errors + + return m + + +class _TextReader: + """Text format reader. + + tok: the tokenizer. + message: The message object being built. + DNS dynamic updates. + last_name: The most recently read name when building a message object. + one_rr_per_rrset: Put each RR into its own RRset? + origin: The origin for relative names + relativize: relativize names? + relativize_to: the origin to relativize to. + """ + + def __init__( + self, + text: str, + idna_codec: dns.name.IDNACodec | None, + one_rr_per_rrset: bool = False, + origin: dns.name.Name | None = None, + relativize: bool = True, + relativize_to: dns.name.Name | None = None, + ): + self.message: Message | None = None # mypy: ignore + self.tok = dns.tokenizer.Tokenizer(text, idna_codec=idna_codec) + self.last_name = None + self.one_rr_per_rrset = one_rr_per_rrset + self.origin = origin + self.relativize = relativize + self.relativize_to = relativize_to + self.id = None + self.edns = -1 + self.ednsflags = 0 + self.payload = DEFAULT_EDNS_PAYLOAD + self.rcode = None + self.opcode = dns.opcode.QUERY + self.flags = 0 + + def _header_line(self, _): + """Process one line from the text format header section.""" + + token = self.tok.get() + what = token.value + if what == "id": + self.id = self.tok.get_int() + elif what == "flags": + while True: + token = self.tok.get() + if not token.is_identifier(): + self.tok.unget(token) + break + self.flags = self.flags | dns.flags.from_text(token.value) + elif what == "edns": + self.edns = self.tok.get_int() + self.ednsflags = self.ednsflags | (self.edns << 16) + elif what == "eflags": + if self.edns < 0: + self.edns = 0 + while True: + token = self.tok.get() + if not token.is_identifier(): + self.tok.unget(token) + break + self.ednsflags = self.ednsflags | dns.flags.edns_from_text(token.value) + elif what == "payload": + self.payload = self.tok.get_int() + if self.edns < 0: + self.edns = 0 + elif what == "opcode": + text = self.tok.get_string() + self.opcode = dns.opcode.from_text(text) + self.flags = self.flags | dns.opcode.to_flags(self.opcode) + elif what == "rcode": + text = self.tok.get_string() + self.rcode = dns.rcode.from_text(text) + else: + raise UnknownHeaderField + self.tok.get_eol() + + def _question_line(self, section_number): + """Process one line from the text format question section.""" + + assert self.message is not None + section = self.message.sections[section_number] + token = self.tok.get(want_leading=True) + if not token.is_whitespace(): + self.last_name = self.tok.as_name( + token, self.message.origin, self.relativize, self.relativize_to + ) + name = self.last_name + if name is None: + raise NoPreviousName + token = self.tok.get() + if not token.is_identifier(): + raise dns.exception.SyntaxError + # Class + try: + rdclass = dns.rdataclass.from_text(token.value) + token = self.tok.get() + if not token.is_identifier(): + raise dns.exception.SyntaxError + except dns.exception.SyntaxError: + raise dns.exception.SyntaxError + except Exception: + rdclass = dns.rdataclass.IN + # Type + rdtype = dns.rdatatype.from_text(token.value) + (rdclass, rdtype, _, _) = self.message._parse_rr_header( + section_number, name, rdclass, rdtype + ) + self.message.find_rrset( + section, name, rdclass, rdtype, create=True, force_unique=True + ) + self.tok.get_eol() + + def _rr_line(self, section_number): + """Process one line from the text format answer, authority, or + additional data sections. + """ + + assert self.message is not None + section = self.message.sections[section_number] + # Name + token = self.tok.get(want_leading=True) + if not token.is_whitespace(): + self.last_name = self.tok.as_name( + token, self.message.origin, self.relativize, self.relativize_to + ) + name = self.last_name + if name is None: + raise NoPreviousName + token = self.tok.get() + if not token.is_identifier(): + raise dns.exception.SyntaxError + # TTL + try: + ttl = int(token.value, 0) + token = self.tok.get() + if not token.is_identifier(): + raise dns.exception.SyntaxError + except dns.exception.SyntaxError: + raise dns.exception.SyntaxError + except Exception: + ttl = 0 + # Class + try: + rdclass = dns.rdataclass.from_text(token.value) + token = self.tok.get() + if not token.is_identifier(): + raise dns.exception.SyntaxError + except dns.exception.SyntaxError: + raise dns.exception.SyntaxError + except Exception: + rdclass = dns.rdataclass.IN + # Type + rdtype = dns.rdatatype.from_text(token.value) + (rdclass, rdtype, deleting, empty) = self.message._parse_rr_header( + section_number, name, rdclass, rdtype + ) + token = self.tok.get() + if empty and not token.is_eol_or_eof(): + raise dns.exception.SyntaxError + if not empty and token.is_eol_or_eof(): + raise dns.exception.UnexpectedEnd + if not token.is_eol_or_eof(): + self.tok.unget(token) + rd = dns.rdata.from_text( + rdclass, + rdtype, + self.tok, + self.message.origin, + self.relativize, + self.relativize_to, + ) + covers = rd.covers() + else: + rd = None + covers = dns.rdatatype.NONE + rrset = self.message.find_rrset( + section, + name, + rdclass, + rdtype, + covers, + deleting, + True, + self.one_rr_per_rrset, + ) + if rd is not None: + rrset.add(rd, ttl) + + def _make_message(self): + factory = _message_factory_from_opcode(self.opcode) + message = factory(id=self.id) + message.flags = self.flags + if self.edns >= 0: + message.use_edns(self.edns, self.ednsflags, self.payload) + if self.rcode: + message.set_rcode(self.rcode) + if self.origin: + message.origin = self.origin + return message + + def read(self): + """Read a text format DNS message and build a dns.message.Message + object.""" + + line_method = self._header_line + section_number = None + while 1: + token = self.tok.get(True, True) + if token.is_eol_or_eof(): + break + if token.is_comment(): + u = token.value.upper() + if u == "HEADER": + line_method = self._header_line + + if self.message: + message = self.message + else: + # If we don't have a message, create one with the current + # opcode, so that we know which section names to parse. + message = self._make_message() + try: + section_number = message._section_enum.from_text(u) + # We found a section name. If we don't have a message, + # use the one we just created. + if not self.message: + self.message = message + self.one_rr_per_rrset = message._get_one_rr_per_rrset( + self.one_rr_per_rrset + ) + if section_number == MessageSection.QUESTION: + line_method = self._question_line + else: + line_method = self._rr_line + except Exception: + # It's just a comment. + pass + self.tok.get_eol() + continue + self.tok.unget(token) + line_method(section_number) + if not self.message: + self.message = self._make_message() + return self.message + + +def from_text( + text: str, + idna_codec: dns.name.IDNACodec | None = None, + one_rr_per_rrset: bool = False, + origin: dns.name.Name | None = None, + relativize: bool = True, + relativize_to: dns.name.Name | None = None, +) -> Message: + """Convert the text format message into a message object. + + The reader stops after reading the first blank line in the input to + facilitate reading multiple messages from a single file with + ``dns.message.from_file()``. + + *text*, a ``str``, the text format message. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder + is used. + + *one_rr_per_rrset*, a ``bool``. If ``True``, then each RR is put + into its own rrset. The default is ``False``. + + *origin*, a ``dns.name.Name`` (or ``None``), the + origin to use for relative names. + + *relativize*, a ``bool``. If true, name will be relativized. + + *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use + when relativizing names. If not set, the *origin* value will be used. + + Raises ``dns.message.UnknownHeaderField`` if a header is unknown. + + Raises ``dns.exception.SyntaxError`` if the text is badly formed. + + Returns a ``dns.message.Message object`` + """ + + # 'text' can also be a file, but we don't publish that fact + # since it's an implementation detail. The official file + # interface is from_file(). + + reader = _TextReader( + text, idna_codec, one_rr_per_rrset, origin, relativize, relativize_to + ) + return reader.read() + + +def from_file( + f: Any, + idna_codec: dns.name.IDNACodec | None = None, + one_rr_per_rrset: bool = False, +) -> Message: + """Read the next text format message from the specified file. + + Message blocks are separated by a single blank line. + + *f*, a ``file`` or ``str``. If *f* is text, it is treated as the + pathname of a file to open. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder + is used. + + *one_rr_per_rrset*, a ``bool``. If ``True``, then each RR is put + into its own rrset. The default is ``False``. + + Raises ``dns.message.UnknownHeaderField`` if a header is unknown. + + Raises ``dns.exception.SyntaxError`` if the text is badly formed. + + Returns a ``dns.message.Message object`` + """ + + if isinstance(f, str): + cm: contextlib.AbstractContextManager = open(f, encoding="utf-8") + else: + cm = contextlib.nullcontext(f) + with cm as f: + return from_text(f, idna_codec, one_rr_per_rrset) + assert False # for mypy lgtm[py/unreachable-statement] + + +def make_query( + qname: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str, + rdclass: dns.rdataclass.RdataClass | str = dns.rdataclass.IN, + use_edns: int | bool | None = None, + want_dnssec: bool = False, + ednsflags: int | None = None, + payload: int | None = None, + request_payload: int | None = None, + options: List[dns.edns.Option] | None = None, + idna_codec: dns.name.IDNACodec | None = None, + id: int | None = None, + flags: int = dns.flags.RD, + pad: int = 0, +) -> QueryMessage: + """Make a query message. + + The query name, type, and class may all be specified either + as objects of the appropriate type, or as strings. + + The query will have a randomly chosen query id, and its DNS flags + will be set to dns.flags.RD. + + qname, a ``dns.name.Name`` or ``str``, the query name. + + *rdtype*, an ``int`` or ``str``, the desired rdata type. + + *rdclass*, an ``int`` or ``str``, the desired rdata class; the default + is class IN. + + *use_edns*, an ``int``, ``bool`` or ``None``. The EDNS level to use; the + default is ``None``. If ``None``, EDNS will be enabled only if other + parameters (*ednsflags*, *payload*, *request_payload*, or *options*) are + set. + See the description of dns.message.Message.use_edns() for the possible + values for use_edns and their meanings. + + *want_dnssec*, a ``bool``. If ``True``, DNSSEC data is desired. + + *ednsflags*, an ``int``, the EDNS flag values. + + *payload*, an ``int``, is the EDNS sender's payload field, which is the + maximum size of UDP datagram the sender can handle. I.e. how big + a response to this message can be. + + *request_payload*, an ``int``, is the EDNS payload size to use when + sending this message. If not specified, defaults to the value of + *payload*. + + *options*, a list of ``dns.edns.Option`` objects or ``None``, the EDNS + options. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder + is used. + + *id*, an ``int`` or ``None``, the desired query id. The default is + ``None``, which generates a random query id. + + *flags*, an ``int``, the desired query flags. The default is + ``dns.flags.RD``. + + *pad*, a non-negative ``int``. If 0, the default, do not pad; otherwise add + padding bytes to make the message size a multiple of *pad*. Note that if + padding is non-zero, an EDNS PADDING option will always be added to the + message. + + Returns a ``dns.message.QueryMessage`` + """ + + if isinstance(qname, str): + qname = dns.name.from_text(qname, idna_codec=idna_codec) + rdtype = dns.rdatatype.RdataType.make(rdtype) + rdclass = dns.rdataclass.RdataClass.make(rdclass) + m = QueryMessage(id=id) + m.flags = dns.flags.Flag(flags) + m.find_rrset(m.question, qname, rdclass, rdtype, create=True, force_unique=True) + # only pass keywords on to use_edns if they have been set to a + # non-None value. Setting a field will turn EDNS on if it hasn't + # been configured. + kwargs: Dict[str, Any] = {} + if ednsflags is not None: + kwargs["ednsflags"] = ednsflags + if payload is not None: + kwargs["payload"] = payload + if request_payload is not None: + kwargs["request_payload"] = request_payload + if options is not None: + kwargs["options"] = options + if kwargs and use_edns is None: + use_edns = 0 + kwargs["edns"] = use_edns + kwargs["pad"] = pad + m.use_edns(**kwargs) + if want_dnssec: + m.want_dnssec(want_dnssec) + return m + + +class CopyMode(enum.Enum): + """ + How should sections be copied when making an update response? + """ + + NOTHING = 0 + QUESTION = 1 + EVERYTHING = 2 + + +def make_response( + query: Message, + recursion_available: bool = False, + our_payload: int = 8192, + fudge: int = 300, + tsig_error: int = 0, + pad: int | None = None, + copy_mode: CopyMode | None = None, +) -> Message: + """Make a message which is a response for the specified query. + The message returned is really a response skeleton; it has all of the infrastructure + required of a response, but none of the content. + + Response section(s) which are copied are shallow copies of the matching section(s) + in the query, so the query's RRsets should not be changed. + + *query*, a ``dns.message.Message``, the query to respond to. + + *recursion_available*, a ``bool``, should RA be set in the response? + + *our_payload*, an ``int``, the payload size to advertise in EDNS responses. + + *fudge*, an ``int``, the TSIG time fudge. + + *tsig_error*, an ``int``, the TSIG error. + + *pad*, a non-negative ``int`` or ``None``. If 0, the default, do not pad; otherwise + if not ``None`` add padding bytes to make the message size a multiple of *pad*. Note + that if padding is non-zero, an EDNS PADDING option will always be added to the + message. If ``None``, add padding following RFC 8467, namely if the request is + padded, pad the response to 468 otherwise do not pad. + + *copy_mode*, a ``dns.message.CopyMode`` or ``None``, determines how sections are + copied. The default, ``None`` copies sections according to the default for the + message's opcode, which is currently ``dns.message.CopyMode.QUESTION`` for all + opcodes. ``dns.message.CopyMode.QUESTION`` copies only the question section. + ``dns.message.CopyMode.EVERYTHING`` copies all sections other than OPT or TSIG + records, which are created appropriately if needed. ``dns.message.CopyMode.NOTHING`` + copies no sections; note that this mode is for server testing purposes and is + otherwise not recommended for use. In particular, ``dns.message.is_response()`` + will be ``False`` if you create a response this way and the rcode is not + ``FORMERR``, ``SERVFAIL``, ``NOTIMP``, or ``REFUSED``. + + Returns a ``dns.message.Message`` object whose specific class is appropriate for the + query. For example, if query is a ``dns.update.UpdateMessage``, the response will + be one too. + """ + + if query.flags & dns.flags.QR: + raise dns.exception.FormError("specified query message is not a query") + opcode = query.opcode() + factory = _message_factory_from_opcode(opcode) + response = factory(id=query.id) + response.flags = dns.flags.QR | (query.flags & dns.flags.RD) + if recursion_available: + response.flags |= dns.flags.RA + response.set_opcode(opcode) + if copy_mode is None: + copy_mode = CopyMode.QUESTION + if copy_mode != CopyMode.NOTHING: + response.question = list(query.question) + if copy_mode == CopyMode.EVERYTHING: + response.answer = list(query.answer) + response.authority = list(query.authority) + response.additional = list(query.additional) + if query.edns >= 0: + if pad is None: + # Set response padding per RFC 8467 + pad = 0 + for option in query.options: + if option.otype == dns.edns.OptionType.PADDING: + pad = 468 + response.use_edns(0, 0, our_payload, query.payload, pad=pad) + if query.had_tsig and query.keyring: + assert query.mac is not None + assert query.keyalgorithm is not None + response.use_tsig( + query.keyring, + query.keyname, + fudge, + None, + tsig_error, + b"", + query.keyalgorithm, + ) + response.request_mac = query.mac + return response + + +### BEGIN generated MessageSection constants + +QUESTION = MessageSection.QUESTION +ANSWER = MessageSection.ANSWER +AUTHORITY = MessageSection.AUTHORITY +ADDITIONAL = MessageSection.ADDITIONAL + +### END generated MessageSection constants diff --git a/venv/lib/python3.12/site-packages/dns/name.py b/venv/lib/python3.12/site-packages/dns/name.py new file mode 100644 index 0000000..45c8f45 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/name.py @@ -0,0 +1,1289 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS Names.""" + +import copy +import encodings.idna # type: ignore +import functools +import struct +from typing import Any, Callable, Dict, Iterable, Optional, Tuple + +import dns._features +import dns.enum +import dns.exception +import dns.immutable +import dns.wire + +# Dnspython will never access idna if the import fails, but pyright can't figure +# that out, so... +# +# pyright: reportAttributeAccessIssue = false, reportPossiblyUnboundVariable = false + +if dns._features.have("idna"): + import idna # type: ignore + + have_idna_2008 = True +else: # pragma: no cover + have_idna_2008 = False + + +CompressType = Dict["Name", int] + + +class NameRelation(dns.enum.IntEnum): + """Name relation result from fullcompare().""" + + # This is an IntEnum for backwards compatibility in case anyone + # has hardwired the constants. + + #: The compared names have no relationship to each other. + NONE = 0 + #: the first name is a superdomain of the second. + SUPERDOMAIN = 1 + #: The first name is a subdomain of the second. + SUBDOMAIN = 2 + #: The compared names are equal. + EQUAL = 3 + #: The compared names have a common ancestor. + COMMONANCESTOR = 4 + + @classmethod + def _maximum(cls): + return cls.COMMONANCESTOR # pragma: no cover + + @classmethod + def _short_name(cls): + return cls.__name__ # pragma: no cover + + +# Backwards compatibility +NAMERELN_NONE = NameRelation.NONE +NAMERELN_SUPERDOMAIN = NameRelation.SUPERDOMAIN +NAMERELN_SUBDOMAIN = NameRelation.SUBDOMAIN +NAMERELN_EQUAL = NameRelation.EQUAL +NAMERELN_COMMONANCESTOR = NameRelation.COMMONANCESTOR + + +class EmptyLabel(dns.exception.SyntaxError): + """A DNS label is empty.""" + + +class BadEscape(dns.exception.SyntaxError): + """An escaped code in a text format of DNS name is invalid.""" + + +class BadPointer(dns.exception.FormError): + """A DNS compression pointer points forward instead of backward.""" + + +class BadLabelType(dns.exception.FormError): + """The label type in DNS name wire format is unknown.""" + + +class NeedAbsoluteNameOrOrigin(dns.exception.DNSException): + """An attempt was made to convert a non-absolute name to + wire when there was also a non-absolute (or missing) origin.""" + + +class NameTooLong(dns.exception.FormError): + """A DNS name is > 255 octets long.""" + + +class LabelTooLong(dns.exception.SyntaxError): + """A DNS label is > 63 octets long.""" + + +class AbsoluteConcatenation(dns.exception.DNSException): + """An attempt was made to append anything other than the + empty name to an absolute DNS name.""" + + +class NoParent(dns.exception.DNSException): + """An attempt was made to get the parent of the root name + or the empty name.""" + + +class NoIDNA2008(dns.exception.DNSException): + """IDNA 2008 processing was requested but the idna module is not + available.""" + + +class IDNAException(dns.exception.DNSException): + """IDNA processing raised an exception.""" + + supp_kwargs = {"idna_exception"} + fmt = "IDNA processing exception: {idna_exception}" + + # We do this as otherwise mypy complains about unexpected keyword argument + # idna_exception + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class NeedSubdomainOfOrigin(dns.exception.DNSException): + """An absolute name was provided that is not a subdomain of the specified origin.""" + + +_escaped = b'"().;\\@$' +_escaped_text = '"().;\\@$' + + +def _escapify(label: bytes | str) -> str: + """Escape the characters in label which need it. + @returns: the escaped string + @rtype: string""" + if isinstance(label, bytes): + # Ordinary DNS label mode. Escape special characters and values + # < 0x20 or > 0x7f. + text = "" + for c in label: + if c in _escaped: + text += "\\" + chr(c) + elif c > 0x20 and c < 0x7F: + text += chr(c) + else: + text += f"\\{c:03d}" + return text + + # Unicode label mode. Escape only special characters and values < 0x20 + text = "" + for uc in label: + if uc in _escaped_text: + text += "\\" + uc + elif uc <= "\x20": + text += f"\\{ord(uc):03d}" + else: + text += uc + return text + + +class IDNACodec: + """Abstract base class for IDNA encoder/decoders.""" + + def __init__(self): + pass + + def is_idna(self, label: bytes) -> bool: + return label.lower().startswith(b"xn--") + + def encode(self, label: str) -> bytes: + raise NotImplementedError # pragma: no cover + + def decode(self, label: bytes) -> str: + # We do not apply any IDNA policy on decode. + if self.is_idna(label): + try: + slabel = label[4:].decode("punycode") + return _escapify(slabel) + except Exception as e: + raise IDNAException(idna_exception=e) + else: + return _escapify(label) + + +class IDNA2003Codec(IDNACodec): + """IDNA 2003 encoder/decoder.""" + + def __init__(self, strict_decode: bool = False): + """Initialize the IDNA 2003 encoder/decoder. + + *strict_decode* is a ``bool``. If `True`, then IDNA2003 checking + is done when decoding. This can cause failures if the name + was encoded with IDNA2008. The default is `False`. + """ + + super().__init__() + self.strict_decode = strict_decode + + def encode(self, label: str) -> bytes: + """Encode *label*.""" + + if label == "": + return b"" + try: + return encodings.idna.ToASCII(label) + except UnicodeError: + raise LabelTooLong + + def decode(self, label: bytes) -> str: + """Decode *label*.""" + if not self.strict_decode: + return super().decode(label) + if label == b"": + return "" + try: + return _escapify(encodings.idna.ToUnicode(label)) + except Exception as e: + raise IDNAException(idna_exception=e) + + +class IDNA2008Codec(IDNACodec): + """IDNA 2008 encoder/decoder.""" + + def __init__( + self, + uts_46: bool = False, + transitional: bool = False, + allow_pure_ascii: bool = False, + strict_decode: bool = False, + ): + """Initialize the IDNA 2008 encoder/decoder. + + *uts_46* is a ``bool``. If True, apply Unicode IDNA + compatibility processing as described in Unicode Technical + Standard #46 (https://unicode.org/reports/tr46/). + If False, do not apply the mapping. The default is False. + + *transitional* is a ``bool``: If True, use the + "transitional" mode described in Unicode Technical Standard + #46. The default is False. + + *allow_pure_ascii* is a ``bool``. If True, then a label which + consists of only ASCII characters is allowed. This is less + strict than regular IDNA 2008, but is also necessary for mixed + names, e.g. a name with starting with "_sip._tcp." and ending + in an IDN suffix which would otherwise be disallowed. The + default is False. + + *strict_decode* is a ``bool``: If True, then IDNA2008 checking + is done when decoding. This can cause failures if the name + was encoded with IDNA2003. The default is False. + """ + super().__init__() + self.uts_46 = uts_46 + self.transitional = transitional + self.allow_pure_ascii = allow_pure_ascii + self.strict_decode = strict_decode + + def encode(self, label: str) -> bytes: + if label == "": + return b"" + if self.allow_pure_ascii and is_all_ascii(label): + encoded = label.encode("ascii") + if len(encoded) > 63: + raise LabelTooLong + return encoded + if not have_idna_2008: + raise NoIDNA2008 + try: + if self.uts_46: + # pylint: disable=possibly-used-before-assignment + label = idna.uts46_remap(label, False, self.transitional) + return idna.alabel(label) + except idna.IDNAError as e: + if e.args[0] == "Label too long": + raise LabelTooLong + else: + raise IDNAException(idna_exception=e) + + def decode(self, label: bytes) -> str: + if not self.strict_decode: + return super().decode(label) + if label == b"": + return "" + if not have_idna_2008: + raise NoIDNA2008 + try: + ulabel = idna.ulabel(label) + if self.uts_46: + ulabel = idna.uts46_remap(ulabel, False, self.transitional) + return _escapify(ulabel) + except (idna.IDNAError, UnicodeError) as e: + raise IDNAException(idna_exception=e) + + +IDNA_2003_Practical = IDNA2003Codec(False) +IDNA_2003_Strict = IDNA2003Codec(True) +IDNA_2003 = IDNA_2003_Practical +IDNA_2008_Practical = IDNA2008Codec(True, False, True, False) +IDNA_2008_UTS_46 = IDNA2008Codec(True, False, False, False) +IDNA_2008_Strict = IDNA2008Codec(False, False, False, True) +IDNA_2008_Transitional = IDNA2008Codec(True, True, False, False) +IDNA_2008 = IDNA_2008_Practical + + +def _validate_labels(labels: Tuple[bytes, ...]) -> None: + """Check for empty labels in the middle of a label sequence, + labels that are too long, and for too many labels. + + Raises ``dns.name.NameTooLong`` if the name as a whole is too long. + + Raises ``dns.name.EmptyLabel`` if a label is empty (i.e. the root + label) and appears in a position other than the end of the label + sequence + + """ + + l = len(labels) + total = 0 + i = -1 + j = 0 + for label in labels: + ll = len(label) + total += ll + 1 + if ll > 63: + raise LabelTooLong + if i < 0 and label == b"": + i = j + j += 1 + if total > 255: + raise NameTooLong + if i >= 0 and i != l - 1: + raise EmptyLabel + + +def _maybe_convert_to_binary(label: bytes | str) -> bytes: + """If label is ``str``, convert it to ``bytes``. If it is already + ``bytes`` just return it. + + """ + + if isinstance(label, bytes): + return label + if isinstance(label, str): + return label.encode() + raise ValueError # pragma: no cover + + +@dns.immutable.immutable +class Name: + """A DNS name. + + The dns.name.Name class represents a DNS name as a tuple of + labels. Each label is a ``bytes`` in DNS wire format. Instances + of the class are immutable. + """ + + __slots__ = ["labels"] + + def __init__(self, labels: Iterable[bytes | str]): + """*labels* is any iterable whose values are ``str`` or ``bytes``.""" + + blabels = [_maybe_convert_to_binary(x) for x in labels] + self.labels = tuple(blabels) + _validate_labels(self.labels) + + def __copy__(self): + return Name(self.labels) + + def __deepcopy__(self, memo): + return Name(copy.deepcopy(self.labels, memo)) + + def __getstate__(self): + # Names can be pickled + return {"labels": self.labels} + + def __setstate__(self, state): + super().__setattr__("labels", state["labels"]) + _validate_labels(self.labels) + + def is_absolute(self) -> bool: + """Is the most significant label of this name the root label? + + Returns a ``bool``. + """ + + return len(self.labels) > 0 and self.labels[-1] == b"" + + def is_wild(self) -> bool: + """Is this name wild? (I.e. Is the least significant label '*'?) + + Returns a ``bool``. + """ + + return len(self.labels) > 0 and self.labels[0] == b"*" + + def __hash__(self) -> int: + """Return a case-insensitive hash of the name. + + Returns an ``int``. + """ + + h = 0 + for label in self.labels: + for c in label.lower(): + h += (h << 3) + c + return h + + def fullcompare(self, other: "Name") -> Tuple[NameRelation, int, int]: + """Compare two names, returning a 3-tuple + ``(relation, order, nlabels)``. + + *relation* describes the relation ship between the names, + and is one of: ``dns.name.NameRelation.NONE``, + ``dns.name.NameRelation.SUPERDOMAIN``, ``dns.name.NameRelation.SUBDOMAIN``, + ``dns.name.NameRelation.EQUAL``, or ``dns.name.NameRelation.COMMONANCESTOR``. + + *order* is < 0 if *self* < *other*, > 0 if *self* > *other*, and == + 0 if *self* == *other*. A relative name is always less than an + absolute name. If both names have the same relativity, then + the DNSSEC order relation is used to order them. + + *nlabels* is the number of significant labels that the two names + have in common. + + Here are some examples. Names ending in "." are absolute names, + those not ending in "." are relative names. + + ============= ============= =========== ===== ======= + self other relation order nlabels + ============= ============= =========== ===== ======= + www.example. www.example. equal 0 3 + www.example. example. subdomain > 0 2 + example. www.example. superdomain < 0 2 + example1.com. example2.com. common anc. < 0 2 + example1 example2. none < 0 0 + example1. example2 none > 0 0 + ============= ============= =========== ===== ======= + """ + + sabs = self.is_absolute() + oabs = other.is_absolute() + if sabs != oabs: + if sabs: + return (NameRelation.NONE, 1, 0) + else: + return (NameRelation.NONE, -1, 0) + l1 = len(self.labels) + l2 = len(other.labels) + ldiff = l1 - l2 + if ldiff < 0: + l = l1 + else: + l = l2 + + order = 0 + nlabels = 0 + namereln = NameRelation.NONE + while l > 0: + l -= 1 + l1 -= 1 + l2 -= 1 + label1 = self.labels[l1].lower() + label2 = other.labels[l2].lower() + if label1 < label2: + order = -1 + if nlabels > 0: + namereln = NameRelation.COMMONANCESTOR + return (namereln, order, nlabels) + elif label1 > label2: + order = 1 + if nlabels > 0: + namereln = NameRelation.COMMONANCESTOR + return (namereln, order, nlabels) + nlabels += 1 + order = ldiff + if ldiff < 0: + namereln = NameRelation.SUPERDOMAIN + elif ldiff > 0: + namereln = NameRelation.SUBDOMAIN + else: + namereln = NameRelation.EQUAL + return (namereln, order, nlabels) + + def is_subdomain(self, other: "Name") -> bool: + """Is self a subdomain of other? + + Note that the notion of subdomain includes equality, e.g. + "dnspython.org" is a subdomain of itself. + + Returns a ``bool``. + """ + + (nr, _, _) = self.fullcompare(other) + if nr == NameRelation.SUBDOMAIN or nr == NameRelation.EQUAL: + return True + return False + + def is_superdomain(self, other: "Name") -> bool: + """Is self a superdomain of other? + + Note that the notion of superdomain includes equality, e.g. + "dnspython.org" is a superdomain of itself. + + Returns a ``bool``. + """ + + (nr, _, _) = self.fullcompare(other) + if nr == NameRelation.SUPERDOMAIN or nr == NameRelation.EQUAL: + return True + return False + + def canonicalize(self) -> "Name": + """Return a name which is equal to the current name, but is in + DNSSEC canonical form. + """ + + return Name([x.lower() for x in self.labels]) + + def __eq__(self, other): + if isinstance(other, Name): + return self.fullcompare(other)[1] == 0 + else: + return False + + def __ne__(self, other): + if isinstance(other, Name): + return self.fullcompare(other)[1] != 0 + else: + return True + + def __lt__(self, other): + if isinstance(other, Name): + return self.fullcompare(other)[1] < 0 + else: + return NotImplemented + + def __le__(self, other): + if isinstance(other, Name): + return self.fullcompare(other)[1] <= 0 + else: + return NotImplemented + + def __ge__(self, other): + if isinstance(other, Name): + return self.fullcompare(other)[1] >= 0 + else: + return NotImplemented + + def __gt__(self, other): + if isinstance(other, Name): + return self.fullcompare(other)[1] > 0 + else: + return NotImplemented + + def __repr__(self): + return "" + + def __str__(self): + return self.to_text(False) + + def to_text(self, omit_final_dot: bool = False) -> str: + """Convert name to DNS text format. + + *omit_final_dot* is a ``bool``. If True, don't emit the final + dot (denoting the root label) for absolute names. The default + is False. + + Returns a ``str``. + """ + + if len(self.labels) == 0: + return "@" + if len(self.labels) == 1 and self.labels[0] == b"": + return "." + if omit_final_dot and self.is_absolute(): + l = self.labels[:-1] + else: + l = self.labels + s = ".".join(map(_escapify, l)) + return s + + def to_unicode( + self, omit_final_dot: bool = False, idna_codec: IDNACodec | None = None + ) -> str: + """Convert name to Unicode text format. + + IDN ACE labels are converted to Unicode. + + *omit_final_dot* is a ``bool``. If True, don't emit the final + dot (denoting the root label) for absolute names. The default + is False. + *idna_codec* specifies the IDNA encoder/decoder. If None, the + dns.name.IDNA_2003_Practical encoder/decoder is used. + The IDNA_2003_Practical decoder does + not impose any policy, it just decodes punycode, so if you + don't want checking for compliance, you can use this decoder + for IDNA2008 as well. + + Returns a ``str``. + """ + + if len(self.labels) == 0: + return "@" + if len(self.labels) == 1 and self.labels[0] == b"": + return "." + if omit_final_dot and self.is_absolute(): + l = self.labels[:-1] + else: + l = self.labels + if idna_codec is None: + idna_codec = IDNA_2003_Practical + return ".".join([idna_codec.decode(x) for x in l]) + + def to_digestable(self, origin: Optional["Name"] = None) -> bytes: + """Convert name to a format suitable for digesting in hashes. + + The name is canonicalized and converted to uncompressed wire + format. All names in wire format are absolute. If the name + is a relative name, then an origin must be supplied. + + *origin* is a ``dns.name.Name`` or ``None``. If the name is + relative and origin is not ``None``, then origin will be appended + to the name. + + Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is + relative and no origin was provided. + + Returns a ``bytes``. + """ + + digest = self.to_wire(origin=origin, canonicalize=True) + assert digest is not None + return digest + + def to_wire( + self, + file: Any | None = None, + compress: CompressType | None = None, + origin: Optional["Name"] = None, + canonicalize: bool = False, + ) -> bytes | None: + """Convert name to wire format, possibly compressing it. + + *file* is the file where the name is emitted (typically an + io.BytesIO file). If ``None`` (the default), a ``bytes`` + containing the wire name will be returned. + + *compress*, a ``dict``, is the compression table to use. If + ``None`` (the default), names will not be compressed. Note that + the compression code assumes that compression offset 0 is the + start of *file*, and thus compression will not be correct + if this is not the case. + + *origin* is a ``dns.name.Name`` or ``None``. If the name is + relative and origin is not ``None``, then *origin* will be appended + to it. + + *canonicalize*, a ``bool``, indicates whether the name should + be canonicalized; that is, converted to a format suitable for + digesting in hashes. + + Raises ``dns.name.NeedAbsoluteNameOrOrigin`` if the name is + relative and no origin was provided. + + Returns a ``bytes`` or ``None``. + """ + + if file is None: + out = bytearray() + for label in self.labels: + out.append(len(label)) + if canonicalize: + out += label.lower() + else: + out += label + if not self.is_absolute(): + if origin is None or not origin.is_absolute(): + raise NeedAbsoluteNameOrOrigin + for label in origin.labels: + out.append(len(label)) + if canonicalize: + out += label.lower() + else: + out += label + return bytes(out) + + labels: Iterable[bytes] + if not self.is_absolute(): + if origin is None or not origin.is_absolute(): + raise NeedAbsoluteNameOrOrigin + labels = list(self.labels) + labels.extend(list(origin.labels)) + else: + labels = self.labels + i = 0 + for label in labels: + n = Name(labels[i:]) + i += 1 + if compress is not None: + pos = compress.get(n) + else: + pos = None + if pos is not None: + value = 0xC000 + pos + s = struct.pack("!H", value) + file.write(s) + break + else: + if compress is not None and len(n) > 1: + pos = file.tell() + if pos <= 0x3FFF: + compress[n] = pos + l = len(label) + file.write(struct.pack("!B", l)) + if l > 0: + if canonicalize: + file.write(label.lower()) + else: + file.write(label) + return None + + def __len__(self) -> int: + """The length of the name (in labels). + + Returns an ``int``. + """ + + return len(self.labels) + + def __getitem__(self, index): + return self.labels[index] + + def __add__(self, other): + return self.concatenate(other) + + def __sub__(self, other): + return self.relativize(other) + + def split(self, depth: int) -> Tuple["Name", "Name"]: + """Split a name into a prefix and suffix names at the specified depth. + + *depth* is an ``int`` specifying the number of labels in the suffix + + Raises ``ValueError`` if *depth* was not >= 0 and <= the length of the + name. + + Returns the tuple ``(prefix, suffix)``. + """ + + l = len(self.labels) + if depth == 0: + return (self, dns.name.empty) + elif depth == l: + return (dns.name.empty, self) + elif depth < 0 or depth > l: + raise ValueError("depth must be >= 0 and <= the length of the name") + return (Name(self[:-depth]), Name(self[-depth:])) + + def concatenate(self, other: "Name") -> "Name": + """Return a new name which is the concatenation of self and other. + + Raises ``dns.name.AbsoluteConcatenation`` if the name is + absolute and *other* is not the empty name. + + Returns a ``dns.name.Name``. + """ + + if self.is_absolute() and len(other) > 0: + raise AbsoluteConcatenation + labels = list(self.labels) + labels.extend(list(other.labels)) + return Name(labels) + + def relativize(self, origin: "Name") -> "Name": + """If the name is a subdomain of *origin*, return a new name which is + the name relative to origin. Otherwise return the name. + + For example, relativizing ``www.dnspython.org.`` to origin + ``dnspython.org.`` returns the name ``www``. Relativizing ``example.`` + to origin ``dnspython.org.`` returns ``example.``. + + Returns a ``dns.name.Name``. + """ + + if origin is not None and self.is_subdomain(origin): + return Name(self[: -len(origin)]) + else: + return self + + def derelativize(self, origin: "Name") -> "Name": + """If the name is a relative name, return a new name which is the + concatenation of the name and origin. Otherwise return the name. + + For example, derelativizing ``www`` to origin ``dnspython.org.`` + returns the name ``www.dnspython.org.``. Derelativizing ``example.`` + to origin ``dnspython.org.`` returns ``example.``. + + Returns a ``dns.name.Name``. + """ + + if not self.is_absolute(): + return self.concatenate(origin) + else: + return self + + def choose_relativity( + self, origin: Optional["Name"] = None, relativize: bool = True + ) -> "Name": + """Return a name with the relativity desired by the caller. + + If *origin* is ``None``, then the name is returned. + Otherwise, if *relativize* is ``True`` the name is + relativized, and if *relativize* is ``False`` the name is + derelativized. + + Returns a ``dns.name.Name``. + """ + + if origin: + if relativize: + return self.relativize(origin) + else: + return self.derelativize(origin) + else: + return self + + def parent(self) -> "Name": + """Return the parent of the name. + + For example, the parent of ``www.dnspython.org.`` is ``dnspython.org``. + + Raises ``dns.name.NoParent`` if the name is either the root name or the + empty name, and thus has no parent. + + Returns a ``dns.name.Name``. + """ + + if self == root or self == empty: + raise NoParent + return Name(self.labels[1:]) + + def predecessor(self, origin: "Name", prefix_ok: bool = True) -> "Name": + """Return the maximal predecessor of *name* in the DNSSEC ordering in the zone + whose origin is *origin*, or return the longest name under *origin* if the + name is origin (i.e. wrap around to the longest name, which may still be + *origin* due to length considerations. + + The relativity of the name is preserved, so if this name is relative + then the method will return a relative name, and likewise if this name + is absolute then the predecessor will be absolute. + + *prefix_ok* indicates if prefixing labels is allowed, and + defaults to ``True``. Normally it is good to allow this, but if computing + a maximal predecessor at a zone cut point then ``False`` must be specified. + """ + return _handle_relativity_and_call( + _absolute_predecessor, self, origin, prefix_ok + ) + + def successor(self, origin: "Name", prefix_ok: bool = True) -> "Name": + """Return the minimal successor of *name* in the DNSSEC ordering in the zone + whose origin is *origin*, or return *origin* if the successor cannot be + computed due to name length limitations. + + Note that *origin* is returned in the "too long" cases because wrapping + around to the origin is how NSEC records express "end of the zone". + + The relativity of the name is preserved, so if this name is relative + then the method will return a relative name, and likewise if this name + is absolute then the successor will be absolute. + + *prefix_ok* indicates if prefixing a new minimal label is allowed, and + defaults to ``True``. Normally it is good to allow this, but if computing + a minimal successor at a zone cut point then ``False`` must be specified. + """ + return _handle_relativity_and_call(_absolute_successor, self, origin, prefix_ok) + + +#: The root name, '.' +root = Name([b""]) + +#: The empty name. +empty = Name([]) + + +def from_unicode( + text: str, origin: Name | None = root, idna_codec: IDNACodec | None = None +) -> Name: + """Convert unicode text into a Name object. + + Labels are encoded in IDN ACE form according to rules specified by + the IDNA codec. + + *text*, a ``str``, is the text to convert into a name. + + *origin*, a ``dns.name.Name``, specifies the origin to + append to non-absolute names. The default is the root name. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder + is used. + + Returns a ``dns.name.Name``. + """ + + if not isinstance(text, str): + raise ValueError("input to from_unicode() must be a unicode string") + if not (origin is None or isinstance(origin, Name)): + raise ValueError("origin must be a Name or None") + labels = [] + label = "" + escaping = False + edigits = 0 + total = 0 + if idna_codec is None: + idna_codec = IDNA_2003 + if text == "@": + text = "" + if text: + if text in [".", "\u3002", "\uff0e", "\uff61"]: + return Name([b""]) # no Unicode "u" on this constant! + for c in text: + if escaping: + if edigits == 0: + if c.isdigit(): + total = int(c) + edigits += 1 + else: + label += c + escaping = False + else: + if not c.isdigit(): + raise BadEscape + total *= 10 + total += int(c) + edigits += 1 + if edigits == 3: + escaping = False + label += chr(total) + elif c in [".", "\u3002", "\uff0e", "\uff61"]: + if len(label) == 0: + raise EmptyLabel + labels.append(idna_codec.encode(label)) + label = "" + elif c == "\\": + escaping = True + edigits = 0 + total = 0 + else: + label += c + if escaping: + raise BadEscape + if len(label) > 0: + labels.append(idna_codec.encode(label)) + else: + labels.append(b"") + + if (len(labels) == 0 or labels[-1] != b"") and origin is not None: + labels.extend(list(origin.labels)) + return Name(labels) + + +def is_all_ascii(text: str) -> bool: + for c in text: + if ord(c) > 0x7F: + return False + return True + + +def from_text( + text: bytes | str, + origin: Name | None = root, + idna_codec: IDNACodec | None = None, +) -> Name: + """Convert text into a Name object. + + *text*, a ``bytes`` or ``str``, is the text to convert into a name. + + *origin*, a ``dns.name.Name``, specifies the origin to + append to non-absolute names. The default is the root name. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder + is used. + + Returns a ``dns.name.Name``. + """ + + if isinstance(text, str): + if not is_all_ascii(text): + # Some codepoint in the input text is > 127, so IDNA applies. + return from_unicode(text, origin, idna_codec) + # The input is all ASCII, so treat this like an ordinary non-IDNA + # domain name. Note that "all ASCII" is about the input text, + # not the codepoints in the domain name. E.g. if text has value + # + # r'\150\151\152\153\154\155\156\157\158\159' + # + # then it's still "all ASCII" even though the domain name has + # codepoints > 127. + text = text.encode("ascii") + if not isinstance(text, bytes): + raise ValueError("input to from_text() must be a string") + if not (origin is None or isinstance(origin, Name)): + raise ValueError("origin must be a Name or None") + labels = [] + label = b"" + escaping = False + edigits = 0 + total = 0 + if text == b"@": + text = b"" + if text: + if text == b".": + return Name([b""]) + for c in text: + byte_ = struct.pack("!B", c) + if escaping: + if edigits == 0: + if byte_.isdigit(): + total = int(byte_) + edigits += 1 + else: + label += byte_ + escaping = False + else: + if not byte_.isdigit(): + raise BadEscape + total *= 10 + total += int(byte_) + edigits += 1 + if edigits == 3: + escaping = False + label += struct.pack("!B", total) + elif byte_ == b".": + if len(label) == 0: + raise EmptyLabel + labels.append(label) + label = b"" + elif byte_ == b"\\": + escaping = True + edigits = 0 + total = 0 + else: + label += byte_ + if escaping: + raise BadEscape + if len(label) > 0: + labels.append(label) + else: + labels.append(b"") + if (len(labels) == 0 or labels[-1] != b"") and origin is not None: + labels.extend(list(origin.labels)) + return Name(labels) + + +# we need 'dns.wire.Parser' quoted as dns.name and dns.wire depend on each other. + + +def from_wire_parser(parser: "dns.wire.Parser") -> Name: + """Convert possibly compressed wire format into a Name. + + *parser* is a dns.wire.Parser. + + Raises ``dns.name.BadPointer`` if a compression pointer did not + point backwards in the message. + + Raises ``dns.name.BadLabelType`` if an invalid label type was encountered. + + Returns a ``dns.name.Name`` + """ + + labels = [] + biggest_pointer = parser.current + with parser.restore_furthest(): + count = parser.get_uint8() + while count != 0: + if count < 64: + labels.append(parser.get_bytes(count)) + elif count >= 192: + current = (count & 0x3F) * 256 + parser.get_uint8() + if current >= biggest_pointer: + raise BadPointer + biggest_pointer = current + parser.seek(current) + else: + raise BadLabelType + count = parser.get_uint8() + labels.append(b"") + return Name(labels) + + +def from_wire(message: bytes, current: int) -> Tuple[Name, int]: + """Convert possibly compressed wire format into a Name. + + *message* is a ``bytes`` containing an entire DNS message in DNS + wire form. + + *current*, an ``int``, is the offset of the beginning of the name + from the start of the message + + Raises ``dns.name.BadPointer`` if a compression pointer did not + point backwards in the message. + + Raises ``dns.name.BadLabelType`` if an invalid label type was encountered. + + Returns a ``(dns.name.Name, int)`` tuple consisting of the name + that was read and the number of bytes of the wire format message + which were consumed reading it. + """ + + if not isinstance(message, bytes): + raise ValueError("input to from_wire() must be a byte string") + parser = dns.wire.Parser(message, current) + name = from_wire_parser(parser) + return (name, parser.current - current) + + +# RFC 4471 Support + +_MINIMAL_OCTET = b"\x00" +_MINIMAL_OCTET_VALUE = ord(_MINIMAL_OCTET) +_SUCCESSOR_PREFIX = Name([_MINIMAL_OCTET]) +_MAXIMAL_OCTET = b"\xff" +_MAXIMAL_OCTET_VALUE = ord(_MAXIMAL_OCTET) +_AT_SIGN_VALUE = ord("@") +_LEFT_SQUARE_BRACKET_VALUE = ord("[") + + +def _wire_length(labels): + return functools.reduce(lambda v, x: v + len(x) + 1, labels, 0) + + +def _pad_to_max_name(name): + needed = 255 - _wire_length(name.labels) + new_labels = [] + while needed > 64: + new_labels.append(_MAXIMAL_OCTET * 63) + needed -= 64 + if needed >= 2: + new_labels.append(_MAXIMAL_OCTET * (needed - 1)) + # Note we're already maximal in the needed == 1 case as while we'd like + # to add one more byte as a new label, we can't, as adding a new non-empty + # label requires at least 2 bytes. + new_labels = list(reversed(new_labels)) + new_labels.extend(name.labels) + return Name(new_labels) + + +def _pad_to_max_label(label, suffix_labels): + length = len(label) + # We have to subtract one here to account for the length byte of label. + remaining = 255 - _wire_length(suffix_labels) - length - 1 + if remaining <= 0: + # Shouldn't happen! + return label + needed = min(63 - length, remaining) + return label + _MAXIMAL_OCTET * needed + + +def _absolute_predecessor(name: Name, origin: Name, prefix_ok: bool) -> Name: + # This is the RFC 4471 predecessor algorithm using the "absolute method" of section + # 3.1.1. + # + # Our caller must ensure that the name and origin are absolute, and that name is a + # subdomain of origin. + if name == origin: + return _pad_to_max_name(name) + least_significant_label = name[0] + if least_significant_label == _MINIMAL_OCTET: + return name.parent() + least_octet = least_significant_label[-1] + suffix_labels = name.labels[1:] + if least_octet == _MINIMAL_OCTET_VALUE: + new_labels = [least_significant_label[:-1]] + else: + octets = bytearray(least_significant_label) + octet = octets[-1] + if octet == _LEFT_SQUARE_BRACKET_VALUE: + octet = _AT_SIGN_VALUE + else: + octet -= 1 + octets[-1] = octet + least_significant_label = bytes(octets) + new_labels = [_pad_to_max_label(least_significant_label, suffix_labels)] + new_labels.extend(suffix_labels) + name = Name(new_labels) + if prefix_ok: + return _pad_to_max_name(name) + else: + return name + + +def _absolute_successor(name: Name, origin: Name, prefix_ok: bool) -> Name: + # This is the RFC 4471 successor algorithm using the "absolute method" of section + # 3.1.2. + # + # Our caller must ensure that the name and origin are absolute, and that name is a + # subdomain of origin. + if prefix_ok: + # Try prefixing \000 as new label + try: + return _SUCCESSOR_PREFIX.concatenate(name) + except NameTooLong: + pass + while name != origin: + # Try extending the least significant label. + least_significant_label = name[0] + if len(least_significant_label) < 63: + # We may be able to extend the least label with a minimal additional byte. + # This is only "may" because we could have a maximal length name even though + # the least significant label isn't maximally long. + new_labels = [least_significant_label + _MINIMAL_OCTET] + new_labels.extend(name.labels[1:]) + try: + return dns.name.Name(new_labels) + except dns.name.NameTooLong: + pass + # We can't extend the label either, so we'll try to increment the least + # signficant non-maximal byte in it. + octets = bytearray(least_significant_label) + # We do this reversed iteration with an explicit indexing variable because + # if we find something to increment, we're going to want to truncate everything + # to the right of it. + for i in range(len(octets) - 1, -1, -1): + octet = octets[i] + if octet == _MAXIMAL_OCTET_VALUE: + # We can't increment this, so keep looking. + continue + # Finally, something we can increment. We have to apply a special rule for + # incrementing "@", sending it to "[", because RFC 4034 6.1 says that when + # comparing names, uppercase letters compare as if they were their + # lower-case equivalents. If we increment "@" to "A", then it would compare + # as "a", which is after "[", "\", "]", "^", "_", and "`", so we would have + # skipped the most minimal successor, namely "[". + if octet == _AT_SIGN_VALUE: + octet = _LEFT_SQUARE_BRACKET_VALUE + else: + octet += 1 + octets[i] = octet + # We can now truncate all of the maximal values we skipped (if any) + new_labels = [bytes(octets[: i + 1])] + new_labels.extend(name.labels[1:]) + # We haven't changed the length of the name, so the Name constructor will + # always work. + return Name(new_labels) + # We couldn't increment, so chop off the least significant label and try + # again. + name = name.parent() + + # We couldn't increment at all, so return the origin, as wrapping around is the + # DNSSEC way. + return origin + + +def _handle_relativity_and_call( + function: Callable[[Name, Name, bool], Name], + name: Name, + origin: Name, + prefix_ok: bool, +) -> Name: + # Make "name" absolute if needed, ensure that the origin is absolute, + # call function(), and then relativize the result if needed. + if not origin.is_absolute(): + raise NeedAbsoluteNameOrOrigin + relative = not name.is_absolute() + if relative: + name = name.derelativize(origin) + elif not name.is_subdomain(origin): + raise NeedSubdomainOfOrigin + result_name = function(name, origin, prefix_ok) + if relative: + result_name = result_name.relativize(origin) + return result_name diff --git a/venv/lib/python3.12/site-packages/dns/namedict.py b/venv/lib/python3.12/site-packages/dns/namedict.py new file mode 100644 index 0000000..ca8b197 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/namedict.py @@ -0,0 +1,109 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# Copyright (C) 2016 Coresec Systems AB +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND CORESEC SYSTEMS AB DISCLAIMS ALL +# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL CORESEC +# SYSTEMS AB 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. + +"""DNS name dictionary""" + +# pylint seems to be confused about this one! +from collections.abc import MutableMapping # pylint: disable=no-name-in-module + +import dns.name + + +class NameDict(MutableMapping): + """A dictionary whose keys are dns.name.Name objects. + + In addition to being like a regular Python dictionary, this + dictionary can also get the deepest match for a given key. + """ + + __slots__ = ["max_depth", "max_depth_items", "__store"] + + def __init__(self, *args, **kwargs): + super().__init__() + self.__store = dict() + #: the maximum depth of the keys that have ever been added + self.max_depth = 0 + #: the number of items of maximum depth + self.max_depth_items = 0 + self.update(dict(*args, **kwargs)) + + def __update_max_depth(self, key): + if len(key) == self.max_depth: + self.max_depth_items = self.max_depth_items + 1 + elif len(key) > self.max_depth: + self.max_depth = len(key) + self.max_depth_items = 1 + + def __getitem__(self, key): + return self.__store[key] + + def __setitem__(self, key, value): + if not isinstance(key, dns.name.Name): + raise ValueError("NameDict key must be a name") + self.__store[key] = value + self.__update_max_depth(key) + + def __delitem__(self, key): + self.__store.pop(key) + if len(key) == self.max_depth: + self.max_depth_items = self.max_depth_items - 1 + if self.max_depth_items == 0: + self.max_depth = 0 + for k in self.__store: + self.__update_max_depth(k) + + def __iter__(self): + return iter(self.__store) + + def __len__(self): + return len(self.__store) + + def has_key(self, key): + return key in self.__store + + def get_deepest_match(self, name): + """Find the deepest match to *name* in the dictionary. + + The deepest match is the longest name in the dictionary which is + a superdomain of *name*. Note that *superdomain* includes matching + *name* itself. + + *name*, a ``dns.name.Name``, the name to find. + + Returns a ``(key, value)`` where *key* is the deepest + ``dns.name.Name``, and *value* is the value associated with *key*. + """ + + depth = len(name) + if depth > self.max_depth: + depth = self.max_depth + for i in range(-depth, 0): + n = dns.name.Name(name[i:]) + if n in self: + return (n, self[n]) + v = self[dns.name.empty] + return (dns.name.empty, v) diff --git a/venv/lib/python3.12/site-packages/dns/nameserver.py b/venv/lib/python3.12/site-packages/dns/nameserver.py new file mode 100644 index 0000000..c9307d3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/nameserver.py @@ -0,0 +1,361 @@ +from urllib.parse import urlparse + +import dns.asyncbackend +import dns.asyncquery +import dns.message +import dns.query + + +class Nameserver: + def __init__(self): + pass + + def __str__(self): + raise NotImplementedError + + def kind(self) -> str: + raise NotImplementedError + + def is_always_max_size(self) -> bool: + raise NotImplementedError + + def answer_nameserver(self) -> str: + raise NotImplementedError + + def answer_port(self) -> int: + raise NotImplementedError + + def query( + self, + request: dns.message.QueryMessage, + timeout: float, + source: str | None, + source_port: int, + max_size: bool, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + ) -> dns.message.Message: + raise NotImplementedError + + async def async_query( + self, + request: dns.message.QueryMessage, + timeout: float, + source: str | None, + source_port: int, + max_size: bool, + backend: dns.asyncbackend.Backend, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + ) -> dns.message.Message: + raise NotImplementedError + + +class AddressAndPortNameserver(Nameserver): + def __init__(self, address: str, port: int): + super().__init__() + self.address = address + self.port = port + + def kind(self) -> str: + raise NotImplementedError + + def is_always_max_size(self) -> bool: + return False + + def __str__(self): + ns_kind = self.kind() + return f"{ns_kind}:{self.address}@{self.port}" + + def answer_nameserver(self) -> str: + return self.address + + def answer_port(self) -> int: + return self.port + + +class Do53Nameserver(AddressAndPortNameserver): + def __init__(self, address: str, port: int = 53): + super().__init__(address, port) + + def kind(self): + return "Do53" + + def query( + self, + request: dns.message.QueryMessage, + timeout: float, + source: str | None, + source_port: int, + max_size: bool, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + ) -> dns.message.Message: + if max_size: + response = dns.query.tcp( + request, + self.address, + timeout=timeout, + port=self.port, + source=source, + source_port=source_port, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + ) + else: + response = dns.query.udp( + request, + self.address, + timeout=timeout, + port=self.port, + source=source, + source_port=source_port, + raise_on_truncation=True, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + ignore_errors=True, + ignore_unexpected=True, + ) + return response + + async def async_query( + self, + request: dns.message.QueryMessage, + timeout: float, + source: str | None, + source_port: int, + max_size: bool, + backend: dns.asyncbackend.Backend, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + ) -> dns.message.Message: + if max_size: + response = await dns.asyncquery.tcp( + request, + self.address, + timeout=timeout, + port=self.port, + source=source, + source_port=source_port, + backend=backend, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + ) + else: + response = await dns.asyncquery.udp( + request, + self.address, + timeout=timeout, + port=self.port, + source=source, + source_port=source_port, + raise_on_truncation=True, + backend=backend, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + ignore_errors=True, + ignore_unexpected=True, + ) + return response + + +class DoHNameserver(Nameserver): + def __init__( + self, + url: str, + bootstrap_address: str | None = None, + verify: bool | str = True, + want_get: bool = False, + http_version: dns.query.HTTPVersion = dns.query.HTTPVersion.DEFAULT, + ): + super().__init__() + self.url = url + self.bootstrap_address = bootstrap_address + self.verify = verify + self.want_get = want_get + self.http_version = http_version + + def kind(self): + return "DoH" + + def is_always_max_size(self) -> bool: + return True + + def __str__(self): + return self.url + + def answer_nameserver(self) -> str: + return self.url + + def answer_port(self) -> int: + port = urlparse(self.url).port + if port is None: + port = 443 + return port + + def query( + self, + request: dns.message.QueryMessage, + timeout: float, + source: str | None, + source_port: int, + max_size: bool = False, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + ) -> dns.message.Message: + return dns.query.https( + request, + self.url, + timeout=timeout, + source=source, + source_port=source_port, + bootstrap_address=self.bootstrap_address, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + verify=self.verify, + post=(not self.want_get), + http_version=self.http_version, + ) + + async def async_query( + self, + request: dns.message.QueryMessage, + timeout: float, + source: str | None, + source_port: int, + max_size: bool, + backend: dns.asyncbackend.Backend, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + ) -> dns.message.Message: + return await dns.asyncquery.https( + request, + self.url, + timeout=timeout, + source=source, + source_port=source_port, + bootstrap_address=self.bootstrap_address, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + verify=self.verify, + post=(not self.want_get), + http_version=self.http_version, + ) + + +class DoTNameserver(AddressAndPortNameserver): + def __init__( + self, + address: str, + port: int = 853, + hostname: str | None = None, + verify: bool | str = True, + ): + super().__init__(address, port) + self.hostname = hostname + self.verify = verify + + def kind(self): + return "DoT" + + def query( + self, + request: dns.message.QueryMessage, + timeout: float, + source: str | None, + source_port: int, + max_size: bool = False, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + ) -> dns.message.Message: + return dns.query.tls( + request, + self.address, + port=self.port, + timeout=timeout, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + server_hostname=self.hostname, + verify=self.verify, + ) + + async def async_query( + self, + request: dns.message.QueryMessage, + timeout: float, + source: str | None, + source_port: int, + max_size: bool, + backend: dns.asyncbackend.Backend, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + ) -> dns.message.Message: + return await dns.asyncquery.tls( + request, + self.address, + port=self.port, + timeout=timeout, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + server_hostname=self.hostname, + verify=self.verify, + ) + + +class DoQNameserver(AddressAndPortNameserver): + def __init__( + self, + address: str, + port: int = 853, + verify: bool | str = True, + server_hostname: str | None = None, + ): + super().__init__(address, port) + self.verify = verify + self.server_hostname = server_hostname + + def kind(self): + return "DoQ" + + def query( + self, + request: dns.message.QueryMessage, + timeout: float, + source: str | None, + source_port: int, + max_size: bool = False, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + ) -> dns.message.Message: + return dns.query.quic( + request, + self.address, + port=self.port, + timeout=timeout, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + verify=self.verify, + server_hostname=self.server_hostname, + ) + + async def async_query( + self, + request: dns.message.QueryMessage, + timeout: float, + source: str | None, + source_port: int, + max_size: bool, + backend: dns.asyncbackend.Backend, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + ) -> dns.message.Message: + return await dns.asyncquery.quic( + request, + self.address, + port=self.port, + timeout=timeout, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + verify=self.verify, + server_hostname=self.server_hostname, + ) diff --git a/venv/lib/python3.12/site-packages/dns/node.py b/venv/lib/python3.12/site-packages/dns/node.py new file mode 100644 index 0000000..b2cbf1b --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/node.py @@ -0,0 +1,358 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS nodes. A node is a set of rdatasets.""" + +import enum +import io +from typing import Any, Dict + +import dns.immutable +import dns.name +import dns.rdataclass +import dns.rdataset +import dns.rdatatype +import dns.rrset + +_cname_types = { + dns.rdatatype.CNAME, +} + +# "neutral" types can coexist with a CNAME and thus are not "other data" +_neutral_types = { + dns.rdatatype.NSEC, # RFC 4035 section 2.5 + dns.rdatatype.NSEC3, # This is not likely to happen, but not impossible! + dns.rdatatype.KEY, # RFC 4035 section 2.5, RFC 3007 +} + + +def _matches_type_or_its_signature(rdtypes, rdtype, covers): + return rdtype in rdtypes or (rdtype == dns.rdatatype.RRSIG and covers in rdtypes) + + +@enum.unique +class NodeKind(enum.Enum): + """Rdatasets in nodes""" + + REGULAR = 0 # a.k.a "other data" + NEUTRAL = 1 + CNAME = 2 + + @classmethod + def classify( + cls, rdtype: dns.rdatatype.RdataType, covers: dns.rdatatype.RdataType + ) -> "NodeKind": + if _matches_type_or_its_signature(_cname_types, rdtype, covers): + return NodeKind.CNAME + elif _matches_type_or_its_signature(_neutral_types, rdtype, covers): + return NodeKind.NEUTRAL + else: + return NodeKind.REGULAR + + @classmethod + def classify_rdataset(cls, rdataset: dns.rdataset.Rdataset) -> "NodeKind": + return cls.classify(rdataset.rdtype, rdataset.covers) + + +class Node: + """A Node is a set of rdatasets. + + A node is either a CNAME node or an "other data" node. A CNAME + node contains only CNAME, KEY, NSEC, and NSEC3 rdatasets along with their + covering RRSIG rdatasets. An "other data" node contains any + rdataset other than a CNAME or RRSIG(CNAME) rdataset. When + changes are made to a node, the CNAME or "other data" state is + always consistent with the update, i.e. the most recent change + wins. For example, if you have a node which contains a CNAME + rdataset, and then add an MX rdataset to it, then the CNAME + rdataset will be deleted. Likewise if you have a node containing + an MX rdataset and add a CNAME rdataset, the MX rdataset will be + deleted. + """ + + __slots__ = ["rdatasets"] + + def __init__(self): + # the set of rdatasets, represented as a list. + self.rdatasets = [] + + def to_text(self, name: dns.name.Name, **kw: Dict[str, Any]) -> str: + """Convert a node to text format. + + Each rdataset at the node is printed. Any keyword arguments + to this method are passed on to the rdataset's to_text() method. + + *name*, a ``dns.name.Name``, the owner name of the + rdatasets. + + Returns a ``str``. + + """ + + s = io.StringIO() + for rds in self.rdatasets: + if len(rds) > 0: + s.write(rds.to_text(name, **kw)) # type: ignore[arg-type] + s.write("\n") + return s.getvalue()[:-1] + + def __repr__(self): + return "" + + def __eq__(self, other): + # + # This is inefficient. Good thing we don't need to do it much. + # + for rd in self.rdatasets: + if rd not in other.rdatasets: + return False + for rd in other.rdatasets: + if rd not in self.rdatasets: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def __len__(self): + return len(self.rdatasets) + + def __iter__(self): + return iter(self.rdatasets) + + def _append_rdataset(self, rdataset): + """Append rdataset to the node with special handling for CNAME and + other data conditions. + + Specifically, if the rdataset being appended has ``NodeKind.CNAME``, + then all rdatasets other than KEY, NSEC, NSEC3, and their covering + RRSIGs are deleted. If the rdataset being appended has + ``NodeKind.REGULAR`` then CNAME and RRSIG(CNAME) are deleted. + """ + # Make having just one rdataset at the node fast. + if len(self.rdatasets) > 0: + kind = NodeKind.classify_rdataset(rdataset) + if kind == NodeKind.CNAME: + self.rdatasets = [ + rds + for rds in self.rdatasets + if NodeKind.classify_rdataset(rds) != NodeKind.REGULAR + ] + elif kind == NodeKind.REGULAR: + self.rdatasets = [ + rds + for rds in self.rdatasets + if NodeKind.classify_rdataset(rds) != NodeKind.CNAME + ] + # Otherwise the rdataset is NodeKind.NEUTRAL and we do not need to + # edit self.rdatasets. + self.rdatasets.append(rdataset) + + def find_rdataset( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + create: bool = False, + ) -> dns.rdataset.Rdataset: + """Find an rdataset matching the specified properties in the + current node. + + *rdclass*, a ``dns.rdataclass.RdataClass``, the class of the rdataset. + + *rdtype*, a ``dns.rdatatype.RdataType``, the type of the rdataset. + + *covers*, a ``dns.rdatatype.RdataType``, the covered type. + Usually this value is ``dns.rdatatype.NONE``, but if the + rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, + then the covers value will be the rdata type the SIG/RRSIG + covers. The library treats the SIG and RRSIG types as if they + were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). + This makes RRSIGs much easier to work with than if RRSIGs + covering different rdata types were aggregated into a single + RRSIG rdataset. + + *create*, a ``bool``. If True, create the rdataset if it is not found. + + Raises ``KeyError`` if an rdataset of the desired type and class does + not exist and *create* is not ``True``. + + Returns a ``dns.rdataset.Rdataset``. + """ + + for rds in self.rdatasets: + if rds.match(rdclass, rdtype, covers): + return rds + if not create: + raise KeyError + rds = dns.rdataset.Rdataset(rdclass, rdtype, covers) + self._append_rdataset(rds) + return rds + + def get_rdataset( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + create: bool = False, + ) -> dns.rdataset.Rdataset | None: + """Get an rdataset matching the specified properties in the + current node. + + None is returned if an rdataset of the specified type and + class does not exist and *create* is not ``True``. + + *rdclass*, an ``int``, the class of the rdataset. + + *rdtype*, an ``int``, the type of the rdataset. + + *covers*, an ``int``, the covered type. Usually this value is + dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or + dns.rdatatype.RRSIG, then the covers value will be the rdata + type the SIG/RRSIG covers. The library treats the SIG and RRSIG + types as if they were a family of + types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much + easier to work with than if RRSIGs covering different rdata + types were aggregated into a single RRSIG rdataset. + + *create*, a ``bool``. If True, create the rdataset if it is not found. + + Returns a ``dns.rdataset.Rdataset`` or ``None``. + """ + + try: + rds = self.find_rdataset(rdclass, rdtype, covers, create) + except KeyError: + rds = None + return rds + + def delete_rdataset( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + ) -> None: + """Delete the rdataset matching the specified properties in the + current node. + + If a matching rdataset does not exist, it is not an error. + + *rdclass*, an ``int``, the class of the rdataset. + + *rdtype*, an ``int``, the type of the rdataset. + + *covers*, an ``int``, the covered type. + """ + + rds = self.get_rdataset(rdclass, rdtype, covers) + if rds is not None: + self.rdatasets.remove(rds) + + def replace_rdataset(self, replacement: dns.rdataset.Rdataset) -> None: + """Replace an rdataset. + + It is not an error if there is no rdataset matching *replacement*. + + Ownership of the *replacement* object is transferred to the node; + in other words, this method does not store a copy of *replacement* + at the node, it stores *replacement* itself. + + *replacement*, a ``dns.rdataset.Rdataset``. + + Raises ``ValueError`` if *replacement* is not a + ``dns.rdataset.Rdataset``. + """ + + if not isinstance(replacement, dns.rdataset.Rdataset): + raise ValueError("replacement is not an rdataset") + if isinstance(replacement, dns.rrset.RRset): + # RRsets are not good replacements as the match() method + # is not compatible. + replacement = replacement.to_rdataset() + self.delete_rdataset( + replacement.rdclass, replacement.rdtype, replacement.covers + ) + self._append_rdataset(replacement) + + def classify(self) -> NodeKind: + """Classify a node. + + A node which contains a CNAME or RRSIG(CNAME) is a + ``NodeKind.CNAME`` node. + + A node which contains only "neutral" types, i.e. types allowed to + co-exist with a CNAME, is a ``NodeKind.NEUTRAL`` node. The neutral + types are NSEC, NSEC3, KEY, and their associated RRSIGS. An empty node + is also considered neutral. + + A node which contains some rdataset which is not a CNAME, RRSIG(CNAME), + or a neutral type is a a ``NodeKind.REGULAR`` node. Regular nodes are + also commonly referred to as "other data". + """ + for rdataset in self.rdatasets: + kind = NodeKind.classify(rdataset.rdtype, rdataset.covers) + if kind != NodeKind.NEUTRAL: + return kind + return NodeKind.NEUTRAL + + def is_immutable(self) -> bool: + return False + + +@dns.immutable.immutable +class ImmutableNode(Node): + def __init__(self, node): + super().__init__() + self.rdatasets = tuple( + [dns.rdataset.ImmutableRdataset(rds) for rds in node.rdatasets] + ) + + def find_rdataset( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + create: bool = False, + ) -> dns.rdataset.Rdataset: + if create: + raise TypeError("immutable") + return super().find_rdataset(rdclass, rdtype, covers, False) + + def get_rdataset( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + create: bool = False, + ) -> dns.rdataset.Rdataset | None: + if create: + raise TypeError("immutable") + return super().get_rdataset(rdclass, rdtype, covers, False) + + def delete_rdataset( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + ) -> None: + raise TypeError("immutable") + + def replace_rdataset(self, replacement: dns.rdataset.Rdataset) -> None: + raise TypeError("immutable") + + def is_immutable(self) -> bool: + return True diff --git a/venv/lib/python3.12/site-packages/dns/opcode.py b/venv/lib/python3.12/site-packages/dns/opcode.py new file mode 100644 index 0000000..3fa610d --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/opcode.py @@ -0,0 +1,119 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS Opcodes.""" + +from typing import Type + +import dns.enum +import dns.exception + + +class Opcode(dns.enum.IntEnum): + #: Query + QUERY = 0 + #: Inverse Query (historical) + IQUERY = 1 + #: Server Status (unspecified and unimplemented anywhere) + STATUS = 2 + #: Notify + NOTIFY = 4 + #: Dynamic Update + UPDATE = 5 + + @classmethod + def _maximum(cls): + return 15 + + @classmethod + def _unknown_exception_class(cls) -> Type[Exception]: + return UnknownOpcode + + +class UnknownOpcode(dns.exception.DNSException): + """An DNS opcode is unknown.""" + + +def from_text(text: str) -> Opcode: + """Convert text into an opcode. + + *text*, a ``str``, the textual opcode + + Raises ``dns.opcode.UnknownOpcode`` if the opcode is unknown. + + Returns an ``int``. + """ + + return Opcode.from_text(text) + + +def from_flags(flags: int) -> Opcode: + """Extract an opcode from DNS message flags. + + *flags*, an ``int``, the DNS flags. + + Returns an ``int``. + """ + + return Opcode((flags & 0x7800) >> 11) + + +def to_flags(value: Opcode) -> int: + """Convert an opcode to a value suitable for ORing into DNS message + flags. + + *value*, an ``int``, the DNS opcode value. + + Returns an ``int``. + """ + + return (value << 11) & 0x7800 + + +def to_text(value: Opcode) -> str: + """Convert an opcode to text. + + *value*, an ``int`` the opcode value, + + Raises ``dns.opcode.UnknownOpcode`` if the opcode is unknown. + + Returns a ``str``. + """ + + return Opcode.to_text(value) + + +def is_update(flags: int) -> bool: + """Is the opcode in flags UPDATE? + + *flags*, an ``int``, the DNS message flags. + + Returns a ``bool``. + """ + + return from_flags(flags) == Opcode.UPDATE + + +### BEGIN generated Opcode constants + +QUERY = Opcode.QUERY +IQUERY = Opcode.IQUERY +STATUS = Opcode.STATUS +NOTIFY = Opcode.NOTIFY +UPDATE = Opcode.UPDATE + +### END generated Opcode constants diff --git a/venv/lib/python3.12/site-packages/dns/py.typed b/venv/lib/python3.12/site-packages/dns/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/dns/query.py b/venv/lib/python3.12/site-packages/dns/query.py new file mode 100644 index 0000000..17b1862 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/query.py @@ -0,0 +1,1786 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""Talk to a DNS server.""" + +import base64 +import contextlib +import enum +import errno +import os +import random +import selectors +import socket +import struct +import time +import urllib.parse +from typing import Any, Callable, Dict, Optional, Tuple, cast + +import dns._features +import dns._tls_util +import dns.exception +import dns.inet +import dns.message +import dns.name +import dns.quic +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.transaction +import dns.tsig +import dns.xfr + +try: + import ssl +except ImportError: + import dns._no_ssl as ssl # type: ignore + + +def _remaining(expiration): + if expiration is None: + return None + timeout = expiration - time.time() + if timeout <= 0.0: + raise dns.exception.Timeout + return timeout + + +def _expiration_for_this_attempt(timeout, expiration): + if expiration is None: + return None + return min(time.time() + timeout, expiration) + + +_have_httpx = dns._features.have("doh") +if _have_httpx: + import httpcore._backends.sync + import httpx + + _CoreNetworkBackend = httpcore.NetworkBackend + _CoreSyncStream = httpcore._backends.sync.SyncStream + + class _NetworkBackend(_CoreNetworkBackend): + def __init__(self, resolver, local_port, bootstrap_address, family): + super().__init__() + self._local_port = local_port + self._resolver = resolver + self._bootstrap_address = bootstrap_address + self._family = family + + def connect_tcp( + self, host, port, timeout=None, local_address=None, socket_options=None + ): # pylint: disable=signature-differs + addresses = [] + _, expiration = _compute_times(timeout) + if dns.inet.is_address(host): + addresses.append(host) + elif self._bootstrap_address is not None: + addresses.append(self._bootstrap_address) + else: + timeout = _remaining(expiration) + family = self._family + if local_address: + family = dns.inet.af_for_address(local_address) + answers = self._resolver.resolve_name( + host, family=family, lifetime=timeout + ) + addresses = answers.addresses() + for address in addresses: + af = dns.inet.af_for_address(address) + if local_address is not None or self._local_port != 0: + if local_address is None: + local_address = "0.0.0.0" + source = dns.inet.low_level_address_tuple( + (local_address, self._local_port), af + ) + else: + source = None + try: + sock = make_socket(af, socket.SOCK_STREAM, source) + attempt_expiration = _expiration_for_this_attempt(2.0, expiration) + _connect( + sock, + dns.inet.low_level_address_tuple((address, port), af), + attempt_expiration, + ) + return _CoreSyncStream(sock) + except Exception: + pass + raise httpcore.ConnectError + + def connect_unix_socket( + self, path, timeout=None, socket_options=None + ): # pylint: disable=signature-differs + raise NotImplementedError + + class _HTTPTransport(httpx.HTTPTransport): # pyright: ignore + def __init__( + self, + *args, + local_port=0, + bootstrap_address=None, + resolver=None, + family=socket.AF_UNSPEC, + **kwargs, + ): + if resolver is None and bootstrap_address is None: + # pylint: disable=import-outside-toplevel,redefined-outer-name + import dns.resolver + + resolver = dns.resolver.Resolver() + super().__init__(*args, **kwargs) + self._pool._network_backend = _NetworkBackend( + resolver, local_port, bootstrap_address, family + ) + +else: + + class _HTTPTransport: # type: ignore + def __init__( + self, + *args, + local_port=0, + bootstrap_address=None, + resolver=None, + family=socket.AF_UNSPEC, + **kwargs, + ): + pass + + def connect_tcp(self, host, port, timeout, local_address): + raise NotImplementedError + + +have_doh = _have_httpx + + +def default_socket_factory( + af: socket.AddressFamily | int, + kind: socket.SocketKind, + proto: int, +) -> socket.socket: + return socket.socket(af, kind, proto) + + +# Function used to create a socket. Can be overridden if needed in special +# situations. +socket_factory: Callable[ + [socket.AddressFamily | int, socket.SocketKind, int], socket.socket +] = default_socket_factory + + +class UnexpectedSource(dns.exception.DNSException): + """A DNS query response came from an unexpected address or port.""" + + +class BadResponse(dns.exception.FormError): + """A DNS query response does not respond to the question asked.""" + + +class NoDOH(dns.exception.DNSException): + """DNS over HTTPS (DOH) was requested but the httpx module is not + available.""" + + +class NoDOQ(dns.exception.DNSException): + """DNS over QUIC (DOQ) was requested but the aioquic module is not + available.""" + + +# for backwards compatibility +TransferError = dns.xfr.TransferError + + +def _compute_times(timeout): + now = time.time() + if timeout is None: + return (now, None) + else: + return (now, now + timeout) + + +def _wait_for(fd, readable, writable, _, expiration): + # Use the selected selector class to wait for any of the specified + # events. An "expiration" absolute time is converted into a relative + # timeout. + # + # The unused parameter is 'error', which is always set when + # selecting for read or write, and we have no error-only selects. + + if readable and isinstance(fd, ssl.SSLSocket) and fd.pending() > 0: + return True + with selectors.DefaultSelector() as sel: + events = 0 + if readable: + events |= selectors.EVENT_READ + if writable: + events |= selectors.EVENT_WRITE + if events: + sel.register(fd, events) # pyright: ignore + if expiration is None: + timeout = None + else: + timeout = expiration - time.time() + if timeout <= 0.0: + raise dns.exception.Timeout + if not sel.select(timeout): + raise dns.exception.Timeout + + +def _wait_for_readable(s, expiration): + _wait_for(s, True, False, True, expiration) + + +def _wait_for_writable(s, expiration): + _wait_for(s, False, True, True, expiration) + + +def _addresses_equal(af, a1, a2): + # Convert the first value of the tuple, which is a textual format + # address into binary form, so that we are not confused by different + # textual representations of the same address + try: + n1 = dns.inet.inet_pton(af, a1[0]) + n2 = dns.inet.inet_pton(af, a2[0]) + except dns.exception.SyntaxError: + return False + return n1 == n2 and a1[1:] == a2[1:] + + +def _matches_destination(af, from_address, destination, ignore_unexpected): + # Check that from_address is appropriate for a response to a query + # sent to destination. + if not destination: + return True + if _addresses_equal(af, from_address, destination) or ( + dns.inet.is_multicast(destination[0]) and from_address[1:] == destination[1:] + ): + return True + elif ignore_unexpected: + return False + raise UnexpectedSource( + f"got a response from {from_address} instead of " f"{destination}" + ) + + +def _destination_and_source( + where, port, source, source_port, where_must_be_address=True +): + # Apply defaults and compute destination and source tuples + # suitable for use in connect(), sendto(), or bind(). + af = None + destination = None + try: + af = dns.inet.af_for_address(where) + destination = where + except Exception: + if where_must_be_address: + raise + # URLs are ok so eat the exception + if source: + saf = dns.inet.af_for_address(source) + if af: + # We know the destination af, so source had better agree! + if saf != af: + raise ValueError( + "different address families for source and destination" + ) + else: + # We didn't know the destination af, but we know the source, + # so that's our af. + af = saf + if source_port and not source: + # Caller has specified a source_port but not an address, so we + # need to return a source, and we need to use the appropriate + # wildcard address as the address. + try: + source = dns.inet.any_for_af(af) + except Exception: + # we catch this and raise ValueError for backwards compatibility + raise ValueError("source_port specified but address family is unknown") + # Convert high-level (address, port) tuples into low-level address + # tuples. + if destination: + destination = dns.inet.low_level_address_tuple((destination, port), af) + if source: + source = dns.inet.low_level_address_tuple((source, source_port), af) + return (af, destination, source) + + +def make_socket( + af: socket.AddressFamily | int, + type: socket.SocketKind, + source: Any | None = None, +) -> socket.socket: + """Make a socket. + + This function uses the module's ``socket_factory`` to make a socket of the + specified address family and type. + + *af*, a ``socket.AddressFamily`` or ``int`` is the address family, either + ``socket.AF_INET`` or ``socket.AF_INET6``. + + *type*, a ``socket.SocketKind`` is the type of socket, e.g. ``socket.SOCK_DGRAM``, + a datagram socket, or ``socket.SOCK_STREAM``, a stream socket. Note that the + ``proto`` attribute of a socket is always zero with this API, so a datagram socket + will always be a UDP socket, and a stream socket will always be a TCP socket. + + *source* is the source address and port to bind to, if any. The default is + ``None`` which will bind to the wildcard address and a randomly chosen port. + If not ``None``, it should be a (low-level) address tuple appropriate for *af*. + """ + s = socket_factory(af, type, 0) + try: + s.setblocking(False) + if source is not None: + s.bind(source) + return s + except Exception: + s.close() + raise + + +def make_ssl_socket( + af: socket.AddressFamily | int, + type: socket.SocketKind, + ssl_context: ssl.SSLContext, + server_hostname: dns.name.Name | str | None = None, + source: Any | None = None, +) -> ssl.SSLSocket: + """Make a socket. + + This function uses the module's ``socket_factory`` to make a socket of the + specified address family and type. + + *af*, a ``socket.AddressFamily`` or ``int`` is the address family, either + ``socket.AF_INET`` or ``socket.AF_INET6``. + + *type*, a ``socket.SocketKind`` is the type of socket, e.g. ``socket.SOCK_DGRAM``, + a datagram socket, or ``socket.SOCK_STREAM``, a stream socket. Note that the + ``proto`` attribute of a socket is always zero with this API, so a datagram socket + will always be a UDP socket, and a stream socket will always be a TCP socket. + + If *ssl_context* is not ``None``, then it specifies the SSL context to use, + typically created with ``make_ssl_context()``. + + If *server_hostname* is not ``None``, then it is the hostname to use for server + certificate validation. A valid hostname must be supplied if *ssl_context* + requires hostname checking. + + *source* is the source address and port to bind to, if any. The default is + ``None`` which will bind to the wildcard address and a randomly chosen port. + If not ``None``, it should be a (low-level) address tuple appropriate for *af*. + """ + sock = make_socket(af, type, source) + if isinstance(server_hostname, dns.name.Name): + server_hostname = server_hostname.to_text() + # LGTM gets a false positive here, as our default context is OK + return ssl_context.wrap_socket( + sock, + do_handshake_on_connect=False, # lgtm[py/insecure-protocol] + server_hostname=server_hostname, + ) + + +# for backwards compatibility +def _make_socket( + af, + type, + source, + ssl_context, + server_hostname, +): + if ssl_context is not None: + return make_ssl_socket(af, type, ssl_context, server_hostname, source) + else: + return make_socket(af, type, source) + + +def _maybe_get_resolver( + resolver: Optional["dns.resolver.Resolver"], # pyright: ignore +) -> "dns.resolver.Resolver": # pyright: ignore + # We need a separate method for this to avoid overriding the global + # variable "dns" with the as-yet undefined local variable "dns" + # in https(). + if resolver is None: + # pylint: disable=import-outside-toplevel,redefined-outer-name + import dns.resolver + + resolver = dns.resolver.Resolver() + return resolver + + +class HTTPVersion(enum.IntEnum): + """Which version of HTTP should be used? + + DEFAULT will select the first version from the list [2, 1.1, 3] that + is available. + """ + + DEFAULT = 0 + HTTP_1 = 1 + H1 = 1 + HTTP_2 = 2 + H2 = 2 + HTTP_3 = 3 + H3 = 3 + + +def https( + q: dns.message.Message, + where: str, + timeout: float | None = None, + port: int = 443, + source: str | None = None, + source_port: int = 0, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + session: Any | None = None, + path: str = "/dns-query", + post: bool = True, + bootstrap_address: str | None = None, + verify: bool | str | ssl.SSLContext = True, + resolver: Optional["dns.resolver.Resolver"] = None, # pyright: ignore + family: int = socket.AF_UNSPEC, + http_version: HTTPVersion = HTTPVersion.DEFAULT, +) -> dns.message.Message: + """Return the response obtained after sending a query via DNS-over-HTTPS. + + *q*, a ``dns.message.Message``, the query to send. + + *where*, a ``str``, the nameserver IP address or the full URL. If an IP address is + given, the URL will be constructed using the following schema: + https://:/. + + *timeout*, a ``float`` or ``None``, the number of seconds to wait before the query + times out. If ``None``, the default, wait forever. + + *port*, a ``int``, the port to send the query to. The default is 443. + + *source*, a ``str`` containing an IPv4 or IPv6 address, specifying the source + address. The default is the wildcard address. + + *source_port*, an ``int``, the port from which to send the message. The default is + 0. + + *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own RRset. + + *ignore_trailing*, a ``bool``. If ``True``, ignore trailing junk at end of the + received message. + + *session*, an ``httpx.Client``. If provided, the client session to use to send the + queries. + + *path*, a ``str``. If *where* is an IP address, then *path* will be used to + construct the URL to send the DNS query to. + + *post*, a ``bool``. If ``True``, the default, POST method will be used. + + *bootstrap_address*, a ``str``, the IP address to use to bypass resolution. + + *verify*, a ``bool`` or ``str``. If a ``True``, then TLS certificate verification + of the server is done using the default CA bundle; if ``False``, then no + verification is done; if a `str` then it specifies the path to a certificate file or + directory which will be used for verification. + + *resolver*, a ``dns.resolver.Resolver`` or ``None``, the resolver to use for + resolution of hostnames in URLs. If not specified, a new resolver with a default + configuration will be used; note this is *not* the default resolver as that resolver + might have been configured to use DoH causing a chicken-and-egg problem. This + parameter only has an effect if the HTTP library is httpx. + + *family*, an ``int``, the address family. If socket.AF_UNSPEC (the default), both A + and AAAA records will be retrieved. + + *http_version*, a ``dns.query.HTTPVersion``, indicating which HTTP version to use. + + Returns a ``dns.message.Message``. + """ + + (af, _, the_source) = _destination_and_source( + where, port, source, source_port, False + ) + # we bind url and then override as pyright can't figure out all paths bind. + url = where + if af is not None and dns.inet.is_address(where): + if af == socket.AF_INET: + url = f"https://{where}:{port}{path}" + elif af == socket.AF_INET6: + url = f"https://[{where}]:{port}{path}" + + extensions = {} + if bootstrap_address is None: + # pylint: disable=possibly-used-before-assignment + parsed = urllib.parse.urlparse(url) + if parsed.hostname is None: + raise ValueError("no hostname in URL") + if dns.inet.is_address(parsed.hostname): + bootstrap_address = parsed.hostname + extensions["sni_hostname"] = parsed.hostname + if parsed.port is not None: + port = parsed.port + + if http_version == HTTPVersion.H3 or ( + http_version == HTTPVersion.DEFAULT and not have_doh + ): + if bootstrap_address is None: + resolver = _maybe_get_resolver(resolver) + assert parsed.hostname is not None # pyright: ignore + answers = resolver.resolve_name(parsed.hostname, family) # pyright: ignore + bootstrap_address = random.choice(list(answers.addresses())) + if session and not isinstance( + session, dns.quic.SyncQuicConnection + ): # pyright: ignore + raise ValueError("session parameter must be a dns.quic.SyncQuicConnection.") + return _http3( + q, + bootstrap_address, + url, # pyright: ignore + timeout, + port, + source, + source_port, + one_rr_per_rrset, + ignore_trailing, + verify=verify, + post=post, + connection=session, + ) + + if not have_doh: + raise NoDOH # pragma: no cover + if session and not isinstance(session, httpx.Client): # pyright: ignore + raise ValueError("session parameter must be an httpx.Client") + + wire = q.to_wire() + headers = {"accept": "application/dns-message"} + + h1 = http_version in (HTTPVersion.H1, HTTPVersion.DEFAULT) + h2 = http_version in (HTTPVersion.H2, HTTPVersion.DEFAULT) + + # set source port and source address + + if the_source is None: + local_address = None + local_port = 0 + else: + local_address = the_source[0] + local_port = the_source[1] + + if session: + cm: contextlib.AbstractContextManager = contextlib.nullcontext(session) + else: + transport = _HTTPTransport( + local_address=local_address, + http1=h1, + http2=h2, + verify=verify, + local_port=local_port, + bootstrap_address=bootstrap_address, + resolver=resolver, + family=family, # pyright: ignore + ) + + cm = httpx.Client( # type: ignore + http1=h1, http2=h2, verify=verify, transport=transport # type: ignore + ) + with cm as session: + # see https://tools.ietf.org/html/rfc8484#section-4.1.1 for DoH + # GET and POST examples + assert session is not None + if post: + headers.update( + { + "content-type": "application/dns-message", + "content-length": str(len(wire)), + } + ) + response = session.post( + url, + headers=headers, + content=wire, + timeout=timeout, + extensions=extensions, + ) + else: + wire = base64.urlsafe_b64encode(wire).rstrip(b"=") + twire = wire.decode() # httpx does a repr() if we give it bytes + response = session.get( + url, + headers=headers, + timeout=timeout, + params={"dns": twire}, + extensions=extensions, + ) + + # see https://tools.ietf.org/html/rfc8484#section-4.2.1 for info about DoH + # status codes + if response.status_code < 200 or response.status_code > 299: + raise ValueError( + f"{where} responded with status code {response.status_code}" + f"\nResponse body: {response.content}" + ) + r = dns.message.from_wire( + response.content, + keyring=q.keyring, + request_mac=q.request_mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + ) + r.time = response.elapsed.total_seconds() + if not q.is_response(r): + raise BadResponse + return r + + +def _find_header(headers: dns.quic.Headers, name: bytes) -> bytes: + if headers is None: + raise KeyError + for header, value in headers: + if header == name: + return value + raise KeyError + + +def _check_status(headers: dns.quic.Headers, peer: str, wire: bytes) -> None: + value = _find_header(headers, b":status") + if value is None: + raise SyntaxError("no :status header in response") + status = int(value) + if status < 0: + raise SyntaxError("status is negative") + if status < 200 or status > 299: + error = "" + if len(wire) > 0: + try: + error = ": " + wire.decode() + except Exception: + pass + raise ValueError(f"{peer} responded with status code {status}{error}") + + +def _http3( + q: dns.message.Message, + where: str, + url: str, + timeout: float | None = None, + port: int = 443, + source: str | None = None, + source_port: int = 0, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + verify: bool | str | ssl.SSLContext = True, + post: bool = True, + connection: dns.quic.SyncQuicConnection | None = None, +) -> dns.message.Message: + if not dns.quic.have_quic: + raise NoDOH("DNS-over-HTTP3 is not available.") # pragma: no cover + + url_parts = urllib.parse.urlparse(url) + hostname = url_parts.hostname + assert hostname is not None + if url_parts.port is not None: + port = url_parts.port + + q.id = 0 + wire = q.to_wire() + the_connection: dns.quic.SyncQuicConnection + the_manager: dns.quic.SyncQuicManager + if connection: + manager: contextlib.AbstractContextManager = contextlib.nullcontext(None) + else: + manager = dns.quic.SyncQuicManager( + verify_mode=verify, server_name=hostname, h3=True # pyright: ignore + ) + the_manager = manager # for type checking happiness + + with manager: + if connection: + the_connection = connection + else: + the_connection = the_manager.connect( # pyright: ignore + where, port, source, source_port + ) + (start, expiration) = _compute_times(timeout) + with the_connection.make_stream(timeout) as stream: # pyright: ignore + stream.send_h3(url, wire, post) + wire = stream.receive(_remaining(expiration)) + _check_status(stream.headers(), where, wire) + finish = time.time() + r = dns.message.from_wire( + wire, + keyring=q.keyring, + request_mac=q.request_mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + ) + r.time = max(finish - start, 0.0) + if not q.is_response(r): + raise BadResponse + return r + + +def _udp_recv(sock, max_size, expiration): + """Reads a datagram from the socket. + A Timeout exception will be raised if the operation is not completed + by the expiration time. + """ + while True: + try: + return sock.recvfrom(max_size) + except BlockingIOError: + _wait_for_readable(sock, expiration) + + +def _udp_send(sock, data, destination, expiration): + """Sends the specified datagram to destination over the socket. + A Timeout exception will be raised if the operation is not completed + by the expiration time. + """ + while True: + try: + if destination: + return sock.sendto(data, destination) + else: + return sock.send(data) + except BlockingIOError: # pragma: no cover + _wait_for_writable(sock, expiration) + + +def send_udp( + sock: Any, + what: dns.message.Message | bytes, + destination: Any, + expiration: float | None = None, +) -> Tuple[int, float]: + """Send a DNS message to the specified UDP socket. + + *sock*, a ``socket``. + + *what*, a ``bytes`` or ``dns.message.Message``, the message to send. + + *destination*, a destination tuple appropriate for the address family + of the socket, specifying where to send the query. + + *expiration*, a ``float`` or ``None``, the absolute time at which + a timeout exception should be raised. If ``None``, no timeout will + occur. + + Returns an ``(int, float)`` tuple of bytes sent and the sent time. + """ + + if isinstance(what, dns.message.Message): + what = what.to_wire() + sent_time = time.time() + n = _udp_send(sock, what, destination, expiration) + return (n, sent_time) + + +def receive_udp( + sock: Any, + destination: Any | None = None, + expiration: float | None = None, + ignore_unexpected: bool = False, + one_rr_per_rrset: bool = False, + keyring: Dict[dns.name.Name, dns.tsig.Key] | None = None, + request_mac: bytes | None = b"", + ignore_trailing: bool = False, + raise_on_truncation: bool = False, + ignore_errors: bool = False, + query: dns.message.Message | None = None, +) -> Any: + """Read a DNS message from a UDP socket. + + *sock*, a ``socket``. + + *destination*, a destination tuple appropriate for the address family + of the socket, specifying where the message is expected to arrive from. + When receiving a response, this would be where the associated query was + sent. + + *expiration*, a ``float`` or ``None``, the absolute time at which + a timeout exception should be raised. If ``None``, no timeout will + occur. + + *ignore_unexpected*, a ``bool``. If ``True``, ignore responses from + unexpected sources. + + *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own + RRset. + + *keyring*, a ``dict``, the keyring to use for TSIG. + + *request_mac*, a ``bytes`` or ``None``, the MAC of the request (for TSIG). + + *ignore_trailing*, a ``bool``. If ``True``, ignore trailing + junk at end of the received message. + + *raise_on_truncation*, a ``bool``. If ``True``, raise an exception if + the TC bit is set. + + Raises if the message is malformed, if network errors occur, of if + there is a timeout. + + If *destination* is not ``None``, returns a ``(dns.message.Message, float)`` + tuple of the received message and the received time. + + If *destination* is ``None``, returns a + ``(dns.message.Message, float, tuple)`` + tuple of the received message, the received time, and the address where + the message arrived from. + + *ignore_errors*, a ``bool``. If various format errors or response + mismatches occur, ignore them and keep listening for a valid response. + The default is ``False``. + + *query*, a ``dns.message.Message`` or ``None``. If not ``None`` and + *ignore_errors* is ``True``, check that the received message is a response + to this query, and if not keep listening for a valid response. + """ + + wire = b"" + while True: + (wire, from_address) = _udp_recv(sock, 65535, expiration) + if not _matches_destination( + sock.family, from_address, destination, ignore_unexpected + ): + continue + received_time = time.time() + try: + r = dns.message.from_wire( + wire, + keyring=keyring, + request_mac=request_mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + raise_on_truncation=raise_on_truncation, + ) + except dns.message.Truncated as e: + # If we got Truncated and not FORMERR, we at least got the header with TC + # set, and very likely the question section, so we'll re-raise if the + # message seems to be a response as we need to know when truncation happens. + # We need to check that it seems to be a response as we don't want a random + # injected message with TC set to cause us to bail out. + if ( + ignore_errors + and query is not None + and not query.is_response(e.message()) + ): + continue + else: + raise + except Exception: + if ignore_errors: + continue + else: + raise + if ignore_errors and query is not None and not query.is_response(r): + continue + if destination: + return (r, received_time) + else: + return (r, received_time, from_address) + + +def udp( + q: dns.message.Message, + where: str, + timeout: float | None = None, + port: int = 53, + source: str | None = None, + source_port: int = 0, + ignore_unexpected: bool = False, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + raise_on_truncation: bool = False, + sock: Any | None = None, + ignore_errors: bool = False, +) -> dns.message.Message: + """Return the response obtained after sending a query via UDP. + + *q*, a ``dns.message.Message``, the query to send + + *where*, a ``str`` containing an IPv4 or IPv6 address, where + to send the message. + + *timeout*, a ``float`` or ``None``, the number of seconds to wait before the + query times out. If ``None``, the default, wait forever. + + *port*, an ``int``, the port send the message to. The default is 53. + + *source*, a ``str`` containing an IPv4 or IPv6 address, specifying + the source address. The default is the wildcard address. + + *source_port*, an ``int``, the port from which to send the message. + The default is 0. + + *ignore_unexpected*, a ``bool``. If ``True``, ignore responses from + unexpected sources. + + *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own + RRset. + + *ignore_trailing*, a ``bool``. If ``True``, ignore trailing + junk at end of the received message. + + *raise_on_truncation*, a ``bool``. If ``True``, raise an exception if + the TC bit is set. + + *sock*, a ``socket.socket``, or ``None``, the socket to use for the + query. If ``None``, the default, a socket is created. Note that + if a socket is provided, it must be a nonblocking datagram socket, + and the *source* and *source_port* are ignored. + + *ignore_errors*, a ``bool``. If various format errors or response + mismatches occur, ignore them and keep listening for a valid response. + The default is ``False``. + + Returns a ``dns.message.Message``. + """ + + wire = q.to_wire() + (af, destination, source) = _destination_and_source( + where, port, source, source_port, True + ) + (begin_time, expiration) = _compute_times(timeout) + if sock: + cm: contextlib.AbstractContextManager = contextlib.nullcontext(sock) + else: + assert af is not None + cm = make_socket(af, socket.SOCK_DGRAM, source) + with cm as s: + send_udp(s, wire, destination, expiration) + (r, received_time) = receive_udp( + s, + destination, + expiration, + ignore_unexpected, + one_rr_per_rrset, + q.keyring, + q.mac, + ignore_trailing, + raise_on_truncation, + ignore_errors, + q, + ) + r.time = received_time - begin_time + # We don't need to check q.is_response() if we are in ignore_errors mode + # as receive_udp() will have checked it. + if not (ignore_errors or q.is_response(r)): + raise BadResponse + return r + assert ( + False # help mypy figure out we can't get here lgtm[py/unreachable-statement] + ) + + +def udp_with_fallback( + q: dns.message.Message, + where: str, + timeout: float | None = None, + port: int = 53, + source: str | None = None, + source_port: int = 0, + ignore_unexpected: bool = False, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + udp_sock: Any | None = None, + tcp_sock: Any | None = None, + ignore_errors: bool = False, +) -> Tuple[dns.message.Message, bool]: + """Return the response to the query, trying UDP first and falling back + to TCP if UDP results in a truncated response. + + *q*, a ``dns.message.Message``, the query to send + + *where*, a ``str`` containing an IPv4 or IPv6 address, where to send the message. + + *timeout*, a ``float`` or ``None``, the number of seconds to wait before the query + times out. If ``None``, the default, wait forever. + + *port*, an ``int``, the port send the message to. The default is 53. + + *source*, a ``str`` containing an IPv4 or IPv6 address, specifying the source + address. The default is the wildcard address. + + *source_port*, an ``int``, the port from which to send the message. The default is + 0. + + *ignore_unexpected*, a ``bool``. If ``True``, ignore responses from unexpected + sources. + + *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own RRset. + + *ignore_trailing*, a ``bool``. If ``True``, ignore trailing junk at end of the + received message. + + *udp_sock*, a ``socket.socket``, or ``None``, the socket to use for the UDP query. + If ``None``, the default, a socket is created. Note that if a socket is provided, + it must be a nonblocking datagram socket, and the *source* and *source_port* are + ignored for the UDP query. + + *tcp_sock*, a ``socket.socket``, or ``None``, the connected socket to use for the + TCP query. If ``None``, the default, a socket is created. Note that if a socket is + provided, it must be a nonblocking connected stream socket, and *where*, *source* + and *source_port* are ignored for the TCP query. + + *ignore_errors*, a ``bool``. If various format errors or response mismatches occur + while listening for UDP, ignore them and keep listening for a valid response. The + default is ``False``. + + Returns a (``dns.message.Message``, tcp) tuple where tcp is ``True`` if and only if + TCP was used. + """ + try: + response = udp( + q, + where, + timeout, + port, + source, + source_port, + ignore_unexpected, + one_rr_per_rrset, + ignore_trailing, + True, + udp_sock, + ignore_errors, + ) + return (response, False) + except dns.message.Truncated: + response = tcp( + q, + where, + timeout, + port, + source, + source_port, + one_rr_per_rrset, + ignore_trailing, + tcp_sock, + ) + return (response, True) + + +def _net_read(sock, count, expiration): + """Read the specified number of bytes from sock. Keep trying until we + either get the desired amount, or we hit EOF. + A Timeout exception will be raised if the operation is not completed + by the expiration time. + """ + s = b"" + while count > 0: + try: + n = sock.recv(count) + if n == b"": + raise EOFError("EOF") + count -= len(n) + s += n + except (BlockingIOError, ssl.SSLWantReadError): + _wait_for_readable(sock, expiration) + except ssl.SSLWantWriteError: # pragma: no cover + _wait_for_writable(sock, expiration) + return s + + +def _net_write(sock, data, expiration): + """Write the specified data to the socket. + A Timeout exception will be raised if the operation is not completed + by the expiration time. + """ + current = 0 + l = len(data) + while current < l: + try: + current += sock.send(data[current:]) + except (BlockingIOError, ssl.SSLWantWriteError): + _wait_for_writable(sock, expiration) + except ssl.SSLWantReadError: # pragma: no cover + _wait_for_readable(sock, expiration) + + +def send_tcp( + sock: Any, + what: dns.message.Message | bytes, + expiration: float | None = None, +) -> Tuple[int, float]: + """Send a DNS message to the specified TCP socket. + + *sock*, a ``socket``. + + *what*, a ``bytes`` or ``dns.message.Message``, the message to send. + + *expiration*, a ``float`` or ``None``, the absolute time at which + a timeout exception should be raised. If ``None``, no timeout will + occur. + + Returns an ``(int, float)`` tuple of bytes sent and the sent time. + """ + + if isinstance(what, dns.message.Message): + tcpmsg = what.to_wire(prepend_length=True) + else: + # copying the wire into tcpmsg is inefficient, but lets us + # avoid writev() or doing a short write that would get pushed + # onto the net + tcpmsg = len(what).to_bytes(2, "big") + what + sent_time = time.time() + _net_write(sock, tcpmsg, expiration) + return (len(tcpmsg), sent_time) + + +def receive_tcp( + sock: Any, + expiration: float | None = None, + one_rr_per_rrset: bool = False, + keyring: Dict[dns.name.Name, dns.tsig.Key] | None = None, + request_mac: bytes | None = b"", + ignore_trailing: bool = False, +) -> Tuple[dns.message.Message, float]: + """Read a DNS message from a TCP socket. + + *sock*, a ``socket``. + + *expiration*, a ``float`` or ``None``, the absolute time at which + a timeout exception should be raised. If ``None``, no timeout will + occur. + + *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own + RRset. + + *keyring*, a ``dict``, the keyring to use for TSIG. + + *request_mac*, a ``bytes`` or ``None``, the MAC of the request (for TSIG). + + *ignore_trailing*, a ``bool``. If ``True``, ignore trailing + junk at end of the received message. + + Raises if the message is malformed, if network errors occur, of if + there is a timeout. + + Returns a ``(dns.message.Message, float)`` tuple of the received message + and the received time. + """ + + ldata = _net_read(sock, 2, expiration) + (l,) = struct.unpack("!H", ldata) + wire = _net_read(sock, l, expiration) + received_time = time.time() + r = dns.message.from_wire( + wire, + keyring=keyring, + request_mac=request_mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + ) + return (r, received_time) + + +def _connect(s, address, expiration): + err = s.connect_ex(address) + if err == 0: + return + if err in (errno.EINPROGRESS, errno.EWOULDBLOCK, errno.EALREADY): + _wait_for_writable(s, expiration) + err = s.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) + if err != 0: + raise OSError(err, os.strerror(err)) + + +def tcp( + q: dns.message.Message, + where: str, + timeout: float | None = None, + port: int = 53, + source: str | None = None, + source_port: int = 0, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + sock: Any | None = None, +) -> dns.message.Message: + """Return the response obtained after sending a query via TCP. + + *q*, a ``dns.message.Message``, the query to send + + *where*, a ``str`` containing an IPv4 or IPv6 address, where + to send the message. + + *timeout*, a ``float`` or ``None``, the number of seconds to wait before the + query times out. If ``None``, the default, wait forever. + + *port*, an ``int``, the port send the message to. The default is 53. + + *source*, a ``str`` containing an IPv4 or IPv6 address, specifying + the source address. The default is the wildcard address. + + *source_port*, an ``int``, the port from which to send the message. + The default is 0. + + *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own + RRset. + + *ignore_trailing*, a ``bool``. If ``True``, ignore trailing + junk at end of the received message. + + *sock*, a ``socket.socket``, or ``None``, the connected socket to use for the + query. If ``None``, the default, a socket is created. Note that + if a socket is provided, it must be a nonblocking connected stream + socket, and *where*, *port*, *source* and *source_port* are ignored. + + Returns a ``dns.message.Message``. + """ + + wire = q.to_wire() + (begin_time, expiration) = _compute_times(timeout) + if sock: + cm: contextlib.AbstractContextManager = contextlib.nullcontext(sock) + else: + (af, destination, source) = _destination_and_source( + where, port, source, source_port, True + ) + assert af is not None + cm = make_socket(af, socket.SOCK_STREAM, source) + with cm as s: + if not sock: + # pylint: disable=possibly-used-before-assignment + _connect(s, destination, expiration) # pyright: ignore + send_tcp(s, wire, expiration) + (r, received_time) = receive_tcp( + s, expiration, one_rr_per_rrset, q.keyring, q.mac, ignore_trailing + ) + r.time = received_time - begin_time + if not q.is_response(r): + raise BadResponse + return r + assert ( + False # help mypy figure out we can't get here lgtm[py/unreachable-statement] + ) + + +def _tls_handshake(s, expiration): + while True: + try: + s.do_handshake() + return + except ssl.SSLWantReadError: + _wait_for_readable(s, expiration) + except ssl.SSLWantWriteError: # pragma: no cover + _wait_for_writable(s, expiration) + + +def make_ssl_context( + verify: bool | str = True, + check_hostname: bool = True, + alpns: list[str] | None = None, +) -> ssl.SSLContext: + """Make an SSL context + + If *verify* is ``True``, the default, then certificate verification will occur using + the standard CA roots. If *verify* is ``False``, then certificate verification will + be disabled. If *verify* is a string which is a valid pathname, then if the + pathname is a regular file, the CA roots will be taken from the file, otherwise if + the pathname is a directory roots will be taken from the directory. + + If *check_hostname* is ``True``, the default, then the hostname of the server must + be specified when connecting and the server's certificate must authorize the + hostname. If ``False``, then hostname checking is disabled. + + *aplns* is ``None`` or a list of TLS ALPN (Application Layer Protocol Negotiation) + strings to use in negotiation. For DNS-over-TLS, the right value is `["dot"]`. + """ + cafile, capath = dns._tls_util.convert_verify_to_cafile_and_capath(verify) + ssl_context = ssl.create_default_context(cafile=cafile, capath=capath) + # the pyright ignores below are because it gets confused between the + # _no_ssl compatibility types and the real ones. + ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2 # type: ignore + ssl_context.check_hostname = check_hostname + if verify is False: + ssl_context.verify_mode = ssl.CERT_NONE # type: ignore + if alpns is not None: + ssl_context.set_alpn_protocols(alpns) + return ssl_context # type: ignore + + +# for backwards compatibility +def _make_dot_ssl_context( + server_hostname: str | None, verify: bool | str +) -> ssl.SSLContext: + return make_ssl_context(verify, server_hostname is not None, ["dot"]) + + +def tls( + q: dns.message.Message, + where: str, + timeout: float | None = None, + port: int = 853, + source: str | None = None, + source_port: int = 0, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + sock: ssl.SSLSocket | None = None, + ssl_context: ssl.SSLContext | None = None, + server_hostname: str | None = None, + verify: bool | str = True, +) -> dns.message.Message: + """Return the response obtained after sending a query via TLS. + + *q*, a ``dns.message.Message``, the query to send + + *where*, a ``str`` containing an IPv4 or IPv6 address, where + to send the message. + + *timeout*, a ``float`` or ``None``, the number of seconds to wait before the + query times out. If ``None``, the default, wait forever. + + *port*, an ``int``, the port send the message to. The default is 853. + + *source*, a ``str`` containing an IPv4 or IPv6 address, specifying + the source address. The default is the wildcard address. + + *source_port*, an ``int``, the port from which to send the message. + The default is 0. + + *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own + RRset. + + *ignore_trailing*, a ``bool``. If ``True``, ignore trailing + junk at end of the received message. + + *sock*, an ``ssl.SSLSocket``, or ``None``, the socket to use for + the query. If ``None``, the default, a socket is created. Note + that if a socket is provided, it must be a nonblocking connected + SSL stream socket, and *where*, *port*, *source*, *source_port*, + and *ssl_context* are ignored. + + *ssl_context*, an ``ssl.SSLContext``, the context to use when establishing + a TLS connection. If ``None``, the default, creates one with the default + configuration. + + *server_hostname*, a ``str`` containing the server's hostname. The + default is ``None``, which means that no hostname is known, and if an + SSL context is created, hostname checking will be disabled. + + *verify*, a ``bool`` or ``str``. If a ``True``, then TLS certificate verification + of the server is done using the default CA bundle; if ``False``, then no + verification is done; if a `str` then it specifies the path to a certificate file or + directory which will be used for verification. + + Returns a ``dns.message.Message``. + + """ + + if sock: + # + # If a socket was provided, there's no special TLS handling needed. + # + return tcp( + q, + where, + timeout, + port, + source, + source_port, + one_rr_per_rrset, + ignore_trailing, + sock, + ) + + wire = q.to_wire() + (begin_time, expiration) = _compute_times(timeout) + (af, destination, source) = _destination_and_source( + where, port, source, source_port, True + ) + assert af is not None # where must be an address + if ssl_context is None: + ssl_context = make_ssl_context(verify, server_hostname is not None, ["dot"]) + + with make_ssl_socket( + af, + socket.SOCK_STREAM, + ssl_context=ssl_context, + server_hostname=server_hostname, + source=source, + ) as s: + _connect(s, destination, expiration) + _tls_handshake(s, expiration) + send_tcp(s, wire, expiration) + (r, received_time) = receive_tcp( + s, expiration, one_rr_per_rrset, q.keyring, q.mac, ignore_trailing + ) + r.time = received_time - begin_time + if not q.is_response(r): + raise BadResponse + return r + assert ( + False # help mypy figure out we can't get here lgtm[py/unreachable-statement] + ) + + +def quic( + q: dns.message.Message, + where: str, + timeout: float | None = None, + port: int = 853, + source: str | None = None, + source_port: int = 0, + one_rr_per_rrset: bool = False, + ignore_trailing: bool = False, + connection: dns.quic.SyncQuicConnection | None = None, + verify: bool | str = True, + hostname: str | None = None, + server_hostname: str | None = None, +) -> dns.message.Message: + """Return the response obtained after sending a query via DNS-over-QUIC. + + *q*, a ``dns.message.Message``, the query to send. + + *where*, a ``str``, the nameserver IP address. + + *timeout*, a ``float`` or ``None``, the number of seconds to wait before the query + times out. If ``None``, the default, wait forever. + + *port*, a ``int``, the port to send the query to. The default is 853. + + *source*, a ``str`` containing an IPv4 or IPv6 address, specifying the source + address. The default is the wildcard address. + + *source_port*, an ``int``, the port from which to send the message. The default is + 0. + + *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own RRset. + + *ignore_trailing*, a ``bool``. If ``True``, ignore trailing junk at end of the + received message. + + *connection*, a ``dns.quic.SyncQuicConnection``. If provided, the connection to use + to send the query. + + *verify*, a ``bool`` or ``str``. If a ``True``, then TLS certificate verification + of the server is done using the default CA bundle; if ``False``, then no + verification is done; if a `str` then it specifies the path to a certificate file or + directory which will be used for verification. + + *hostname*, a ``str`` containing the server's hostname or ``None``. The default is + ``None``, which means that no hostname is known, and if an SSL context is created, + hostname checking will be disabled. This value is ignored if *url* is not + ``None``. + + *server_hostname*, a ``str`` or ``None``. This item is for backwards compatibility + only, and has the same meaning as *hostname*. + + Returns a ``dns.message.Message``. + """ + + if not dns.quic.have_quic: + raise NoDOQ("DNS-over-QUIC is not available.") # pragma: no cover + + if server_hostname is not None and hostname is None: + hostname = server_hostname + + q.id = 0 + wire = q.to_wire() + the_connection: dns.quic.SyncQuicConnection + the_manager: dns.quic.SyncQuicManager + if connection: + manager: contextlib.AbstractContextManager = contextlib.nullcontext(None) + the_connection = connection + else: + manager = dns.quic.SyncQuicManager( + verify_mode=verify, server_name=hostname # pyright: ignore + ) + the_manager = manager # for type checking happiness + + with manager: + if not connection: + the_connection = the_manager.connect( # pyright: ignore + where, port, source, source_port + ) + (start, expiration) = _compute_times(timeout) + with the_connection.make_stream(timeout) as stream: # pyright: ignore + stream.send(wire, True) + wire = stream.receive(_remaining(expiration)) + finish = time.time() + r = dns.message.from_wire( + wire, + keyring=q.keyring, + request_mac=q.request_mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + ) + r.time = max(finish - start, 0.0) + if not q.is_response(r): + raise BadResponse + return r + + +class UDPMode(enum.IntEnum): + """How should UDP be used in an IXFR from :py:func:`inbound_xfr()`? + + NEVER means "never use UDP; always use TCP" + TRY_FIRST means "try to use UDP but fall back to TCP if needed" + ONLY means "raise ``dns.xfr.UseTCP`` if trying UDP does not succeed" + """ + + NEVER = 0 + TRY_FIRST = 1 + ONLY = 2 + + +def _inbound_xfr( + txn_manager: dns.transaction.TransactionManager, + s: socket.socket | ssl.SSLSocket, + query: dns.message.Message, + serial: int | None, + timeout: float | None, + expiration: float | None, +) -> Any: + """Given a socket, does the zone transfer.""" + rdtype = query.question[0].rdtype + is_ixfr = rdtype == dns.rdatatype.IXFR + origin = txn_manager.from_wire_origin() + wire = query.to_wire() + is_udp = isinstance(s, socket.socket) and s.type == socket.SOCK_DGRAM + if is_udp: + _udp_send(s, wire, None, expiration) + else: + tcpmsg = struct.pack("!H", len(wire)) + wire + _net_write(s, tcpmsg, expiration) + with dns.xfr.Inbound(txn_manager, rdtype, serial, is_udp) as inbound: + done = False + tsig_ctx = None + r: dns.message.Message | None = None + while not done: + (_, mexpiration) = _compute_times(timeout) + if mexpiration is None or ( + expiration is not None and mexpiration > expiration + ): + mexpiration = expiration + if is_udp: + (rwire, _) = _udp_recv(s, 65535, mexpiration) + else: + ldata = _net_read(s, 2, mexpiration) + (l,) = struct.unpack("!H", ldata) + rwire = _net_read(s, l, mexpiration) + r = dns.message.from_wire( + rwire, + keyring=query.keyring, + request_mac=query.mac, + xfr=True, + origin=origin, + tsig_ctx=tsig_ctx, + multi=(not is_udp), + one_rr_per_rrset=is_ixfr, + ) + done = inbound.process_message(r) + yield r + tsig_ctx = r.tsig_ctx + if query.keyring and r is not None and not r.had_tsig: + raise dns.exception.FormError("missing TSIG") + + +def xfr( + where: str, + zone: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str = dns.rdatatype.AXFR, + rdclass: dns.rdataclass.RdataClass | str = dns.rdataclass.IN, + timeout: float | None = None, + port: int = 53, + keyring: Dict[dns.name.Name, dns.tsig.Key] | None = None, + keyname: dns.name.Name | str | None = None, + relativize: bool = True, + lifetime: float | None = None, + source: str | None = None, + source_port: int = 0, + serial: int = 0, + use_udp: bool = False, + keyalgorithm: dns.name.Name | str = dns.tsig.default_algorithm, +) -> Any: + """Return a generator for the responses to a zone transfer. + + *where*, a ``str`` containing an IPv4 or IPv6 address, where + to send the message. + + *zone*, a ``dns.name.Name`` or ``str``, the name of the zone to transfer. + + *rdtype*, an ``int`` or ``str``, the type of zone transfer. The + default is ``dns.rdatatype.AXFR``. ``dns.rdatatype.IXFR`` can be + used to do an incremental transfer instead. + + *rdclass*, an ``int`` or ``str``, the class of the zone transfer. + The default is ``dns.rdataclass.IN``. + + *timeout*, a ``float``, the number of seconds to wait for each + response message. If None, the default, wait forever. + + *port*, an ``int``, the port send the message to. The default is 53. + + *keyring*, a ``dict``, the keyring to use for TSIG. + + *keyname*, a ``dns.name.Name`` or ``str``, the name of the TSIG + key to use. + + *relativize*, a ``bool``. If ``True``, all names in the zone will be + relativized to the zone origin. It is essential that the + relativize setting matches the one specified to + ``dns.zone.from_xfr()`` if using this generator to make a zone. + + *lifetime*, a ``float``, the total number of seconds to spend + doing the transfer. If ``None``, the default, then there is no + limit on the time the transfer may take. + + *source*, a ``str`` containing an IPv4 or IPv6 address, specifying + the source address. The default is the wildcard address. + + *source_port*, an ``int``, the port from which to send the message. + The default is 0. + + *serial*, an ``int``, the SOA serial number to use as the base for + an IXFR diff sequence (only meaningful if *rdtype* is + ``dns.rdatatype.IXFR``). + + *use_udp*, a ``bool``. If ``True``, use UDP (only meaningful for IXFR). + + *keyalgorithm*, a ``dns.name.Name`` or ``str``, the TSIG algorithm to use. + + Raises on errors, and so does the generator. + + Returns a generator of ``dns.message.Message`` objects. + """ + + class DummyTransactionManager(dns.transaction.TransactionManager): + def __init__(self, origin, relativize): + self.info = (origin, relativize, dns.name.empty if relativize else origin) + + def origin_information(self): + return self.info + + def get_class(self) -> dns.rdataclass.RdataClass: + raise NotImplementedError # pragma: no cover + + def reader(self): + raise NotImplementedError # pragma: no cover + + def writer(self, replacement: bool = False) -> dns.transaction.Transaction: + class DummyTransaction: + def nop(self, *args, **kw): + pass + + def __getattr__(self, _): + return self.nop + + return cast(dns.transaction.Transaction, DummyTransaction()) + + if isinstance(zone, str): + zone = dns.name.from_text(zone) + rdtype = dns.rdatatype.RdataType.make(rdtype) + q = dns.message.make_query(zone, rdtype, rdclass) + if rdtype == dns.rdatatype.IXFR: + rrset = q.find_rrset( + q.authority, zone, dns.rdataclass.IN, dns.rdatatype.SOA, create=True + ) + soa = dns.rdata.from_text("IN", "SOA", f". . {serial} 0 0 0 0") + rrset.add(soa, 0) + if keyring is not None: + q.use_tsig(keyring, keyname, algorithm=keyalgorithm) + (af, destination, source) = _destination_and_source( + where, port, source, source_port, True + ) + assert af is not None + (_, expiration) = _compute_times(lifetime) + tm = DummyTransactionManager(zone, relativize) + if use_udp and rdtype != dns.rdatatype.IXFR: + raise ValueError("cannot do a UDP AXFR") + sock_type = socket.SOCK_DGRAM if use_udp else socket.SOCK_STREAM + with make_socket(af, sock_type, source) as s: + _connect(s, destination, expiration) + yield from _inbound_xfr(tm, s, q, serial, timeout, expiration) + + +def inbound_xfr( + where: str, + txn_manager: dns.transaction.TransactionManager, + query: dns.message.Message | None = None, + port: int = 53, + timeout: float | None = None, + lifetime: float | None = None, + source: str | None = None, + source_port: int = 0, + udp_mode: UDPMode = UDPMode.NEVER, +) -> None: + """Conduct an inbound transfer and apply it via a transaction from the + txn_manager. + + *where*, a ``str`` containing an IPv4 or IPv6 address, where + to send the message. + + *txn_manager*, a ``dns.transaction.TransactionManager``, the txn_manager + for this transfer (typically a ``dns.zone.Zone``). + + *query*, the query to send. If not supplied, a default query is + constructed using information from the *txn_manager*. + + *port*, an ``int``, the port send the message to. The default is 53. + + *timeout*, a ``float``, the number of seconds to wait for each + response message. If None, the default, wait forever. + + *lifetime*, a ``float``, the total number of seconds to spend + doing the transfer. If ``None``, the default, then there is no + limit on the time the transfer may take. + + *source*, a ``str`` containing an IPv4 or IPv6 address, specifying + the source address. The default is the wildcard address. + + *source_port*, an ``int``, the port from which to send the message. + The default is 0. + + *udp_mode*, a ``dns.query.UDPMode``, determines how UDP is used + for IXFRs. The default is ``dns.query.UDPMode.NEVER``, i.e. only use + TCP. Other possibilities are ``dns.query.UDPMode.TRY_FIRST``, which + means "try UDP but fallback to TCP if needed", and + ``dns.query.UDPMode.ONLY``, which means "try UDP and raise + ``dns.xfr.UseTCP`` if it does not succeed. + + Raises on errors. + """ + if query is None: + (query, serial) = dns.xfr.make_query(txn_manager) + else: + serial = dns.xfr.extract_serial_from_query(query) + + (af, destination, source) = _destination_and_source( + where, port, source, source_port, True + ) + assert af is not None + (_, expiration) = _compute_times(lifetime) + if query.question[0].rdtype == dns.rdatatype.IXFR and udp_mode != UDPMode.NEVER: + with make_socket(af, socket.SOCK_DGRAM, source) as s: + _connect(s, destination, expiration) + try: + for _ in _inbound_xfr( + txn_manager, s, query, serial, timeout, expiration + ): + pass + return + except dns.xfr.UseTCP: + if udp_mode == UDPMode.ONLY: + raise + + with make_socket(af, socket.SOCK_STREAM, source) as s: + _connect(s, destination, expiration) + for _ in _inbound_xfr(txn_manager, s, query, serial, timeout, expiration): + pass diff --git a/venv/lib/python3.12/site-packages/dns/quic/__init__.py b/venv/lib/python3.12/site-packages/dns/quic/__init__.py new file mode 100644 index 0000000..7c2a699 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/quic/__init__.py @@ -0,0 +1,78 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +from typing import Any, Dict, List, Tuple + +import dns._features +import dns.asyncbackend + +if dns._features.have("doq"): + from dns._asyncbackend import NullContext + from dns.quic._asyncio import AsyncioQuicConnection as AsyncioQuicConnection + from dns.quic._asyncio import AsyncioQuicManager + from dns.quic._asyncio import AsyncioQuicStream as AsyncioQuicStream + from dns.quic._common import AsyncQuicConnection # pyright: ignore + from dns.quic._common import AsyncQuicManager as AsyncQuicManager + from dns.quic._sync import SyncQuicConnection # pyright: ignore + from dns.quic._sync import SyncQuicStream # pyright: ignore + from dns.quic._sync import SyncQuicManager as SyncQuicManager + + have_quic = True + + def null_factory( + *args, # pylint: disable=unused-argument + **kwargs, # pylint: disable=unused-argument + ): + return NullContext(None) + + def _asyncio_manager_factory( + context, *args, **kwargs # pylint: disable=unused-argument + ): + return AsyncioQuicManager(*args, **kwargs) + + # We have a context factory and a manager factory as for trio we need to have + # a nursery. + + _async_factories: Dict[str, Tuple[Any, Any]] = { + "asyncio": (null_factory, _asyncio_manager_factory) + } + + if dns._features.have("trio"): + import trio + + # pylint: disable=ungrouped-imports + from dns.quic._trio import TrioQuicConnection as TrioQuicConnection + from dns.quic._trio import TrioQuicManager + from dns.quic._trio import TrioQuicStream as TrioQuicStream + + def _trio_context_factory(): + return trio.open_nursery() + + def _trio_manager_factory(context, *args, **kwargs): + return TrioQuicManager(context, *args, **kwargs) + + _async_factories["trio"] = (_trio_context_factory, _trio_manager_factory) + + def factories_for_backend(backend=None): + if backend is None: + backend = dns.asyncbackend.get_default_backend() + return _async_factories[backend.name()] + +else: # pragma: no cover + have_quic = False + + class AsyncQuicStream: # type: ignore + pass + + class AsyncQuicConnection: # type: ignore + async def make_stream(self) -> Any: + raise NotImplementedError + + class SyncQuicStream: # type: ignore + pass + + class SyncQuicConnection: # type: ignore + def make_stream(self) -> Any: + raise NotImplementedError + + +Headers = List[Tuple[bytes, bytes]] diff --git a/venv/lib/python3.12/site-packages/dns/quic/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/quic/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a0768b4147f214003bb60cc948deb35c4555152 GIT binary patch literal 3293 zcmb7GQEwZ^5#GH!9!ZfBDN>{)%eE-VrX!;fV@XL0MNaCvPNLQo5?IN@p}>LWEhWml zqr7)?%0LF>L_qu$=&9DDk`|~1BS3#cAKMqQHlW~Q6#?>+yeXywCwa=u9*-hrBS;pw z*|~3b=4NMRXYRkfy*&iRfByadl^Aee;fp^4EvETBCFBlyhZw}5Wnxl|nvy1&vL>5~ zrclJCazInX9Mn{%uEu>Ohx2OkkoP%5z6ZK-@V|kyQtmM$T103Jl%r;^)+=}w_&%*q z@Il~XT1@bva@_3K`UM{bKA|N9-&0PS1KNP#BfzJ$l;ESlr?oWjz1pDBw@I`iBL;NX zh_fL!T$D;wJH{k7!W2dsElR^+nMc`iqkkUWe#O}m?Xyh?&z#Usz?>|fG)J{jVb1{Y z87(9D6!2r(81QMZXxtbCI>E--L{VbLj0Aq$7&nGCsdmabYYc0H5_y9d$2N&Ea!-Oq z7}{xp9S7`08|xR?Nx(+iSV~|Sz{c9x0F&X=S(f$AiznC4)TGIYZ&3B5;>s7SnydVw zRB&A7a>;hwz*2Rs%obhQs9fKrZe+1qE?=xz4*RP!N!`>1yJi(imA9*<0-zQvIHigO z`w|@sf6iO^WyWE9e+PQS;Vf?gm=s{Q27u#j_)tD^1-|yw&ORS)tBmX(cDPp7_DMLU!pQ55fARYf(-K*i{y{&c zR|p?~T|5G0S9XI1FM1q5!qY%(bVw$X;e#-{Lweqescw34Yg={#mPc?ussqbZ5+C20 zt;eSCzSM|K*F)1B^T`di&gXcgh z2anwkWP?0R4(=sSHIk?9C$sfvR>(g)J{j2GY2-Rtrt9=m`k8t(PUEnX3qN&OdDyY8Jj*1Lh7{CmSan;JooH|E>Cc z!ruh5@6(&aQGIxwtcHF5aQ1g%e)tgxKuNJ|H!8B_DqIj|8E$f!Il94$`D)qGTOc0= zDOa)bCWF)owon^yK`Uib-ra!=(~A|}mOBSKUWUj14Ui4;c_eZBwTJ29y>zyb&hDk> z8tJ+F>G|7lJdBO*1pgWRB>MNhMl4qk<%GXpFMw2g_;;=u1?Uc8M56}Lq#A|#5NlT| zFH0BUmW6a34c2wHN7v1YQ7t1M)%EMueA!#!XF%0q#_<_^Me-UvP4xFB`8*W$b|Btm zke}nNBIX-pP$m~8L)wI%U6Usj-UouN%9&H;R##@Cf)C+oVhQ*Yp_rY(dE*26nfx9V zbptMDaVIi~@F$AT0qKek8ixmNqS@V% z@v1rgD)3izE9~zmWNzqHpg4(#u4i(0pa!zS6Rv5U~az<>)gY! z#CSM1(<#4Warn6<9CM2Iwv~H33Ki3=SRzX}_Di=9wR&sV?zlLF2yOzG!}w{qtfhiz z$Q*m0SBPZDZ#SmvdCRIedHmh6b=~VG9Xhdj@G%scKynHR_70Bqnqy@WSKSR_H s(EBX`@6FA_DSez?4XB4Vz;YX?`&V%`b#4=-sqXQ6%>j46JjUpWB>pF literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/quic/__pycache__/_asyncio.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/quic/__pycache__/_asyncio.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..116ffdfb480b4303cc210635c9dca38c2929fa0a GIT binary patch literal 18865 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 E0))ZV2mk;8 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/quic/__pycache__/_common.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/quic/__pycache__/_common.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d15bb75abba909bb818d9954098fd66db4c0a68e GIT binary patch literal 15041 zcmd@*TW}lKb-TaPNzyHbv?;+iWTJnykav-laG4(Ba3RC zx-*@gbN2xdgzUKe=tr-Fv-kZt_uTWkmp^y892A6~{p1%Xo_>U)euW7=ShUEBpQflw z6i@MVk{VOL^cYRjrZE%wn#auK%ZxGPYZ(ah; zV{Ve~fv0wkA(T>CYsg<-6L|}HzF81>&vH#_nM65p&OHWOuQ+?AJ!3NzC&p>}oYg&1ZJ_!f{ zJisPS;&G@7f>pBWS+CL=6Kv<`aF|id$#{w^S6w&~B_M$;+!~!q&!i*~N7Yx%oQR%^ zXPw%TF|iR!#U22!P|MD`ymTRVKDXoyEm&_kdrH1W4G+upZ3V8a4S=^=u9UUj)d(3N({k{0A;Zd zK!CKT;-5@sJtmQ;1t$W+l98neZXMuWiR}Q+QTI(IOWS?cV(GkJXR!qC*U^@B_pOY@ zzrq1f8)Lf|{EVQJ&dL$U`fU81=Xl#Wdd$v)ARcq@c7RSE1n?M_afV$=-IGx<{tO74 z1o@Xrf#^-7Qy^bf!9bo*o;9%-8r-e;WU8v?^rUak$R?RfQBXNjGeJX{YJ&*Lg_Nacw3G~&P zfK5YnwxMbkTK=)sQ-Q-)U_<}wsUZH>;DXindd=SH1;~-za>3;`pbfUZdZ z&Com*JtORdlrF+hED3~B<4}g?6x1=IqNj|mwHVS+S6y)ox)UD*fEWrCoE^&zP4gSy zl-|l*$$amNa`R)?_boN-$R9*RHRkI8PmL}4gC%!EUQ;p|XsZX51_Vt%0P=a54l-#V zOK%41mlhSBWqt{$(zwH`bXn<|M6pv9CY@CokSwbivrLYeMEb4@lPFB8f! zB-Dl}?0BBFg$`EEb=ZRvIxnJ)KO% z2wOzMcQZ5=!3u_ogjuIkQamMXmNL_E;Yp}2)&YPSl3cJxCZrRiVW02_z(>9i;*cF2styd+ULggseRlnUD*AtEaPR!D+sD%;e72G&SlEgyNTxE*$g zC;~-yo9u2Yy1Qj}_iguv<@(mbx&uq~!v$`5+24HWsf$k){bAW3UhG=(_vP&+cMwgV zqImQgcuDW|1Wp@?Xpv(q~Y`4sIFYs@rucWV^zPbMJft|Yw9=@wh+P>_Ccmff>t2LnV1A*1MSOnu|LCpzL%Poh`DnrReOCogKHGT_s;j{^@1TJ;#3SSe{;X z2QGZ^{1@l9E_j#RT?;2a?{#<=~CbfEjB!~w;Iy=IMqc-$OcJRPYGt4`aG2}ouFJRalldO98p$>b>)7j=@Nj=fP=)ex>&64OCr+VA=l6*>c^Yx zLp#hr-cAEtscwL3)65znwX=aW8-ksD1KNpbQITC(%cgF#gR6JPH_yMvc99b_a+Mwp zyJ|Wl25>qkEQIX{b|ct}-~|LG_Eu^TMp6pcQBUz`<%@ZT&Xu%)~h{YKQZ_j0ZAOg3LHq-U){BC zhqG&kivjRO)8LUtUo=B}(H7nT-e?+p(hl%P)8LhM68{BD{1^R?zYHTE8P}}Rc?!%^~+D>te>PP3R@)fV3SZw zv7SUHYE_uTlV26UCurDKFdswVPr*%2Wm-aZLc%2KCRJh5PoLHndE8Knp)bRX$T6=R1rx(`5{Oh726o%7w<&+69z8yZv{V&NLLL|_=2<4y^QrAO zRyBsC)}Dd(R%w%74>taO4;?8(_W$)-zz)uTF<8Bg2GiXKh>h$iJ*<*RF6-u|)WE zDz2)bEHYgI)L6)!P74xTfDo3zgNJG=ngZokfSBrp5{c|qY|%s-Z+sNfi2-2?%uC@8 z4V0-HU0Vah8K#&)M^=~w=%7knbUZSi7L3u%k?6S6m`tCJB;%*zNj*Oz%}gibIF~pK zY!%b7lhD{UG#oiRa$rXxwnyMf7J|L`=^Jv3S@<)K_(KHhqDh zy~6sSPpqOi)vvZ_ZK=d8sMKCnKbxU&lvio^+U5ZP;=clTg}PTyHK13`VL07_ZN$44 zXd=0d=xgIjT4oUaw=UHMvatQF$FDs8=HBH{*BeJJA1Q_&l|zpfLwn@Vo*UVBw=IR9 zfoqjxcSG&@{jVKc_BCAEadAh{w^8-x}reCGU_*9YI&b9v7#cevEldC^(|0U5j)eEs=1B9|k#yqmxq zc+vXnhH$|X#yg!*=fcrK%NCfyQ+He1@T2NT23vx~BU3`1X$76f50i{MH3Mp;=6DjO zp=Pyzj1X8hxRz!15o_tMpoxgmjF<`+z)`obzu@jIu)XSx4Aux$eybVf=*Pjv)Mu20 zX4(yr@F)(2eQ4K1LKNcJwi+|5l(&C_tWhNEZ&p`7qqn-?0o+^WzAVy4QBzslJV#S(Etuz^L zVmst|#$Kp90T7h}*LkpOI%T$VVdvt+^;7S$d(+G2 zhLQYJsPSnSXp`Bt1^eRG>kaR*JJu2@VuKu;cOX)EBZ|6jb-1QwJnRN;(zvE5w=~+A zbLLkpITNpXg3V!5Dr^E6zHlm>f$n0UUk>yy1s=^ih&Q&i)D)VRE;r|o03)8uF6@&VH!OOWe2?T= zyb<30)!lMv^8=O&w#@HX7+q@Im_JnVG|wL@xVsB%_gWgj{U5+iyF#Wlj>Yk*@L-aYq%596c0~C$nNMSIdOColMLsnvkAo&sjOT!>MDg~ zWsUcsv1U$G;@KIP_uX=WAI0!<0Lujb&U;)R`Zv@&ZQTHci}GP11|5YRHEoN1P()a@ zfJ{ulLo9p^!S5qL(j^<9^0_!B5nO_wh%E6O1s)lXzvK^;JpL7TyQTg9ewwy~(WdBL zv6?JMA}Sr1udLxKl=GH!-52>L&7jx*zEM!@?f zFchqcZ%E9fVtBh*M&Jm!Ur?+F z8kMq4aZW`~>Ly~gt;V>06&04THCm$oLj8sqg8xCp#j7Sp9SV*%3#H|2XT>s}Oh+Zb zjJe-LP=)t7%tLSoej@VTbJT|>+TvOaT=!oM-J>9S)As;BDt@u!CAZDugdbb}2a4_V3MAsd)#_=JcKI~2FhCyuEq??|lD9LL?n#jLypD$gZ~ zSW}Qx1F^ykRfv@7vr}tgOA>Mx(9ei8wT zd=ET&iy0=ksQ4;Uk@09uN(&jq1CPE$5iUJ}0ixw0f;l9mPsYKFF{8KMkq{FpQHrKw zap7|?LtzT1$q;ME1-*|aCT7%IQ^l*Df>9k~*tKvO zvtLK>9RTDSgox9!OjXSvLm$^phhh8t5}W-L0N6fOsuhf#+iX**Ya4#sOYNQbZ7nsT z^~05X0W?!UMCNyw;9mFWvb$;ikvGNL?ng@N!gJ2jy3RT0`)+TkvuB~Z5Og<|}5`Th4Cl&7WO?pf|w z|5odj)?&xAa>uhJf7^=L)LDPAZq7Pyg;{z7MbAdrv+;V*&4z+!<899&ko55Q!J$g# zO3w_kHFEBs2ly~0{b7oD*{c8-d$un#IU7%-egx9m-~rIT7G_yE-!BpuaV6)`N^3a+ zQQ5B;!(QeilMcPt@;AroXA?%U=h*TimQT4trE2+laBPEHI8yL#ch-@!YV$YvMtSt# zV*bw0Fn=e^-{6#=I)BiSs>7=Jb7cOU6w+%=q77RWCflnrAUWB!HV=54Bgeg6#*Rx` zuh*J1yhVBr^bsz{^;p%;*5zE2kLYFPZ>~;4|2qkjoAg-udrga0@L6gS-=tK9oD0W8 z?L9|b(`XMV7U;C9CMMOn?Z%jm2hSO~I54CTTpTcXaWF@}wn@J_fQyC7a&oco+8wecRHGqswSd1NTxbC4k_Ti3O_{feFQfU zTnC_7UdGWBdormxyhN0#Wrkkn>foW%CRTQ}7_40@`7dZI{t4(cs#~kM80e7$J;lIA zIk0i@;PuhPy-R`L(JZ2ll7HQTrQqu+a6RZ?%AU{SJ?y%BCYviz>gXc**T`vvvrQS?df{JQ9b>|?m@YG5LED@r&sp$ zE^fHK6_|KWoy>LKW2v5v1+MdbXF%;f7d>35l>5O`_Ovv%y)p2kbuMXu=CAbU0xJ%h4m5Cm1JYiFq$ z1&wz*$QiFAZ-H*W*LLXQp`w4Q?B9BQ;)ZlRvgAJix6!N%9UpUvH~nulU1|DW%WZZu zu_HIjfz2h(JGXs)|3c5=BiAKZPJ<)w0D086BhM7sCYf!T-@dSazF%fHqPYlut&RfQ z@tX$+Y04kEPnleSfA;wC_A2n758CytUusz&i^z+9xO14Je&X9X9HQRg?88mwcY-v+ zkbSt<{LTg%U<~D%9vFyVdgVy~m(&YNy{zJ^o_TbX!C5wRTjP?D($BJLO`6ox`RCAq zZa*5tzWQR&s#^KwcL6#Ls`-OJd?GSFpg@z^?j_^^mwg=z+ZXpQ^vj;dezWXu#7jKy z`#1+p*O|XVuUmb62OX;GMUn6@d0uu<{t?uw?VvQS%k&u&@8oWr zu#-$!bF=2%s#1Z7)`NlcUqDW!xnJ9iH^er)n39Zs$bMhlW@{WI*8Q z^1UxS?zZGC`>8Jg7HT%q2AE#dPGUW29mfN8$Ai%G>5e5MB5GAY1qu9z{6TAarDR8#*iAtT#WW##qN;QV`W6Vp)p6$*SfF3%iO^mNOf z?j^1#PnVn=u&7(kuI1K_h4sbG?Q-Y#V&`tTbN5o`o~72kH|g6?juu*<%a4>AyMZ!Y z{d4A$Kd8T0k^SJ&fXus(ZpSm zfu+uEORa;~U%tKbXrc8fW3zk6X5$m31)uEi#;xwT+q*f>;QNKSiFs)*a!Ydn!qd{> zu;qj2fct6>D@GskdjBTOO!x_ceF%__1zb|KPvs9W55ZmdiGu*nQFo6#y<%>$G~auI zrs}-FlhCgfYKK2vS#b}-lm2O%v3T!uv?X}oYPalIX|P+==P#tA81!KTo$arnxmk9& zfFpw3iGve`Q9OntNn?v6G2IV_{UF03(gxK*V0@TI!mFAei59Yu)_lN%aoo!qQkBD3 z_-m-14OWlZr)e<1iyMn39J+N;mJ(hJGU*>6PkXk{RJpE5m({5KN2o6#cpiI1)^d(| z0PdCv9Kb9qJQIY!1R!oQC7|7@ z_MI5e=MP>8)xFTHm!X>hOUo_=rLnq9{|f{cT)p-gt_Ichz+xV`jrBhB+~Iu@WXAi4 zMu!eQJ9JDvad^%Y`++m~;1S2i#j1b6(2KD+f;j|)+z4V&;HijkLF{98xB5qhdjtiN zxaT5j>u?xodc{i7b$2M|9m;cua*_WK_x%&q@GB~Ghw8XPt@{<_zeBn2`hx|^TWV-5 zP(DzR8p4Z1pg z)=X4c^SbAL=X~GyJKy;ZzqH#e6og;g{cpqnqlThB#UFBDrNqXs zA#s%oQbBr*8Wf-OAWeSj26g1g3^L@YAJmg4JIIo!VbDmPCh2LGo)+n8m7caiJE@mJ zp1Lu|xO31s?izHBXAWkLXANeJy9eDg6{1IV^6=E>V789xqk_yODyR=-y~T(ngE=IZ zgCdnX$%@5*A#@$7&He> z5L<#~h^=$fEA*i46y>uC8KcKN1woe*e%|AQJKWBREvA*L!iRw{du4Jk&TeF?MFEacDRYjZQV54~5S+ zj*XmYJU0~`o(MNJ?AhBG8HtAK&jp5Fg2|3F2E&oYmw~*E{s<7d;oKBwhYqK6Q_0>S ze}xwcBfBB`@*=fnvR^jxCeLF2(sMCW>5?y2QHwX2@`IsLqSExY*Y_pf|^HM??7 z|GH%Z+E6L8hZG~T_fKhLOfxk@jY29t!2C(MvoNPxjMGBV*jAY{wTDronwC>DwA>$O zF{2oOf9eXE!WEM=|LNuM9*uP%jX10c~h%{`ps!=co(xw)`S7->2*6kV$+N!Q+pPjE5#BqyDqp z#JC?Ae}oI*O671Yg3*6w^6Xhy*ZSeW`H*123MfayFLF43L4P4I5)~L&Rf0M6%24PW zS>(n6u}`0#%fecQ!1%;uI4W4w?ggXtUU@-#xm>Jj!VdjsC%A1(QZA^3YLT}gx=1B5 zG7~xZ@th_;r)lNrYEJuHU&57rHUCQf=M-c2C9-m_wq0q9XI1c76-&>?vg#7<{7n{0 z*0FfqK$ZIz^nd8sG~zE4m6>z3=t|M&l+M08ky~(e_R4HL*T?7jmaAg9d)M-duGe3y zkLNe?`Hc_Ujq42MY1(9gq))OeFpHU`=1$NzQ-e+8tH2H}oe3WrcRY+S8KuiuNEn zLSqenGubV_ZwLWnism?l-{p|Ur9MG6Cn1oXIV9?m+h|GsAo8=8e#w?wsn4K7Nm;Kw z4^m?mp>4{Dwa5D^^YzC2^~a;?J1wfE^X3$_18;s$J72EvWBNZskvXj^J?yiB9#eIT zsLCY$B3S)Nn2H^V_=lUgYG|0m=*AaAQGZ};j6>Zb7yx%o4n+mSWEf$Sz(A>_$q0`V zZU?+JG95}&UPdl76dE}nn$AgMbt(UMq5H@e5CNBG=f$)4^4WV=T354wcg~WqI^xy> z-dYg1mh#rpn6*4%bH;5&ysc<)G-mTDdX2U>pmLhIG850O<#TISbL%%5$o!mw2>9Oa zdh__@gE3omQmZa{E#He8Y+1h?d}r*|SiIp7-*D*tmscA)R`WZr7#G~wPZ#}( z%}IKwShB=ywMjjw@2%ov0UA_OKgFchMj}l>7(^$UElzZWD9Yy0_cS zd{AD{?PWeRvE44?hi18CF|Ca|Ox7!RFGVFnoj z-w1)GrYKQ4=mi@H@u9%E$mCcc8sh4pRA4V~0ImTQDM(#LJf>hE8hDX|-IM`V97umb zk2z`U1u#G8g{-v2f|*C4`$z*suu>dZafg?8c$Yk@j+!}r!eon^Jb=UFrefYyycoUy z>a|x_^ebl{nhtUvXp%xc1xYMH$5!XBcg0#IV;dEcMU$MuX6}+<|=G=umQiW@~3IL#O_ohz% zxfMYWalN2J&)m_o9d_f8w2gt>BtUl-evoM(*{P94VrlIqmpzG`PbQ{L#PJ5$KTbqK zw`hV_A~-Y&qp+l4+MPxyl8d|iNEJjtBakbDmj^-0nDaq0z4`Rzr{|9YG0gd!m__op zBB*a)cU3Vr+1;t%k1r%#jO!7<|CGpAw@jB**S#JxRi?)BNxxhrr}v@%zUo zf|FyIw)y=pPX@-s5@KW^+ErA&P58a24Y_uV$mjQB3b|6SOV*B|(Ab#Y4{J6Da>){T zir{L_j8!p0G#(-5iA&UD9mDQ=WU;W`M;;Tqd&6yG8#W4c?4d^{hOK&J(XsiDOf>8M zdxM_MCJTmiF$7&G0hcI-4OBK?!@i{l>y-g)TNK!~>|ouZ!MbH9wlL!*GuXJqvr%@b&$nrRxqP#1p_sv zpJ8VVZy_f@NVbM)E50c9OoA5j1e7UJ2ka$MpRjwQw03Gg>?hV8Wj81H3wt;P^{RPM z#p*HYR&(XlDDFDag7#arze>i$vJHmp3$#XtJM^>wJ>sS>EvP+3aSf6%>Xi~n4_UfA z5Ay^xQ@$M1$``{MDw3K=$x)QKX(5=pv(fs3uV@wZ$z6`t$SL(Hj}}IU>_$Qm$F0ym zsXek>T1aZ2Cl{fnk0K-?p@QNHcceaUxYL5%BTfB?x*+Wm0QD2gRj)6fLlO|dB4k9s zh6)h=Y+xul!A%K_KX4XcJjoO+r=ID0+TTs=;YPodiHS*WC?x35jKKaL<%pmk35TLW z?%2cy|5)gJXbiCe7XqVebn@I-NHF)v_AMf>1f#UA02A)H5Ln18V%O&aBOC}n1aZXF z3zlAoU&8-#hBXq3o zL7BsaM6|lkBw8PkYXqY-T0wtqf`iSA^nF(G=_i@Sana;23RGlwFjZiVH3xrpIk?jM{?qq!W2O^pxs^-7o5MGTe`<*D?%;QK+}^XA+XDt- z8^EkL+b*{~u$Hgu4E7vER<+zS^S7Jt?Z2~s z)qU(SqjP1h+bFaBP1|L_zJvYI+!z6;o0{RDnCPKq5v!MTo0aznRHn22Bqd=C>q+ zg@)-`W>#5AYzO|z_tNuIQ?RcB(V(ai5Dg3(ucs77Pe--$UJ)?)^r8Ux42>h7A*=+u z-9Lecpa&*9&mp1Uh)ZIwBhU13+yp0X_3N<6DYK%yLF3Ml#%L{&_7wsf;Zo|( zf(1qn1XWm?%pq8Us3GzP5CO_^y5`tKM%Kca*G3aI*TRX*N9TIhb&NeXQBZQd?OI#B zppGx7i`5@mEofgbi3U4jxTfu=Y2b~x)5|-(apxZ1xo4&N{q8?M`m>{drT-iAUz&eu zjX4K!A99*rw^I)H!kO1czkF<>+~tp8Uy}P@oH^@In7g&9CsiQtG`$rmgkil=(#cRi zJJ6ZQ+-1z24(4tKjd3R1S&$0XacPm|`aC4?tmd06Ow}F*ClN(3&B{XEKFx3v@tGIi z0i+MXgX1uYVAKgw^0vV44|0K#up~mKz1vYzLrRTTv3)BFvDsALz&L7@N)GiOBL{;4ndNJ8h`_g zAkr*qNH1zs10bsJd*Zt^K;&qtIwCn0EtgZHWpYY=%I}iLIEy|CDF)tGjX)#lm(a*G zYamRkv8qPV%$XKuOme?KyK+K14JjX?=l?GKtvi1?8nOL~KGJBXHMZKcfx%Dt%L#Zy zDV`Qa8`Co2r`l$_xoQ4?YMYklZ^-%4U2>1I$eRB%8G>%pOg~gwRc~wc=8ml{%B{XF z{ld2Nk%SqsPw4Ug3T5SQ15n;O;PVJ(aAAPM;R!AxIJ!e;1CwLXQ&Ns#;zBQuM8K>g zDr-y6vpxL-{*yf&-P{GBnkfCJAq8+cI1vtk4G@j;sFpdjd`yNSfN4o@9I^Z1778o{ zh?l|MKjRycC)eXz=q;`WF!c?g&8n+zopRetSD1t|3lTG5-{-GA|Mu_1YuowS_S^1z`FHYT z&SO$(!tIH>t9f_z+rD_sA-?9&ZGHTyll)UBMHp?M0Djc`aZRFrPrSa1ukX6uvs!GMhxh2HDOt{saP*7Jq+%fk-~ zTi02rv7Sx7<;OLfRv^!MGah+L2&ybmR0=2i7DP#9c%X6Y^0BzBn70+jY+m>sw^i`A ziny(sw^cv1)ol7;%wW{bZpHHo7zCyV=-)iHP~Jws{duLoD)a)*&nw;9ti*TWeEjBd z4du*xL@{R9=XmV0ZiLja3*ul$@b-$ z?`0by|6U>6S82XiO48NlzGmaSJ=o{HCK}Vt45nMz!<6w}n-O}r&zKK8nEN)};bQ83 z290S4gXv86aDnlDvHi$F=6=D-bmvkGXwRb;run&@4f8evy!?!zP zm_?tyR6R&vq^GDT?zu>qp`UEp8r8rwS7>zJgkQ%pDN8#TX90~g6($1TF>)aFV` znw@+lLrG{MBS3T-s7J;av*>S`I%j&o785i2QLoailDL>T!)PI*8NFY@DB&V#TcI>j z5*J-NwpC^aYAE_%X$YH-`S+kt#b~CM1a*L5K~iG?wDgPLaxQAbo|0p^cNoL}`bU}# z-2y$|s{Q>yFAWr?O-1=jXjr-P;Ago&hbRAWz6f}7{|?_nYkm(siU+W1A^Cg4&ZL?D zHZn%bj4G`wt^laCH|^!{bF^i9o{DwD_kPO}?bGO89#PTG^F|yJ(ZWDNI&X((e#=oI z;7begJaGLxj|xiu!K4+JslAp`Wy1n9e?A?hL*5A)`=`2XF6ibj?NCC`|6<3Iuy)CO z`wsbjF&|L-067Np6(`xpEa3Yc^%MLoSVkfv;Yc(P20OViFfxID!-Af~1`%PyVHNRF z?grn6lcMiSkgJEOfD1$PC{o5yB)ViTe<^(MQm6h3o@F+xjK= zIbEuS|C8wxS%zwnRk960?^fBHmA3CAFuR)fRL4C{yr*f^)3U%KzTF4Mv~#lwXU>`< zZ?WJ@9_Zs<&g|NTQ;CgK}hlk6tvb6;v(_|J3-N#`bt-P6k z1Mz>+F*@O^f2ZhHQQUWc_dzdz-u|=pRo~M~1{fWb{<@?xQSL)4`-AcWiG7FS`+E6( zy-U4+`b?s-HeT7nSGGK;JeX*0kGJ;mt$j@WmJB#yu<0HvGbxEMe)+TeCghmmiOA>!FFJ69xFFz7<9VJHI zYTj8Lch>RFx`)mNjk-_sfjZ0zi{n>!7OzD;ZbTWxiqxg z9CPpf{23aZLBYjkAMRK7ZEd=7Ld8i>`(Z&sKncWKn;G)Ra61Kk<0zj38+0Em?l%gK z?`J+J+SSjpe^IfkKa;(0-_>8j{L*LYuVenIc~}2_=CAj&{W{}EEZgref0W7g7n(mR zVd0L&M|Es}i}9oV>@&1+NDqC<0j(Xx8vE}d@fF@Rk~Wi@;#1Qe3vQfXf5cuPXxanu zt!ZIYIS^J0RBI=Clcj5e`ASh5gCocW8Xsv8F$hdW+l7-HBTBLk?F?KH%g}TodnEoV z%s^=;dO{&tQ0j<62i3XeaRqT)`_E7s>4ykRFeqN~X3#T^djj)U#+>@Zb0ZuDN*S$#*9 zsk*EOzXl*J-tKwgq%d3|^!KeMMq_HK#i`*?Cd<=;TSd@5s8}Smlr$xxb|CtT`!Q4` zQz9O=%Gy9WX?+_?r%N=nL2}P=s1!K{(U&O`ba2h)y4(kTI!k%WeJjt#Y;8C(2Y+RC zDK4XNTN!UFTWWo1t6c{-wM?9!gE&10w>GTM;TxG9M&^!@?Qs5oI6Gj5gxN{smgK2X zxJ7en-odFU+-YjC+yAOPFRjwN6fKS{yO+<$Y-kHJw|>>UpyA2fy2bq&^qOKdLD@s3 z+P{RvPK?5#r9+ZZAjy)YBDBJw%@A)Zb_KA}E3KfeNog zc7xE}iMCp&?&@_ixUQnmO;hA(P?A`FG|ucK+X@Pow7?nED`TX!V)0NbKKYDoRu@G; zO~MRbw~^=or*3G)%8|>|=Zvlmex&c>W|&jkwAZVUCke?T(8OmGAvIcl(r(I#O6?*R zpj}Q>Z-r>$CmW8yc>wl-GhoS=l-)>B^zyp~m1R{|ferCnI8+{KK9JUNbea)cPZw*j zZ&LYZ*nAEmSWW0%UBx@9;*L7rQMdfvn4@JaGy7`&mHK#Q6`xu4z*eOwN7mc`_1~O+ zZF()I{DHGP36l5lo}SyGRnM`xBWu~^OWn)9)$HcE-b7XXTsLv2_kqEA(+WL+CyO&@ z&c10^(~|dyuEX4!&D_mqJ4;e8)F`Xg_*3j;4A`?&C2wtAi+2z16Ae4elSH2(3dG<2nGB<~q^E z_zkCqi?Kqa|M&!!-r2;gedi4rL}79msT6*=Pdi`pMv|sT4btc&r!8)SIxLgf6ebPW z)Q{MZ1ZblnK(y|isv;WNXdK^b8L)%;ofixAqL@tO(NNS6(NLw4uVu_uMXh38O9M9t zZw#)`H(prje(&hJN8f+>-QzK5C+xxDvgolSkd0U^W~XVwUc`W)8E19 z1V-R|Mv3RJ)0iTv`Vyva1HrwG5yB$w-(mDVM#$kJwNGN|6hz4$+NUuSqu;}VPKdrl z4{aBCXzwz!r|CyIESvi%!^_q_>ZYsNJsSfw!&ZM`(X(}5*dTfYQN@PM%;r4K(Xn-Z zZ?dpeqR%#VIt2ffAoJ(OE0C4!vmK;Ej24^;g5%v;J@LS1z%!dg?%0yB|NNNA&0;}MVb&m9R75aOJ5cJCdwH`qnvJ6)}ZZ5N`+qQ}+(1H6csRwZ0 zR!+0DK1pUQ5ERNg<(o?e)lrtr2tj2@PNW9Wq@^F42%iPsU8& zNp-gr(VFtT(9{h73GJ33u@hR8L_5|?3*xph)g7Mr$QWl$GbYYCW7M($$TS776VS%# zXlOeY($btDtAMQdmp2IONL~dT9q>g5l44y1S6a0-w^!L=Zh%E%Ni@)*p;iLzZc>u=O2 zoVknc#q&!)c+lK;KYxSKm1lr>nHyZphJ$TqcEXu|HRDP~%;`;J6>Zv~-6jRmBaEcY ze!Y6@DVhF?N*;!ne{I|X>Q3J5Cn>?S^TKa{T)6)PZeOqN`lCFpOEc#2<; zyyn!-Dz$0WBGIgP@d8}v4aIEDNV68CSqsvvWsCc3=sCrp06mFf6!{XxsB{UB>w(|f z08%exs`n@5fhkw;e9#-$hYit}AjKSuOhv>sYGn8;J@{S{04XMK!lm3N311nFN)Dia zGiOfF;9 z9fL@)CeZWK6Jk^_)a^ zI`|V%Cwsa@JQu)^L6OEGgL1ggIGj{+$&S;6ZMU%)BNm5`l>xSWl9ju3DqgvduiSUL z`~K-K7(KfS^@t15aKfDp-dS+IwBc~D#gEH$Y|Uenj%~-NMY?WhkgwZe$G$vn1|HmV z7@>iaYs3h*C&VpJ$d(Qo8-|-2)H&d;Kn1~-R3@Ff<;z)kEQt-yj=VT2{skK$H@!^$ z6IfX(1yWCT_#-wAaJ^Fe8#dyVAQR@1Ye7y-k$2$59JrfIZW?g7)H&p64wr{$+W0P} zP)Cb!>o%qchasa7b-iV36YohL;(i5rKx-<3NB}0DrZ)_5TJkYv|Cq9n*!eMK{gf*F znDTr~m3~YWe@x|nO1VF#9Gf}=t^0z4Xx&X^dhp8f#f-;>Lv;4S_a9U6c-*Nwqob=H XvMfL3B(~EgY8;jp=~#?JIlGh{ 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/quic/_asyncio.py b/venv/lib/python3.12/site-packages/dns/quic/_asyncio.py new file mode 100644 index 0000000..0a177b6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/quic/_asyncio.py @@ -0,0 +1,276 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import asyncio +import socket +import ssl +import struct +import time + +import aioquic.h3.connection # type: ignore +import aioquic.h3.events # type: ignore +import aioquic.quic.configuration # type: ignore +import aioquic.quic.connection # type: ignore +import aioquic.quic.events # type: ignore + +import dns.asyncbackend +import dns.exception +import dns.inet +from dns.quic._common import ( + QUIC_MAX_DATAGRAM, + AsyncQuicConnection, + AsyncQuicManager, + BaseQuicStream, + UnexpectedEOF, +) + + +class AsyncioQuicStream(BaseQuicStream): + def __init__(self, connection, stream_id): + super().__init__(connection, stream_id) + self._wake_up = asyncio.Condition() + + async def _wait_for_wake_up(self): + async with self._wake_up: + await self._wake_up.wait() + + async def wait_for(self, amount, expiration): + while True: + timeout = self._timeout_from_expiration(expiration) + if self._buffer.have(amount): + return + self._expecting = amount + try: + await asyncio.wait_for(self._wait_for_wake_up(), timeout) + except TimeoutError: + raise dns.exception.Timeout + self._expecting = 0 + + async def wait_for_end(self, expiration): + while True: + timeout = self._timeout_from_expiration(expiration) + if self._buffer.seen_end(): + return + try: + await asyncio.wait_for(self._wait_for_wake_up(), timeout) + except TimeoutError: + raise dns.exception.Timeout + + async def receive(self, timeout=None): + expiration = self._expiration_from_timeout(timeout) + if self._connection.is_h3(): + await self.wait_for_end(expiration) + return self._buffer.get_all() + else: + await self.wait_for(2, expiration) + (size,) = struct.unpack("!H", self._buffer.get(2)) + await self.wait_for(size, expiration) + return self._buffer.get(size) + + async def send(self, datagram, is_end=False): + data = self._encapsulate(datagram) + await self._connection.write(self._stream_id, data, is_end) + + async def _add_input(self, data, is_end): + if self._common_add_input(data, is_end): + async with self._wake_up: + self._wake_up.notify() + + async def close(self): + self._close() + + # Streams are async context managers + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.close() + async with self._wake_up: + self._wake_up.notify() + return False + + +class AsyncioQuicConnection(AsyncQuicConnection): + def __init__(self, connection, address, port, source, source_port, manager=None): + super().__init__(connection, address, port, source, source_port, manager) + self._socket = None + self._handshake_complete = asyncio.Event() + self._socket_created = asyncio.Event() + self._wake_timer = asyncio.Condition() + self._receiver_task = None + self._sender_task = None + self._wake_pending = False + + async def _receiver(self): + try: + af = dns.inet.af_for_address(self._address) + backend = dns.asyncbackend.get_backend("asyncio") + # Note that peer is a low-level address tuple, but make_socket() wants + # a high-level address tuple, so we convert. + self._socket = await backend.make_socket( + af, socket.SOCK_DGRAM, 0, self._source, (self._peer[0], self._peer[1]) + ) + self._socket_created.set() + async with self._socket: + while not self._done: + (datagram, address) = await self._socket.recvfrom( + QUIC_MAX_DATAGRAM, None + ) + if address[0] != self._peer[0] or address[1] != self._peer[1]: + continue + self._connection.receive_datagram(datagram, address, time.time()) + # Wake up the timer in case the sender is sleeping, as there may be + # stuff to send now. + await self._wakeup() + except Exception: + pass + finally: + self._done = True + await self._wakeup() + self._handshake_complete.set() + + async def _wakeup(self): + self._wake_pending = True + async with self._wake_timer: + self._wake_timer.notify_all() + + async def _wait_for_wake_timer(self): + async with self._wake_timer: + if not self._wake_pending: + await self._wake_timer.wait() + self._wake_pending = False + + async def _sender(self): + await self._socket_created.wait() + while not self._done: + datagrams = self._connection.datagrams_to_send(time.time()) + for datagram, address in datagrams: + assert address == self._peer + assert self._socket is not None + await self._socket.sendto(datagram, self._peer, None) + (expiration, interval) = self._get_timer_values() + try: + await asyncio.wait_for(self._wait_for_wake_timer(), interval) + except Exception: + pass + self._handle_timer(expiration) + await self._handle_events() + + async def _handle_events(self): + count = 0 + while True: + event = self._connection.next_event() + if event is None: + return + if isinstance(event, aioquic.quic.events.StreamDataReceived): + if self.is_h3(): + assert self._h3_conn is not None + h3_events = self._h3_conn.handle_event(event) + for h3_event in h3_events: + if isinstance(h3_event, aioquic.h3.events.HeadersReceived): + stream = self._streams.get(event.stream_id) + if stream: + if stream._headers is None: + stream._headers = h3_event.headers + elif stream._trailers is None: + stream._trailers = h3_event.headers + if h3_event.stream_ended: + await stream._add_input(b"", True) + elif isinstance(h3_event, aioquic.h3.events.DataReceived): + stream = self._streams.get(event.stream_id) + if stream: + await stream._add_input( + h3_event.data, h3_event.stream_ended + ) + else: + stream = self._streams.get(event.stream_id) + if stream: + await stream._add_input(event.data, event.end_stream) + elif isinstance(event, aioquic.quic.events.HandshakeCompleted): + self._handshake_complete.set() + elif isinstance(event, aioquic.quic.events.ConnectionTerminated): + self._done = True + if self._receiver_task is not None: + self._receiver_task.cancel() + elif isinstance(event, aioquic.quic.events.StreamReset): + stream = self._streams.get(event.stream_id) + if stream: + await stream._add_input(b"", True) + + count += 1 + if count > 10: + # yield + count = 0 + await asyncio.sleep(0) + + async def write(self, stream, data, is_end=False): + self._connection.send_stream_data(stream, data, is_end) + await self._wakeup() + + def run(self): + if self._closed: + return + self._receiver_task = asyncio.Task(self._receiver()) + self._sender_task = asyncio.Task(self._sender()) + + async def make_stream(self, timeout=None): + try: + await asyncio.wait_for(self._handshake_complete.wait(), timeout) + except TimeoutError: + raise dns.exception.Timeout + if self._done: + raise UnexpectedEOF + stream_id = self._connection.get_next_available_stream_id(False) + stream = AsyncioQuicStream(self, stream_id) + self._streams[stream_id] = stream + return stream + + async def close(self): + if not self._closed: + if self._manager is not None: + self._manager.closed(self._peer[0], self._peer[1]) + self._closed = True + self._connection.close() + # sender might be blocked on this, so set it + self._socket_created.set() + await self._wakeup() + try: + if self._receiver_task is not None: + await self._receiver_task + except asyncio.CancelledError: + pass + try: + if self._sender_task is not None: + await self._sender_task + except asyncio.CancelledError: + pass + if self._socket is not None: + await self._socket.close() + + +class AsyncioQuicManager(AsyncQuicManager): + def __init__( + self, conf=None, verify_mode=ssl.CERT_REQUIRED, server_name=None, h3=False + ): + super().__init__(conf, verify_mode, AsyncioQuicConnection, server_name, h3) + + def connect( + self, address, port=853, source=None, source_port=0, want_session_ticket=True + ): + (connection, start) = self._connect( + address, port, source, source_port, want_session_ticket + ) + if start: + connection.run() + return connection + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + # Copy the iterator into a list as exiting things will mutate the connections + # table. + connections = list(self._connections.values()) + for connection in connections: + await connection.close() + return False diff --git a/venv/lib/python3.12/site-packages/dns/quic/_common.py b/venv/lib/python3.12/site-packages/dns/quic/_common.py new file mode 100644 index 0000000..ba9d245 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/quic/_common.py @@ -0,0 +1,344 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import base64 +import copy +import functools +import socket +import struct +import time +import urllib.parse +from typing import Any + +import aioquic.h3.connection # type: ignore +import aioquic.quic.configuration # type: ignore +import aioquic.quic.connection # type: ignore + +import dns._tls_util +import dns.inet + +QUIC_MAX_DATAGRAM = 2048 +MAX_SESSION_TICKETS = 8 +# If we hit the max sessions limit we will delete this many of the oldest connections. +# The value must be a integer > 0 and <= MAX_SESSION_TICKETS. +SESSIONS_TO_DELETE = MAX_SESSION_TICKETS // 4 + + +class UnexpectedEOF(Exception): + pass + + +class Buffer: + def __init__(self): + self._buffer = b"" + self._seen_end = False + + def put(self, data, is_end): + if self._seen_end: + return + self._buffer += data + if is_end: + self._seen_end = True + + def have(self, amount): + if len(self._buffer) >= amount: + return True + if self._seen_end: + raise UnexpectedEOF + return False + + def seen_end(self): + return self._seen_end + + def get(self, amount): + assert self.have(amount) + data = self._buffer[:amount] + self._buffer = self._buffer[amount:] + return data + + def get_all(self): + assert self.seen_end() + data = self._buffer + self._buffer = b"" + return data + + +class BaseQuicStream: + def __init__(self, connection, stream_id): + self._connection = connection + self._stream_id = stream_id + self._buffer = Buffer() + self._expecting = 0 + self._headers = None + self._trailers = None + + def id(self): + return self._stream_id + + def headers(self): + return self._headers + + def trailers(self): + return self._trailers + + def _expiration_from_timeout(self, timeout): + if timeout is not None: + expiration = time.time() + timeout + else: + expiration = None + return expiration + + def _timeout_from_expiration(self, expiration): + if expiration is not None: + timeout = max(expiration - time.time(), 0.0) + else: + timeout = None + return timeout + + # Subclass must implement receive() as sync / async and which returns a message + # or raises. + + # Subclass must implement send() as sync / async and which takes a message and + # an EOF indicator. + + def send_h3(self, url, datagram, post=True): + if not self._connection.is_h3(): + raise SyntaxError("cannot send H3 to a non-H3 connection") + url_parts = urllib.parse.urlparse(url) + path = url_parts.path.encode() + if post: + method = b"POST" + else: + method = b"GET" + path += b"?dns=" + base64.urlsafe_b64encode(datagram).rstrip(b"=") + headers = [ + (b":method", method), + (b":scheme", url_parts.scheme.encode()), + (b":authority", url_parts.netloc.encode()), + (b":path", path), + (b"accept", b"application/dns-message"), + ] + if post: + headers.extend( + [ + (b"content-type", b"application/dns-message"), + (b"content-length", str(len(datagram)).encode()), + ] + ) + self._connection.send_headers(self._stream_id, headers, not post) + if post: + self._connection.send_data(self._stream_id, datagram, True) + + def _encapsulate(self, datagram): + if self._connection.is_h3(): + return datagram + l = len(datagram) + return struct.pack("!H", l) + datagram + + def _common_add_input(self, data, is_end): + self._buffer.put(data, is_end) + try: + return ( + self._expecting > 0 and self._buffer.have(self._expecting) + ) or self._buffer.seen_end + except UnexpectedEOF: + return True + + def _close(self): + self._connection.close_stream(self._stream_id) + self._buffer.put(b"", True) # send EOF in case we haven't seen it. + + +class BaseQuicConnection: + def __init__( + self, + connection, + address, + port, + source=None, + source_port=0, + manager=None, + ): + self._done = False + self._connection = connection + self._address = address + self._port = port + self._closed = False + self._manager = manager + self._streams = {} + if manager is not None and manager.is_h3(): + self._h3_conn = aioquic.h3.connection.H3Connection(connection, False) + else: + self._h3_conn = None + self._af = dns.inet.af_for_address(address) + self._peer = dns.inet.low_level_address_tuple((address, port)) + if source is None and source_port != 0: + if self._af == socket.AF_INET: + source = "0.0.0.0" + elif self._af == socket.AF_INET6: + source = "::" + else: + raise NotImplementedError + if source: + self._source = (source, source_port) + else: + self._source = None + + def is_h3(self): + return self._h3_conn is not None + + def close_stream(self, stream_id): + del self._streams[stream_id] + + def send_headers(self, stream_id, headers, is_end=False): + assert self._h3_conn is not None + self._h3_conn.send_headers(stream_id, headers, is_end) + + def send_data(self, stream_id, data, is_end=False): + assert self._h3_conn is not None + self._h3_conn.send_data(stream_id, data, is_end) + + def _get_timer_values(self, closed_is_special=True): + now = time.time() + expiration = self._connection.get_timer() + if expiration is None: + expiration = now + 3600 # arbitrary "big" value + interval = max(expiration - now, 0) + if self._closed and closed_is_special: + # lower sleep interval to avoid a race in the closing process + # which can lead to higher latency closing due to sleeping when + # we have events. + interval = min(interval, 0.05) + return (expiration, interval) + + def _handle_timer(self, expiration): + now = time.time() + if expiration <= now: + self._connection.handle_timer(now) + + +class AsyncQuicConnection(BaseQuicConnection): + async def make_stream(self, timeout: float | None = None) -> Any: + pass + + +class BaseQuicManager: + def __init__( + self, conf, verify_mode, connection_factory, server_name=None, h3=False + ): + self._connections = {} + self._connection_factory = connection_factory + self._session_tickets = {} + self._tokens = {} + self._h3 = h3 + if conf is None: + verify_path = None + if isinstance(verify_mode, str): + verify_path = verify_mode + verify_mode = True + if h3: + alpn_protocols = ["h3"] + else: + alpn_protocols = ["doq", "doq-i03"] + conf = aioquic.quic.configuration.QuicConfiguration( + alpn_protocols=alpn_protocols, + verify_mode=verify_mode, + server_name=server_name, + ) + if verify_path is not None: + cafile, capath = dns._tls_util.convert_verify_to_cafile_and_capath( + verify_path + ) + conf.load_verify_locations(cafile=cafile, capath=capath) + self._conf = conf + + def _connect( + self, + address, + port=853, + source=None, + source_port=0, + want_session_ticket=True, + want_token=True, + ): + connection = self._connections.get((address, port)) + if connection is not None: + return (connection, False) + conf = self._conf + if want_session_ticket: + try: + session_ticket = self._session_tickets.pop((address, port)) + # We found a session ticket, so make a configuration that uses it. + conf = copy.copy(conf) + conf.session_ticket = session_ticket + except KeyError: + # No session ticket. + pass + # Whether or not we found a session ticket, we want a handler to save + # one. + session_ticket_handler = functools.partial( + self.save_session_ticket, address, port + ) + else: + session_ticket_handler = None + if want_token: + try: + token = self._tokens.pop((address, port)) + # We found a token, so make a configuration that uses it. + conf = copy.copy(conf) + conf.token = token + except KeyError: + # No token + pass + # Whether or not we found a token, we want a handler to save # one. + token_handler = functools.partial(self.save_token, address, port) + else: + token_handler = None + + qconn = aioquic.quic.connection.QuicConnection( + configuration=conf, + session_ticket_handler=session_ticket_handler, + token_handler=token_handler, + ) + lladdress = dns.inet.low_level_address_tuple((address, port)) + qconn.connect(lladdress, time.time()) + connection = self._connection_factory( + qconn, address, port, source, source_port, self + ) + self._connections[(address, port)] = connection + return (connection, True) + + def closed(self, address, port): + try: + del self._connections[(address, port)] + except KeyError: + pass + + def is_h3(self): + return self._h3 + + def save_session_ticket(self, address, port, ticket): + # We rely on dictionaries keys() being in insertion order here. We + # can't just popitem() as that would be LIFO which is the opposite of + # what we want. + l = len(self._session_tickets) + if l >= MAX_SESSION_TICKETS: + keys_to_delete = list(self._session_tickets.keys())[0:SESSIONS_TO_DELETE] + for key in keys_to_delete: + del self._session_tickets[key] + self._session_tickets[(address, port)] = ticket + + def save_token(self, address, port, token): + # We rely on dictionaries keys() being in insertion order here. We + # can't just popitem() as that would be LIFO which is the opposite of + # what we want. + l = len(self._tokens) + if l >= MAX_SESSION_TICKETS: + keys_to_delete = list(self._tokens.keys())[0:SESSIONS_TO_DELETE] + for key in keys_to_delete: + del self._tokens[key] + self._tokens[(address, port)] = token + + +class AsyncQuicManager(BaseQuicManager): + def connect(self, address, port=853, source=None, source_port=0): + raise NotImplementedError diff --git a/venv/lib/python3.12/site-packages/dns/quic/_sync.py b/venv/lib/python3.12/site-packages/dns/quic/_sync.py new file mode 100644 index 0000000..18f9d05 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/quic/_sync.py @@ -0,0 +1,306 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import selectors +import socket +import ssl +import struct +import threading +import time + +import aioquic.h3.connection # type: ignore +import aioquic.h3.events # type: ignore +import aioquic.quic.configuration # type: ignore +import aioquic.quic.connection # type: ignore +import aioquic.quic.events # type: ignore + +import dns.exception +import dns.inet +from dns.quic._common import ( + QUIC_MAX_DATAGRAM, + BaseQuicConnection, + BaseQuicManager, + BaseQuicStream, + UnexpectedEOF, +) + +# Function used to create a socket. Can be overridden if needed in special +# situations. +socket_factory = socket.socket + + +class SyncQuicStream(BaseQuicStream): + def __init__(self, connection, stream_id): + super().__init__(connection, stream_id) + self._wake_up = threading.Condition() + self._lock = threading.Lock() + + def wait_for(self, amount, expiration): + while True: + timeout = self._timeout_from_expiration(expiration) + with self._lock: + if self._buffer.have(amount): + return + self._expecting = amount + with self._wake_up: + if not self._wake_up.wait(timeout): + raise dns.exception.Timeout + self._expecting = 0 + + def wait_for_end(self, expiration): + while True: + timeout = self._timeout_from_expiration(expiration) + with self._lock: + if self._buffer.seen_end(): + return + with self._wake_up: + if not self._wake_up.wait(timeout): + raise dns.exception.Timeout + + def receive(self, timeout=None): + expiration = self._expiration_from_timeout(timeout) + if self._connection.is_h3(): + self.wait_for_end(expiration) + with self._lock: + return self._buffer.get_all() + else: + self.wait_for(2, expiration) + with self._lock: + (size,) = struct.unpack("!H", self._buffer.get(2)) + self.wait_for(size, expiration) + with self._lock: + return self._buffer.get(size) + + def send(self, datagram, is_end=False): + data = self._encapsulate(datagram) + self._connection.write(self._stream_id, data, is_end) + + def _add_input(self, data, is_end): + if self._common_add_input(data, is_end): + with self._wake_up: + self._wake_up.notify() + + def close(self): + with self._lock: + self._close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + with self._wake_up: + self._wake_up.notify() + return False + + +class SyncQuicConnection(BaseQuicConnection): + def __init__(self, connection, address, port, source, source_port, manager): + super().__init__(connection, address, port, source, source_port, manager) + self._socket = socket_factory(self._af, socket.SOCK_DGRAM, 0) + if self._source is not None: + try: + self._socket.bind( + dns.inet.low_level_address_tuple(self._source, self._af) + ) + except Exception: + self._socket.close() + raise + self._socket.connect(self._peer) + (self._send_wakeup, self._receive_wakeup) = socket.socketpair() + self._receive_wakeup.setblocking(False) + self._socket.setblocking(False) + self._handshake_complete = threading.Event() + self._worker_thread = None + self._lock = threading.Lock() + + def _read(self): + count = 0 + while count < 10: + count += 1 + try: + datagram = self._socket.recv(QUIC_MAX_DATAGRAM) + except BlockingIOError: + return + with self._lock: + self._connection.receive_datagram(datagram, self._peer, time.time()) + + def _drain_wakeup(self): + while True: + try: + self._receive_wakeup.recv(32) + except BlockingIOError: + return + + def _worker(self): + try: + with selectors.DefaultSelector() as sel: + sel.register(self._socket, selectors.EVENT_READ, self._read) + sel.register( + self._receive_wakeup, selectors.EVENT_READ, self._drain_wakeup + ) + while not self._done: + (expiration, interval) = self._get_timer_values(False) + items = sel.select(interval) + for key, _ in items: + key.data() + with self._lock: + self._handle_timer(expiration) + self._handle_events() + with self._lock: + datagrams = self._connection.datagrams_to_send(time.time()) + for datagram, _ in datagrams: + try: + self._socket.send(datagram) + except BlockingIOError: + # we let QUIC handle any lossage + pass + except Exception: + # Eat all exceptions as we have no way to pass them back to the + # caller currently. It might be nice to fix this in the future. + pass + finally: + with self._lock: + self._done = True + self._socket.close() + # Ensure anyone waiting for this gets woken up. + self._handshake_complete.set() + + def _handle_events(self): + while True: + with self._lock: + event = self._connection.next_event() + if event is None: + return + if isinstance(event, aioquic.quic.events.StreamDataReceived): + if self.is_h3(): + assert self._h3_conn is not None + h3_events = self._h3_conn.handle_event(event) + for h3_event in h3_events: + if isinstance(h3_event, aioquic.h3.events.HeadersReceived): + with self._lock: + stream = self._streams.get(event.stream_id) + if stream: + if stream._headers is None: + stream._headers = h3_event.headers + elif stream._trailers is None: + stream._trailers = h3_event.headers + if h3_event.stream_ended: + stream._add_input(b"", True) + elif isinstance(h3_event, aioquic.h3.events.DataReceived): + with self._lock: + stream = self._streams.get(event.stream_id) + if stream: + stream._add_input(h3_event.data, h3_event.stream_ended) + else: + with self._lock: + stream = self._streams.get(event.stream_id) + if stream: + stream._add_input(event.data, event.end_stream) + elif isinstance(event, aioquic.quic.events.HandshakeCompleted): + self._handshake_complete.set() + elif isinstance(event, aioquic.quic.events.ConnectionTerminated): + with self._lock: + self._done = True + elif isinstance(event, aioquic.quic.events.StreamReset): + with self._lock: + stream = self._streams.get(event.stream_id) + if stream: + stream._add_input(b"", True) + + def write(self, stream, data, is_end=False): + with self._lock: + self._connection.send_stream_data(stream, data, is_end) + self._send_wakeup.send(b"\x01") + + def send_headers(self, stream_id, headers, is_end=False): + with self._lock: + super().send_headers(stream_id, headers, is_end) + if is_end: + self._send_wakeup.send(b"\x01") + + def send_data(self, stream_id, data, is_end=False): + with self._lock: + super().send_data(stream_id, data, is_end) + if is_end: + self._send_wakeup.send(b"\x01") + + def run(self): + if self._closed: + return + self._worker_thread = threading.Thread(target=self._worker) + self._worker_thread.start() + + def make_stream(self, timeout=None): + if not self._handshake_complete.wait(timeout): + raise dns.exception.Timeout + with self._lock: + if self._done: + raise UnexpectedEOF + stream_id = self._connection.get_next_available_stream_id(False) + stream = SyncQuicStream(self, stream_id) + self._streams[stream_id] = stream + return stream + + def close_stream(self, stream_id): + with self._lock: + super().close_stream(stream_id) + + def close(self): + with self._lock: + if self._closed: + return + if self._manager is not None: + self._manager.closed(self._peer[0], self._peer[1]) + self._closed = True + self._connection.close() + self._send_wakeup.send(b"\x01") + if self._worker_thread is not None: + self._worker_thread.join() + + +class SyncQuicManager(BaseQuicManager): + def __init__( + self, conf=None, verify_mode=ssl.CERT_REQUIRED, server_name=None, h3=False + ): + super().__init__(conf, verify_mode, SyncQuicConnection, server_name, h3) + self._lock = threading.Lock() + + def connect( + self, + address, + port=853, + source=None, + source_port=0, + want_session_ticket=True, + want_token=True, + ): + with self._lock: + (connection, start) = self._connect( + address, port, source, source_port, want_session_ticket, want_token + ) + if start: + connection.run() + return connection + + def closed(self, address, port): + with self._lock: + super().closed(address, port) + + def save_session_ticket(self, address, port, ticket): + with self._lock: + super().save_session_ticket(address, port, ticket) + + def save_token(self, address, port, token): + with self._lock: + super().save_token(address, port, token) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + # Copy the iterator into a list as exiting things will mutate the connections + # table. + connections = list(self._connections.values()) + for connection in connections: + connection.close() + return False diff --git a/venv/lib/python3.12/site-packages/dns/quic/_trio.py b/venv/lib/python3.12/site-packages/dns/quic/_trio.py new file mode 100644 index 0000000..046e6aa --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/quic/_trio.py @@ -0,0 +1,250 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import socket +import ssl +import struct +import time + +import aioquic.h3.connection # type: ignore +import aioquic.h3.events # type: ignore +import aioquic.quic.configuration # type: ignore +import aioquic.quic.connection # type: ignore +import aioquic.quic.events # type: ignore +import trio + +import dns.exception +import dns.inet +from dns._asyncbackend import NullContext +from dns.quic._common import ( + QUIC_MAX_DATAGRAM, + AsyncQuicConnection, + AsyncQuicManager, + BaseQuicStream, + UnexpectedEOF, +) + + +class TrioQuicStream(BaseQuicStream): + def __init__(self, connection, stream_id): + super().__init__(connection, stream_id) + self._wake_up = trio.Condition() + + async def wait_for(self, amount): + while True: + if self._buffer.have(amount): + return + self._expecting = amount + async with self._wake_up: + await self._wake_up.wait() + self._expecting = 0 + + async def wait_for_end(self): + while True: + if self._buffer.seen_end(): + return + async with self._wake_up: + await self._wake_up.wait() + + async def receive(self, timeout=None): + if timeout is None: + context = NullContext(None) + else: + context = trio.move_on_after(timeout) + with context: + if self._connection.is_h3(): + await self.wait_for_end() + return self._buffer.get_all() + else: + await self.wait_for(2) + (size,) = struct.unpack("!H", self._buffer.get(2)) + await self.wait_for(size) + return self._buffer.get(size) + raise dns.exception.Timeout + + async def send(self, datagram, is_end=False): + data = self._encapsulate(datagram) + await self._connection.write(self._stream_id, data, is_end) + + async def _add_input(self, data, is_end): + if self._common_add_input(data, is_end): + async with self._wake_up: + self._wake_up.notify() + + async def close(self): + self._close() + + # Streams are async context managers + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.close() + async with self._wake_up: + self._wake_up.notify() + return False + + +class TrioQuicConnection(AsyncQuicConnection): + def __init__(self, connection, address, port, source, source_port, manager=None): + super().__init__(connection, address, port, source, source_port, manager) + self._socket = trio.socket.socket(self._af, socket.SOCK_DGRAM, 0) + self._handshake_complete = trio.Event() + self._run_done = trio.Event() + self._worker_scope = None + self._send_pending = False + + async def _worker(self): + try: + if self._source: + await self._socket.bind( + dns.inet.low_level_address_tuple(self._source, self._af) + ) + await self._socket.connect(self._peer) + while not self._done: + (expiration, interval) = self._get_timer_values(False) + if self._send_pending: + # Do not block forever if sends are pending. Even though we + # have a wake-up mechanism if we've already started the blocking + # read, the possibility of context switching in send means that + # more writes can happen while we have no wake up context, so + # we need self._send_pending to avoid (effectively) a "lost wakeup" + # race. + interval = 0.0 + with trio.CancelScope( + deadline=trio.current_time() + interval # pyright: ignore + ) as self._worker_scope: + datagram = await self._socket.recv(QUIC_MAX_DATAGRAM) + self._connection.receive_datagram(datagram, self._peer, time.time()) + self._worker_scope = None + self._handle_timer(expiration) + await self._handle_events() + # We clear this now, before sending anything, as sending can cause + # context switches that do more sends. We want to know if that + # happens so we don't block a long time on the recv() above. + self._send_pending = False + datagrams = self._connection.datagrams_to_send(time.time()) + for datagram, _ in datagrams: + await self._socket.send(datagram) + finally: + self._done = True + self._socket.close() + self._handshake_complete.set() + + async def _handle_events(self): + count = 0 + while True: + event = self._connection.next_event() + if event is None: + return + if isinstance(event, aioquic.quic.events.StreamDataReceived): + if self.is_h3(): + assert self._h3_conn is not None + h3_events = self._h3_conn.handle_event(event) + for h3_event in h3_events: + if isinstance(h3_event, aioquic.h3.events.HeadersReceived): + stream = self._streams.get(event.stream_id) + if stream: + if stream._headers is None: + stream._headers = h3_event.headers + elif stream._trailers is None: + stream._trailers = h3_event.headers + if h3_event.stream_ended: + await stream._add_input(b"", True) + elif isinstance(h3_event, aioquic.h3.events.DataReceived): + stream = self._streams.get(event.stream_id) + if stream: + await stream._add_input( + h3_event.data, h3_event.stream_ended + ) + else: + stream = self._streams.get(event.stream_id) + if stream: + await stream._add_input(event.data, event.end_stream) + elif isinstance(event, aioquic.quic.events.HandshakeCompleted): + self._handshake_complete.set() + elif isinstance(event, aioquic.quic.events.ConnectionTerminated): + self._done = True + self._socket.close() + elif isinstance(event, aioquic.quic.events.StreamReset): + stream = self._streams.get(event.stream_id) + if stream: + await stream._add_input(b"", True) + count += 1 + if count > 10: + # yield + count = 0 + await trio.sleep(0) + + async def write(self, stream, data, is_end=False): + self._connection.send_stream_data(stream, data, is_end) + self._send_pending = True + if self._worker_scope is not None: + self._worker_scope.cancel() + + async def run(self): + if self._closed: + return + async with trio.open_nursery() as nursery: + nursery.start_soon(self._worker) + self._run_done.set() + + async def make_stream(self, timeout=None): + if timeout is None: + context = NullContext(None) + else: + context = trio.move_on_after(timeout) + with context: + await self._handshake_complete.wait() + if self._done: + raise UnexpectedEOF + stream_id = self._connection.get_next_available_stream_id(False) + stream = TrioQuicStream(self, stream_id) + self._streams[stream_id] = stream + return stream + raise dns.exception.Timeout + + async def close(self): + if not self._closed: + if self._manager is not None: + self._manager.closed(self._peer[0], self._peer[1]) + self._closed = True + self._connection.close() + self._send_pending = True + if self._worker_scope is not None: + self._worker_scope.cancel() + await self._run_done.wait() + + +class TrioQuicManager(AsyncQuicManager): + def __init__( + self, + nursery, + conf=None, + verify_mode=ssl.CERT_REQUIRED, + server_name=None, + h3=False, + ): + super().__init__(conf, verify_mode, TrioQuicConnection, server_name, h3) + self._nursery = nursery + + def connect( + self, address, port=853, source=None, source_port=0, want_session_ticket=True + ): + (connection, start) = self._connect( + address, port, source, source_port, want_session_ticket + ) + if start: + self._nursery.start_soon(connection.run) + return connection + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + # Copy the iterator into a list as exiting things will mutate the connections + # table. + connections = list(self._connections.values()) + for connection in connections: + await connection.close() + return False diff --git a/venv/lib/python3.12/site-packages/dns/rcode.py b/venv/lib/python3.12/site-packages/dns/rcode.py new file mode 100644 index 0000000..7bb8467 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rcode.py @@ -0,0 +1,168 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS Result Codes.""" + +from typing import Tuple, Type + +import dns.enum +import dns.exception + + +class Rcode(dns.enum.IntEnum): + #: No error + NOERROR = 0 + #: Format error + FORMERR = 1 + #: Server failure + SERVFAIL = 2 + #: Name does not exist ("Name Error" in RFC 1025 terminology). + NXDOMAIN = 3 + #: Not implemented + NOTIMP = 4 + #: Refused + REFUSED = 5 + #: Name exists. + YXDOMAIN = 6 + #: RRset exists. + YXRRSET = 7 + #: RRset does not exist. + NXRRSET = 8 + #: Not authoritative. + NOTAUTH = 9 + #: Name not in zone. + NOTZONE = 10 + #: DSO-TYPE Not Implemented + DSOTYPENI = 11 + #: Bad EDNS version. + BADVERS = 16 + #: TSIG Signature Failure + BADSIG = 16 + #: Key not recognized. + BADKEY = 17 + #: Signature out of time window. + BADTIME = 18 + #: Bad TKEY Mode. + BADMODE = 19 + #: Duplicate key name. + BADNAME = 20 + #: Algorithm not supported. + BADALG = 21 + #: Bad Truncation + BADTRUNC = 22 + #: Bad/missing Server Cookie + BADCOOKIE = 23 + + @classmethod + def _maximum(cls): + return 4095 + + @classmethod + def _unknown_exception_class(cls) -> Type[Exception]: + return UnknownRcode + + +class UnknownRcode(dns.exception.DNSException): + """A DNS rcode is unknown.""" + + +def from_text(text: str) -> Rcode: + """Convert text into an rcode. + + *text*, a ``str``, the textual rcode or an integer in textual form. + + Raises ``dns.rcode.UnknownRcode`` if the rcode mnemonic is unknown. + + Returns a ``dns.rcode.Rcode``. + """ + + return Rcode.from_text(text) + + +def from_flags(flags: int, ednsflags: int) -> Rcode: + """Return the rcode value encoded by flags and ednsflags. + + *flags*, an ``int``, the DNS flags field. + + *ednsflags*, an ``int``, the EDNS flags field. + + Raises ``ValueError`` if rcode is < 0 or > 4095 + + Returns a ``dns.rcode.Rcode``. + """ + + value = (flags & 0x000F) | ((ednsflags >> 20) & 0xFF0) + return Rcode.make(value) + + +def to_flags(value: Rcode) -> Tuple[int, int]: + """Return a (flags, ednsflags) tuple which encodes the rcode. + + *value*, a ``dns.rcode.Rcode``, the rcode. + + Raises ``ValueError`` if rcode is < 0 or > 4095. + + Returns an ``(int, int)`` tuple. + """ + + if value < 0 or value > 4095: + raise ValueError("rcode must be >= 0 and <= 4095") + v = value & 0xF + ev = (value & 0xFF0) << 20 + return (v, ev) + + +def to_text(value: Rcode, tsig: bool = False) -> str: + """Convert rcode into text. + + *value*, a ``dns.rcode.Rcode``, the rcode. + + Raises ``ValueError`` if rcode is < 0 or > 4095. + + Returns a ``str``. + """ + + if tsig and value == Rcode.BADVERS: + return "BADSIG" + return Rcode.to_text(value) + + +### BEGIN generated Rcode constants + +NOERROR = Rcode.NOERROR +FORMERR = Rcode.FORMERR +SERVFAIL = Rcode.SERVFAIL +NXDOMAIN = Rcode.NXDOMAIN +NOTIMP = Rcode.NOTIMP +REFUSED = Rcode.REFUSED +YXDOMAIN = Rcode.YXDOMAIN +YXRRSET = Rcode.YXRRSET +NXRRSET = Rcode.NXRRSET +NOTAUTH = Rcode.NOTAUTH +NOTZONE = Rcode.NOTZONE +DSOTYPENI = Rcode.DSOTYPENI +BADVERS = Rcode.BADVERS +BADSIG = Rcode.BADSIG +BADKEY = Rcode.BADKEY +BADTIME = Rcode.BADTIME +BADMODE = Rcode.BADMODE +BADNAME = Rcode.BADNAME +BADALG = Rcode.BADALG +BADTRUNC = Rcode.BADTRUNC +BADCOOKIE = Rcode.BADCOOKIE + +### END generated Rcode constants diff --git a/venv/lib/python3.12/site-packages/dns/rdata.py b/venv/lib/python3.12/site-packages/dns/rdata.py new file mode 100644 index 0000000..c4522e6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdata.py @@ -0,0 +1,935 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS rdata.""" + +import base64 +import binascii +import inspect +import io +import ipaddress +import itertools +import random +from importlib import import_module +from typing import Any, Dict, Tuple + +import dns.exception +import dns.immutable +import dns.ipv4 +import dns.ipv6 +import dns.name +import dns.rdataclass +import dns.rdatatype +import dns.tokenizer +import dns.ttl +import dns.wire + +_chunksize = 32 + +# We currently allow comparisons for rdata with relative names for backwards +# compatibility, but in the future we will not, as these kinds of comparisons +# can lead to subtle bugs if code is not carefully written. +# +# This switch allows the future behavior to be turned on so code can be +# tested with it. +_allow_relative_comparisons = True + + +class NoRelativeRdataOrdering(dns.exception.DNSException): + """An attempt was made to do an ordered comparison of one or more + rdata with relative names. The only reliable way of sorting rdata + is to use non-relativized rdata. + + """ + + +def _wordbreak(data, chunksize=_chunksize, separator=b" "): + """Break a binary string into chunks of chunksize characters separated by + a space. + """ + + if not chunksize: + return data.decode() + return separator.join( + [data[i : i + chunksize] for i in range(0, len(data), chunksize)] + ).decode() + + +# pylint: disable=unused-argument + + +def _hexify(data, chunksize=_chunksize, separator=b" ", **kw): + """Convert a binary string into its hex encoding, broken up into chunks + of chunksize characters separated by a separator. + """ + + return _wordbreak(binascii.hexlify(data), chunksize, separator) + + +def _base64ify(data, chunksize=_chunksize, separator=b" ", **kw): + """Convert a binary string into its base64 encoding, broken up into chunks + of chunksize characters separated by a separator. + """ + + return _wordbreak(base64.b64encode(data), chunksize, separator) + + +# pylint: enable=unused-argument + +__escaped = b'"\\' + + +def _escapify(qstring): + """Escape the characters in a quoted string which need it.""" + + if isinstance(qstring, str): + qstring = qstring.encode() + if not isinstance(qstring, bytearray): + qstring = bytearray(qstring) + + text = "" + for c in qstring: + if c in __escaped: + text += "\\" + chr(c) + elif c >= 0x20 and c < 0x7F: + text += chr(c) + else: + text += f"\\{c:03d}" + return text + + +def _truncate_bitmap(what): + """Determine the index of greatest byte that isn't all zeros, and + return the bitmap that contains all the bytes less than that index. + """ + + for i in range(len(what) - 1, -1, -1): + if what[i] != 0: + return what[0 : i + 1] + return what[0:1] + + +# So we don't have to edit all the rdata classes... +_constify = dns.immutable.constify + + +@dns.immutable.immutable +class Rdata: + """Base class for all DNS rdata types.""" + + __slots__ = ["rdclass", "rdtype", "rdcomment"] + + def __init__( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + ) -> None: + """Initialize an rdata. + + *rdclass*, an ``int`` is the rdataclass of the Rdata. + + *rdtype*, an ``int`` is the rdatatype of the Rdata. + """ + + self.rdclass = self._as_rdataclass(rdclass) + self.rdtype = self._as_rdatatype(rdtype) + self.rdcomment = None + + def _get_all_slots(self): + return itertools.chain.from_iterable( + getattr(cls, "__slots__", []) for cls in self.__class__.__mro__ + ) + + def __getstate__(self): + # We used to try to do a tuple of all slots here, but it + # doesn't work as self._all_slots isn't available at + # __setstate__() time. Before that we tried to store a tuple + # of __slots__, but that didn't work as it didn't store the + # slots defined by ancestors. This older way didn't fail + # outright, but ended up with partially broken objects, e.g. + # if you unpickled an A RR it wouldn't have rdclass and rdtype + # attributes, and would compare badly. + state = {} + for slot in self._get_all_slots(): + state[slot] = getattr(self, slot) + return state + + def __setstate__(self, state): + for slot, val in state.items(): + object.__setattr__(self, slot, val) + if not hasattr(self, "rdcomment"): + # Pickled rdata from 2.0.x might not have a rdcomment, so add + # it if needed. + object.__setattr__(self, "rdcomment", None) + + def covers(self) -> dns.rdatatype.RdataType: + """Return the type a Rdata covers. + + DNS SIG/RRSIG rdatas apply to a specific type; this type is + returned by the covers() function. If the rdata type is not + SIG or RRSIG, dns.rdatatype.NONE is returned. This is useful when + creating rdatasets, allowing the rdataset to contain only RRSIGs + of a particular type, e.g. RRSIG(NS). + + Returns a ``dns.rdatatype.RdataType``. + """ + + return dns.rdatatype.NONE + + def extended_rdatatype(self) -> int: + """Return a 32-bit type value, the least significant 16 bits of + which are the ordinary DNS type, and the upper 16 bits of which are + the "covered" type, if any. + + Returns an ``int``. + """ + + return self.covers() << 16 | self.rdtype + + def to_text( + self, + origin: dns.name.Name | None = None, + relativize: bool = True, + **kw: Dict[str, Any], + ) -> str: + """Convert an rdata to text format. + + Returns a ``str``. + """ + + raise NotImplementedError # pragma: no cover + + def _to_wire( + self, + file: Any, + compress: dns.name.CompressType | None = None, + origin: dns.name.Name | None = None, + canonicalize: bool = False, + ) -> None: + raise NotImplementedError # pragma: no cover + + def to_wire( + self, + file: Any | None = None, + compress: dns.name.CompressType | None = None, + origin: dns.name.Name | None = None, + canonicalize: bool = False, + ) -> bytes | None: + """Convert an rdata to wire format. + + Returns a ``bytes`` if no output file was specified, or ``None`` otherwise. + """ + + if file: + # We call _to_wire() and then return None explicitly instead of + # of just returning the None from _to_wire() as mypy's func-returns-value + # unhelpfully errors out with "error: "_to_wire" of "Rdata" does not return + # a value (it only ever returns None)" + self._to_wire(file, compress, origin, canonicalize) + return None + else: + f = io.BytesIO() + self._to_wire(f, compress, origin, canonicalize) + return f.getvalue() + + def to_generic(self, origin: dns.name.Name | None = None) -> "GenericRdata": + """Creates a dns.rdata.GenericRdata equivalent of this rdata. + + Returns a ``dns.rdata.GenericRdata``. + """ + wire = self.to_wire(origin=origin) + assert wire is not None # for type checkers + return GenericRdata(self.rdclass, self.rdtype, wire) + + def to_digestable(self, origin: dns.name.Name | None = None) -> bytes: + """Convert rdata to a format suitable for digesting in hashes. This + is also the DNSSEC canonical form. + + Returns a ``bytes``. + """ + wire = self.to_wire(origin=origin, canonicalize=True) + assert wire is not None # for mypy + return wire + + def __repr__(self): + covers = self.covers() + if covers == dns.rdatatype.NONE: + ctext = "" + else: + ctext = "(" + dns.rdatatype.to_text(covers) + ")" + return ( + "" + ) + + def __str__(self): + return self.to_text() + + def _cmp(self, other): + """Compare an rdata with another rdata of the same rdtype and + rdclass. + + For rdata with only absolute names: + Return < 0 if self < other in the DNSSEC ordering, 0 if self + == other, and > 0 if self > other. + For rdata with at least one relative names: + The rdata sorts before any rdata with only absolute names. + When compared with another relative rdata, all names are + made absolute as if they were relative to the root, as the + proper origin is not available. While this creates a stable + ordering, it is NOT guaranteed to be the DNSSEC ordering. + In the future, all ordering comparisons for rdata with + relative names will be disallowed. + """ + # the next two lines are for type checkers, so they are bound + our = b"" + their = b"" + try: + our = self.to_digestable() + our_relative = False + except dns.name.NeedAbsoluteNameOrOrigin: + if _allow_relative_comparisons: + our = self.to_digestable(dns.name.root) + our_relative = True + try: + their = other.to_digestable() + their_relative = False + except dns.name.NeedAbsoluteNameOrOrigin: + if _allow_relative_comparisons: + their = other.to_digestable(dns.name.root) + their_relative = True + if _allow_relative_comparisons: + if our_relative != their_relative: + # For the purpose of comparison, all rdata with at least one + # relative name is less than an rdata with only absolute names. + if our_relative: + return -1 + else: + return 1 + elif our_relative or their_relative: + raise NoRelativeRdataOrdering + if our == their: + return 0 + elif our > their: + return 1 + else: + return -1 + + def __eq__(self, other): + if not isinstance(other, Rdata): + return False + if self.rdclass != other.rdclass or self.rdtype != other.rdtype: + return False + our_relative = False + their_relative = False + try: + our = self.to_digestable() + except dns.name.NeedAbsoluteNameOrOrigin: + our = self.to_digestable(dns.name.root) + our_relative = True + try: + their = other.to_digestable() + except dns.name.NeedAbsoluteNameOrOrigin: + their = other.to_digestable(dns.name.root) + their_relative = True + if our_relative != their_relative: + return False + return our == their + + def __ne__(self, other): + if not isinstance(other, Rdata): + return True + if self.rdclass != other.rdclass or self.rdtype != other.rdtype: + return True + return not self.__eq__(other) + + def __lt__(self, other): + if ( + not isinstance(other, Rdata) + or self.rdclass != other.rdclass + or self.rdtype != other.rdtype + ): + return NotImplemented + return self._cmp(other) < 0 + + def __le__(self, other): + if ( + not isinstance(other, Rdata) + or self.rdclass != other.rdclass + or self.rdtype != other.rdtype + ): + return NotImplemented + return self._cmp(other) <= 0 + + def __ge__(self, other): + if ( + not isinstance(other, Rdata) + or self.rdclass != other.rdclass + or self.rdtype != other.rdtype + ): + return NotImplemented + return self._cmp(other) >= 0 + + def __gt__(self, other): + if ( + not isinstance(other, Rdata) + or self.rdclass != other.rdclass + or self.rdtype != other.rdtype + ): + return NotImplemented + return self._cmp(other) > 0 + + def __hash__(self): + return hash(self.to_digestable(dns.name.root)) + + @classmethod + def from_text( + cls, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + tok: dns.tokenizer.Tokenizer, + origin: dns.name.Name | None = None, + relativize: bool = True, + relativize_to: dns.name.Name | None = None, + ) -> "Rdata": + raise NotImplementedError # pragma: no cover + + @classmethod + def from_wire_parser( + cls, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + parser: dns.wire.Parser, + origin: dns.name.Name | None = None, + ) -> "Rdata": + raise NotImplementedError # pragma: no cover + + def replace(self, **kwargs: Any) -> "Rdata": + """ + Create a new Rdata instance based on the instance replace was + invoked on. It is possible to pass different parameters to + override the corresponding properties of the base Rdata. + + Any field specific to the Rdata type can be replaced, but the + *rdtype* and *rdclass* fields cannot. + + Returns an instance of the same Rdata subclass as *self*. + """ + + # Get the constructor parameters. + parameters = inspect.signature(self.__init__).parameters # type: ignore + + # Ensure that all of the arguments correspond to valid fields. + # Don't allow rdclass or rdtype to be changed, though. + for key in kwargs: + if key == "rdcomment": + continue + if key not in parameters: + raise AttributeError( + f"'{self.__class__.__name__}' object has no attribute '{key}'" + ) + if key in ("rdclass", "rdtype"): + raise AttributeError( + f"Cannot overwrite '{self.__class__.__name__}' attribute '{key}'" + ) + + # Construct the parameter list. For each field, use the value in + # kwargs if present, and the current value otherwise. + args = (kwargs.get(key, getattr(self, key)) for key in parameters) + + # Create, validate, and return the new object. + rd = self.__class__(*args) + # The comment is not set in the constructor, so give it special + # handling. + rdcomment = kwargs.get("rdcomment", self.rdcomment) + if rdcomment is not None: + object.__setattr__(rd, "rdcomment", rdcomment) + return rd + + # Type checking and conversion helpers. These are class methods as + # they don't touch object state and may be useful to others. + + @classmethod + def _as_rdataclass(cls, value): + return dns.rdataclass.RdataClass.make(value) + + @classmethod + def _as_rdatatype(cls, value): + return dns.rdatatype.RdataType.make(value) + + @classmethod + def _as_bytes( + cls, + value: Any, + encode: bool = False, + max_length: int | None = None, + empty_ok: bool = True, + ) -> bytes: + if encode and isinstance(value, str): + bvalue = value.encode() + elif isinstance(value, bytearray): + bvalue = bytes(value) + elif isinstance(value, bytes): + bvalue = value + else: + raise ValueError("not bytes") + if max_length is not None and len(bvalue) > max_length: + raise ValueError("too long") + if not empty_ok and len(bvalue) == 0: + raise ValueError("empty bytes not allowed") + return bvalue + + @classmethod + def _as_name(cls, value): + # Note that proper name conversion (e.g. with origin and IDNA + # awareness) is expected to be done via from_text. This is just + # a simple thing for people invoking the constructor directly. + if isinstance(value, str): + return dns.name.from_text(value) + elif not isinstance(value, dns.name.Name): + raise ValueError("not a name") + return value + + @classmethod + def _as_uint8(cls, value): + if not isinstance(value, int): + raise ValueError("not an integer") + if value < 0 or value > 255: + raise ValueError("not a uint8") + return value + + @classmethod + def _as_uint16(cls, value): + if not isinstance(value, int): + raise ValueError("not an integer") + if value < 0 or value > 65535: + raise ValueError("not a uint16") + return value + + @classmethod + def _as_uint32(cls, value): + if not isinstance(value, int): + raise ValueError("not an integer") + if value < 0 or value > 4294967295: + raise ValueError("not a uint32") + return value + + @classmethod + def _as_uint48(cls, value): + if not isinstance(value, int): + raise ValueError("not an integer") + if value < 0 or value > 281474976710655: + raise ValueError("not a uint48") + return value + + @classmethod + def _as_int(cls, value, low=None, high=None): + if not isinstance(value, int): + raise ValueError("not an integer") + if low is not None and value < low: + raise ValueError("value too small") + if high is not None and value > high: + raise ValueError("value too large") + return value + + @classmethod + def _as_ipv4_address(cls, value): + if isinstance(value, str): + return dns.ipv4.canonicalize(value) + elif isinstance(value, bytes): + return dns.ipv4.inet_ntoa(value) + elif isinstance(value, ipaddress.IPv4Address): + return dns.ipv4.inet_ntoa(value.packed) + else: + raise ValueError("not an IPv4 address") + + @classmethod + def _as_ipv6_address(cls, value): + if isinstance(value, str): + return dns.ipv6.canonicalize(value) + elif isinstance(value, bytes): + return dns.ipv6.inet_ntoa(value) + elif isinstance(value, ipaddress.IPv6Address): + return dns.ipv6.inet_ntoa(value.packed) + else: + raise ValueError("not an IPv6 address") + + @classmethod + def _as_bool(cls, value): + if isinstance(value, bool): + return value + else: + raise ValueError("not a boolean") + + @classmethod + def _as_ttl(cls, value): + if isinstance(value, int): + return cls._as_int(value, 0, dns.ttl.MAX_TTL) + elif isinstance(value, str): + return dns.ttl.from_text(value) + else: + raise ValueError("not a TTL") + + @classmethod + def _as_tuple(cls, value, as_value): + try: + # For user convenience, if value is a singleton of the list + # element type, wrap it in a tuple. + return (as_value(value),) + except Exception: + # Otherwise, check each element of the iterable *value* + # against *as_value*. + return tuple(as_value(v) for v in value) + + # Processing order + + @classmethod + def _processing_order(cls, iterable): + items = list(iterable) + random.shuffle(items) + return items + + +@dns.immutable.immutable +class GenericRdata(Rdata): + """Generic Rdata Class + + This class is used for rdata types for which we have no better + implementation. It implements the DNS "unknown RRs" scheme. + """ + + __slots__ = ["data"] + + def __init__( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + data: bytes, + ) -> None: + super().__init__(rdclass, rdtype) + self.data = data + + def to_text( + self, + origin: dns.name.Name | None = None, + relativize: bool = True, + **kw: Dict[str, Any], + ) -> str: + return rf"\# {len(self.data)} " + _hexify(self.data, **kw) # pyright: ignore + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + token = tok.get() + if not token.is_identifier() or token.value != r"\#": + raise dns.exception.SyntaxError(r"generic rdata does not start with \#") + length = tok.get_int() + hex = tok.concatenate_remaining_identifiers(True).encode() + data = binascii.unhexlify(hex) + if len(data) != length: + raise dns.exception.SyntaxError("generic rdata hex data has wrong length") + return cls(rdclass, rdtype, data) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(self.data) + + def to_generic(self, origin: dns.name.Name | None = None) -> "GenericRdata": + return self + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + return cls(rdclass, rdtype, parser.get_remaining()) + + +_rdata_classes: Dict[Tuple[dns.rdataclass.RdataClass, dns.rdatatype.RdataType], Any] = ( + {} +) +_module_prefix = "dns.rdtypes" +_dynamic_load_allowed = True + + +def get_rdata_class(rdclass, rdtype, use_generic=True): + cls = _rdata_classes.get((rdclass, rdtype)) + if not cls: + cls = _rdata_classes.get((dns.rdataclass.ANY, rdtype)) + if not cls and _dynamic_load_allowed: + rdclass_text = dns.rdataclass.to_text(rdclass) + rdtype_text = dns.rdatatype.to_text(rdtype) + rdtype_text = rdtype_text.replace("-", "_") + try: + mod = import_module( + ".".join([_module_prefix, rdclass_text, rdtype_text]) + ) + cls = getattr(mod, rdtype_text) + _rdata_classes[(rdclass, rdtype)] = cls + except ImportError: + try: + mod = import_module(".".join([_module_prefix, "ANY", rdtype_text])) + cls = getattr(mod, rdtype_text) + _rdata_classes[(dns.rdataclass.ANY, rdtype)] = cls + _rdata_classes[(rdclass, rdtype)] = cls + except ImportError: + pass + if not cls and use_generic: + cls = GenericRdata + _rdata_classes[(rdclass, rdtype)] = cls + return cls + + +def load_all_types(disable_dynamic_load=True): + """Load all rdata types for which dnspython has a non-generic implementation. + + Normally dnspython loads DNS rdatatype implementations on demand, but in some + specialized cases loading all types at an application-controlled time is preferred. + + If *disable_dynamic_load*, a ``bool``, is ``True`` then dnspython will not attempt + to use its dynamic loading mechanism if an unknown type is subsequently encountered, + and will simply use the ``GenericRdata`` class. + """ + # Load class IN and ANY types. + for rdtype in dns.rdatatype.RdataType: + get_rdata_class(dns.rdataclass.IN, rdtype, False) + # Load the one non-ANY implementation we have in CH. Everything + # else in CH is an ANY type, and we'll discover those on demand but won't + # have to import anything. + get_rdata_class(dns.rdataclass.CH, dns.rdatatype.A, False) + if disable_dynamic_load: + # Now disable dynamic loading so any subsequent unknown type immediately becomes + # GenericRdata without a load attempt. + global _dynamic_load_allowed + _dynamic_load_allowed = False + + +def from_text( + rdclass: dns.rdataclass.RdataClass | str, + rdtype: dns.rdatatype.RdataType | str, + tok: dns.tokenizer.Tokenizer | str, + origin: dns.name.Name | None = None, + relativize: bool = True, + relativize_to: dns.name.Name | None = None, + idna_codec: dns.name.IDNACodec | None = None, +) -> Rdata: + """Build an rdata object from text format. + + This function attempts to dynamically load a class which + implements the specified rdata class and type. If there is no + class-and-type-specific implementation, the GenericRdata class + is used. + + Once a class is chosen, its from_text() class method is called + with the parameters to this function. + + If *tok* is a ``str``, then a tokenizer is created and the string + is used as its input. + + *rdclass*, a ``dns.rdataclass.RdataClass`` or ``str``, the rdataclass. + + *rdtype*, a ``dns.rdatatype.RdataType`` or ``str``, the rdatatype. + + *tok*, a ``dns.tokenizer.Tokenizer`` or a ``str``. + + *origin*, a ``dns.name.Name`` (or ``None``), the + origin to use for relative names. + + *relativize*, a ``bool``. If true, name will be relativized. + + *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use + when relativizing names. If not set, the *origin* value will be used. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder to use if a tokenizer needs to be created. If + ``None``, the default IDNA 2003 encoder/decoder is used. If a + tokenizer is not created, then the codec associated with the tokenizer + is the one that is used. + + Returns an instance of the chosen Rdata subclass. + + """ + if isinstance(tok, str): + tok = dns.tokenizer.Tokenizer(tok, idna_codec=idna_codec) + if not isinstance(tok, dns.tokenizer.Tokenizer): + raise ValueError("tok must be a string or a Tokenizer") + rdclass = dns.rdataclass.RdataClass.make(rdclass) + rdtype = dns.rdatatype.RdataType.make(rdtype) + cls = get_rdata_class(rdclass, rdtype) + assert cls is not None # for type checkers + with dns.exception.ExceptionWrapper(dns.exception.SyntaxError): + rdata = None + if cls != GenericRdata: + # peek at first token + token = tok.get() + tok.unget(token) + if token.is_identifier() and token.value == r"\#": + # + # Known type using the generic syntax. Extract the + # wire form from the generic syntax, and then run + # from_wire on it. + # + grdata = GenericRdata.from_text( + rdclass, rdtype, tok, origin, relativize, relativize_to + ) + rdata = from_wire( + rdclass, rdtype, grdata.data, 0, len(grdata.data), origin + ) + # + # If this comparison isn't equal, then there must have been + # compressed names in the wire format, which is an error, + # there being no reasonable context to decompress with. + # + rwire = rdata.to_wire() + if rwire != grdata.data: + raise dns.exception.SyntaxError( + "compressed data in " + "generic syntax form " + "of known rdatatype" + ) + if rdata is None: + rdata = cls.from_text( + rdclass, rdtype, tok, origin, relativize, relativize_to + ) + token = tok.get_eol_as_token() + if token.comment is not None: + object.__setattr__(rdata, "rdcomment", token.comment) + return rdata + + +def from_wire_parser( + rdclass: dns.rdataclass.RdataClass | str, + rdtype: dns.rdatatype.RdataType | str, + parser: dns.wire.Parser, + origin: dns.name.Name | None = None, +) -> Rdata: + """Build an rdata object from wire format + + This function attempts to dynamically load a class which + implements the specified rdata class and type. If there is no + class-and-type-specific implementation, the GenericRdata class + is used. + + Once a class is chosen, its from_wire() class method is called + with the parameters to this function. + + *rdclass*, a ``dns.rdataclass.RdataClass`` or ``str``, the rdataclass. + + *rdtype*, a ``dns.rdatatype.RdataType`` or ``str``, the rdatatype. + + *parser*, a ``dns.wire.Parser``, the parser, which should be + restricted to the rdata length. + + *origin*, a ``dns.name.Name`` (or ``None``). If not ``None``, + then names will be relativized to this origin. + + Returns an instance of the chosen Rdata subclass. + """ + + rdclass = dns.rdataclass.RdataClass.make(rdclass) + rdtype = dns.rdatatype.RdataType.make(rdtype) + cls = get_rdata_class(rdclass, rdtype) + assert cls is not None # for type checkers + with dns.exception.ExceptionWrapper(dns.exception.FormError): + return cls.from_wire_parser(rdclass, rdtype, parser, origin) + + +def from_wire( + rdclass: dns.rdataclass.RdataClass | str, + rdtype: dns.rdatatype.RdataType | str, + wire: bytes, + current: int, + rdlen: int, + origin: dns.name.Name | None = None, +) -> Rdata: + """Build an rdata object from wire format + + This function attempts to dynamically load a class which + implements the specified rdata class and type. If there is no + class-and-type-specific implementation, the GenericRdata class + is used. + + Once a class is chosen, its from_wire() class method is called + with the parameters to this function. + + *rdclass*, an ``int``, the rdataclass. + + *rdtype*, an ``int``, the rdatatype. + + *wire*, a ``bytes``, the wire-format message. + + *current*, an ``int``, the offset in wire of the beginning of + the rdata. + + *rdlen*, an ``int``, the length of the wire-format rdata + + *origin*, a ``dns.name.Name`` (or ``None``). If not ``None``, + then names will be relativized to this origin. + + Returns an instance of the chosen Rdata subclass. + """ + parser = dns.wire.Parser(wire, current) + with parser.restrict_to(rdlen): + return from_wire_parser(rdclass, rdtype, parser, origin) + + +class RdatatypeExists(dns.exception.DNSException): + """DNS rdatatype already exists.""" + + supp_kwargs = {"rdclass", "rdtype"} + fmt = ( + "The rdata type with class {rdclass:d} and rdtype {rdtype:d} " + + "already exists." + ) + + +def register_type( + implementation: Any, + rdtype: int, + rdtype_text: str, + is_singleton: bool = False, + rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, +) -> None: + """Dynamically register a module to handle an rdatatype. + + *implementation*, a subclass of ``dns.rdata.Rdata`` implementing the type, + or a module containing such a class named by its text form. + + *rdtype*, an ``int``, the rdatatype to register. + + *rdtype_text*, a ``str``, the textual form of the rdatatype. + + *is_singleton*, a ``bool``, indicating if the type is a singleton (i.e. + RRsets of the type can have only one member.) + + *rdclass*, the rdataclass of the type, or ``dns.rdataclass.ANY`` if + it applies to all classes. + """ + + rdtype = dns.rdatatype.RdataType.make(rdtype) + existing_cls = get_rdata_class(rdclass, rdtype) + if existing_cls != GenericRdata or dns.rdatatype.is_metatype(rdtype): + raise RdatatypeExists(rdclass=rdclass, rdtype=rdtype) + if isinstance(implementation, type) and issubclass(implementation, Rdata): + impclass = implementation + else: + impclass = getattr(implementation, rdtype_text.replace("-", "_")) + _rdata_classes[(rdclass, rdtype)] = impclass + dns.rdatatype.register_type(rdtype, rdtype_text, is_singleton) diff --git a/venv/lib/python3.12/site-packages/dns/rdataclass.py b/venv/lib/python3.12/site-packages/dns/rdataclass.py new file mode 100644 index 0000000..89b85a7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdataclass.py @@ -0,0 +1,118 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS Rdata Classes.""" + +import dns.enum +import dns.exception + + +class RdataClass(dns.enum.IntEnum): + """DNS Rdata Class""" + + RESERVED0 = 0 + IN = 1 + INTERNET = IN + CH = 3 + CHAOS = CH + HS = 4 + HESIOD = HS + NONE = 254 + ANY = 255 + + @classmethod + def _maximum(cls): + return 65535 + + @classmethod + def _short_name(cls): + return "class" + + @classmethod + def _prefix(cls): + return "CLASS" + + @classmethod + def _unknown_exception_class(cls): + return UnknownRdataclass + + +_metaclasses = {RdataClass.NONE, RdataClass.ANY} + + +class UnknownRdataclass(dns.exception.DNSException): + """A DNS class is unknown.""" + + +def from_text(text: str) -> RdataClass: + """Convert text into a DNS rdata class value. + + The input text can be a defined DNS RR class mnemonic or + instance of the DNS generic class syntax. + + For example, "IN" and "CLASS1" will both result in a value of 1. + + Raises ``dns.rdatatype.UnknownRdataclass`` if the class is unknown. + + Raises ``ValueError`` if the rdata class value is not >= 0 and <= 65535. + + Returns a ``dns.rdataclass.RdataClass``. + """ + + return RdataClass.from_text(text) + + +def to_text(value: RdataClass) -> str: + """Convert a DNS rdata class value to text. + + If the value has a known mnemonic, it will be used, otherwise the + DNS generic class syntax will be used. + + Raises ``ValueError`` if the rdata class value is not >= 0 and <= 65535. + + Returns a ``str``. + """ + + return RdataClass.to_text(value) + + +def is_metaclass(rdclass: RdataClass) -> bool: + """True if the specified class is a metaclass. + + The currently defined metaclasses are ANY and NONE. + + *rdclass* is a ``dns.rdataclass.RdataClass``. + """ + + if rdclass in _metaclasses: + return True + return False + + +### BEGIN generated RdataClass constants + +RESERVED0 = RdataClass.RESERVED0 +IN = RdataClass.IN +INTERNET = RdataClass.INTERNET +CH = RdataClass.CH +CHAOS = RdataClass.CHAOS +HS = RdataClass.HS +HESIOD = RdataClass.HESIOD +NONE = RdataClass.NONE +ANY = RdataClass.ANY + +### END generated RdataClass constants diff --git a/venv/lib/python3.12/site-packages/dns/rdataset.py b/venv/lib/python3.12/site-packages/dns/rdataset.py new file mode 100644 index 0000000..1edf67d --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdataset.py @@ -0,0 +1,508 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS rdatasets (an rdataset is a set of rdatas of a given type and class)""" + +import io +import random +import struct +from typing import Any, Collection, Dict, List, cast + +import dns.exception +import dns.immutable +import dns.name +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.renderer +import dns.set +import dns.ttl + +# define SimpleSet here for backwards compatibility +SimpleSet = dns.set.Set + + +class DifferingCovers(dns.exception.DNSException): + """An attempt was made to add a DNS SIG/RRSIG whose covered type + is not the same as that of the other rdatas in the rdataset.""" + + +class IncompatibleTypes(dns.exception.DNSException): + """An attempt was made to add DNS RR data of an incompatible type.""" + + +class Rdataset(dns.set.Set): + """A DNS rdataset.""" + + __slots__ = ["rdclass", "rdtype", "covers", "ttl"] + + def __init__( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + ttl: int = 0, + ): + """Create a new rdataset of the specified class and type. + + *rdclass*, a ``dns.rdataclass.RdataClass``, the rdataclass. + + *rdtype*, an ``dns.rdatatype.RdataType``, the rdatatype. + + *covers*, an ``dns.rdatatype.RdataType``, the covered rdatatype. + + *ttl*, an ``int``, the TTL. + """ + + super().__init__() + self.rdclass = rdclass + self.rdtype: dns.rdatatype.RdataType = rdtype + self.covers: dns.rdatatype.RdataType = covers + self.ttl = ttl + + def _clone(self): + obj = cast(Rdataset, super()._clone()) + obj.rdclass = self.rdclass + obj.rdtype = self.rdtype + obj.covers = self.covers + obj.ttl = self.ttl + return obj + + def update_ttl(self, ttl: int) -> None: + """Perform TTL minimization. + + Set the TTL of the rdataset to be the lesser of the set's current + TTL or the specified TTL. If the set contains no rdatas, set the TTL + to the specified TTL. + + *ttl*, an ``int`` or ``str``. + """ + ttl = dns.ttl.make(ttl) + if len(self) == 0: + self.ttl = ttl + elif ttl < self.ttl: + self.ttl = ttl + + # pylint: disable=arguments-differ,arguments-renamed + def add( # pyright: ignore + self, rd: dns.rdata.Rdata, ttl: int | None = None + ) -> None: + """Add the specified rdata to the rdataset. + + If the optional *ttl* parameter is supplied, then + ``self.update_ttl(ttl)`` will be called prior to adding the rdata. + + *rd*, a ``dns.rdata.Rdata``, the rdata + + *ttl*, an ``int``, the TTL. + + Raises ``dns.rdataset.IncompatibleTypes`` if the type and class + do not match the type and class of the rdataset. + + Raises ``dns.rdataset.DifferingCovers`` if the type is a signature + type and the covered type does not match that of the rdataset. + """ + + # + # If we're adding a signature, do some special handling to + # check that the signature covers the same type as the + # other rdatas in this rdataset. If this is the first rdata + # in the set, initialize the covers field. + # + if self.rdclass != rd.rdclass or self.rdtype != rd.rdtype: + raise IncompatibleTypes + if ttl is not None: + self.update_ttl(ttl) + if self.rdtype == dns.rdatatype.RRSIG or self.rdtype == dns.rdatatype.SIG: + covers = rd.covers() + if len(self) == 0 and self.covers == dns.rdatatype.NONE: + self.covers = covers + elif self.covers != covers: + raise DifferingCovers + if dns.rdatatype.is_singleton(rd.rdtype) and len(self) > 0: + self.clear() + super().add(rd) + + def union_update(self, other): + self.update_ttl(other.ttl) + super().union_update(other) + + def intersection_update(self, other): + self.update_ttl(other.ttl) + super().intersection_update(other) + + def update(self, other): + """Add all rdatas in other to self. + + *other*, a ``dns.rdataset.Rdataset``, the rdataset from which + to update. + """ + + self.update_ttl(other.ttl) + super().update(other) + + def _rdata_repr(self): + def maybe_truncate(s): + if len(s) > 100: + return s[:100] + "..." + return s + + return "[" + ", ".join(f"<{maybe_truncate(str(rr))}>" for rr in self) + "]" + + def __repr__(self): + if self.covers == 0: + ctext = "" + else: + ctext = "(" + dns.rdatatype.to_text(self.covers) + ")" + return ( + "" + ) + + def __str__(self): + return self.to_text() + + def __eq__(self, other): + if not isinstance(other, Rdataset): + return False + if ( + self.rdclass != other.rdclass + or self.rdtype != other.rdtype + or self.covers != other.covers + ): + return False + return super().__eq__(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def to_text( + self, + name: dns.name.Name | None = None, + origin: dns.name.Name | None = None, + relativize: bool = True, + override_rdclass: dns.rdataclass.RdataClass | None = None, + want_comments: bool = False, + **kw: Dict[str, Any], + ) -> str: + """Convert the rdataset into DNS zone file format. + + See ``dns.name.Name.choose_relativity`` for more information + on how *origin* and *relativize* determine the way names + are emitted. + + Any additional keyword arguments are passed on to the rdata + ``to_text()`` method. + + *name*, a ``dns.name.Name``. If name is not ``None``, emit RRs with + *name* as the owner name. + + *origin*, a ``dns.name.Name`` or ``None``, the origin for relative + names. + + *relativize*, a ``bool``. If ``True``, names will be relativized + to *origin*. + + *override_rdclass*, a ``dns.rdataclass.RdataClass`` or ``None``. + If not ``None``, use this class instead of the Rdataset's class. + + *want_comments*, a ``bool``. If ``True``, emit comments for rdata + which have them. The default is ``False``. + """ + + if name is not None: + name = name.choose_relativity(origin, relativize) + ntext = str(name) + pad = " " + else: + ntext = "" + pad = "" + s = io.StringIO() + if override_rdclass is not None: + rdclass = override_rdclass + else: + rdclass = self.rdclass + if len(self) == 0: + # + # Empty rdatasets are used for the question section, and in + # some dynamic updates, so we don't need to print out the TTL + # (which is meaningless anyway). + # + s.write( + f"{ntext}{pad}{dns.rdataclass.to_text(rdclass)} " + f"{dns.rdatatype.to_text(self.rdtype)}\n" + ) + else: + for rd in self: + extra = "" + if want_comments: + if rd.rdcomment: + extra = f" ;{rd.rdcomment}" + s.write( + f"{ntext}{pad}{self.ttl} " + f"{dns.rdataclass.to_text(rdclass)} " + f"{dns.rdatatype.to_text(self.rdtype)} " + f"{rd.to_text(origin=origin, relativize=relativize, **kw)}" + f"{extra}\n" + ) + # + # We strip off the final \n for the caller's convenience in printing + # + return s.getvalue()[:-1] + + def to_wire( + self, + name: dns.name.Name, + file: Any, + compress: dns.name.CompressType | None = None, + origin: dns.name.Name | None = None, + override_rdclass: dns.rdataclass.RdataClass | None = None, + want_shuffle: bool = True, + ) -> int: + """Convert the rdataset to wire format. + + *name*, a ``dns.name.Name`` is the owner name to use. + + *file* is the file where the name is emitted (typically a + BytesIO file). + + *compress*, a ``dict``, is the compression table to use. If + ``None`` (the default), names will not be compressed. + + *origin* is a ``dns.name.Name`` or ``None``. If the name is + relative and origin is not ``None``, then *origin* will be appended + to it. + + *override_rdclass*, an ``int``, is used as the class instead of the + class of the rdataset. This is useful when rendering rdatasets + associated with dynamic updates. + + *want_shuffle*, a ``bool``. If ``True``, then the order of the + Rdatas within the Rdataset will be shuffled before rendering. + + Returns an ``int``, the number of records emitted. + """ + + if override_rdclass is not None: + rdclass = override_rdclass + want_shuffle = False + else: + rdclass = self.rdclass + if len(self) == 0: + name.to_wire(file, compress, origin) + file.write(struct.pack("!HHIH", self.rdtype, rdclass, 0, 0)) + return 1 + else: + l: Rdataset | List[dns.rdata.Rdata] + if want_shuffle: + l = list(self) + random.shuffle(l) + else: + l = self + for rd in l: + name.to_wire(file, compress, origin) + file.write(struct.pack("!HHI", self.rdtype, rdclass, self.ttl)) + with dns.renderer.prefixed_length(file, 2): + rd.to_wire(file, compress, origin) + return len(self) + + def match( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType, + ) -> bool: + """Returns ``True`` if this rdataset matches the specified class, + type, and covers. + """ + if self.rdclass == rdclass and self.rdtype == rdtype and self.covers == covers: + return True + return False + + def processing_order(self) -> List[dns.rdata.Rdata]: + """Return rdatas in a valid processing order according to the type's + specification. For example, MX records are in preference order from + lowest to highest preferences, with items of the same preference + shuffled. + + For types that do not define a processing order, the rdatas are + simply shuffled. + """ + if len(self) == 0: + return [] + else: + return self[0]._processing_order(iter(self)) # pyright: ignore + + +@dns.immutable.immutable +class ImmutableRdataset(Rdataset): # lgtm[py/missing-equals] + """An immutable DNS rdataset.""" + + _clone_class = Rdataset + + def __init__(self, rdataset: Rdataset): + """Create an immutable rdataset from the specified rdataset.""" + + super().__init__( + rdataset.rdclass, rdataset.rdtype, rdataset.covers, rdataset.ttl + ) + self.items = dns.immutable.Dict(rdataset.items) + + def update_ttl(self, ttl): + raise TypeError("immutable") + + def add(self, rd, ttl=None): + raise TypeError("immutable") + + def union_update(self, other): + raise TypeError("immutable") + + def intersection_update(self, other): + raise TypeError("immutable") + + def update(self, other): + raise TypeError("immutable") + + def __delitem__(self, i): + raise TypeError("immutable") + + # lgtm complains about these not raising ArithmeticError, but there is + # precedent for overrides of these methods in other classes to raise + # TypeError, and it seems like the better exception. + + def __ior__(self, other): # lgtm[py/unexpected-raise-in-special-method] + raise TypeError("immutable") + + def __iand__(self, other): # lgtm[py/unexpected-raise-in-special-method] + raise TypeError("immutable") + + def __iadd__(self, other): # lgtm[py/unexpected-raise-in-special-method] + raise TypeError("immutable") + + def __isub__(self, other): # lgtm[py/unexpected-raise-in-special-method] + raise TypeError("immutable") + + def clear(self): + raise TypeError("immutable") + + def __copy__(self): + return ImmutableRdataset(super().copy()) # pyright: ignore + + def copy(self): + return ImmutableRdataset(super().copy()) # pyright: ignore + + def union(self, other): + return ImmutableRdataset(super().union(other)) # pyright: ignore + + def intersection(self, other): + return ImmutableRdataset(super().intersection(other)) # pyright: ignore + + def difference(self, other): + return ImmutableRdataset(super().difference(other)) # pyright: ignore + + def symmetric_difference(self, other): + return ImmutableRdataset(super().symmetric_difference(other)) # pyright: ignore + + +def from_text_list( + rdclass: dns.rdataclass.RdataClass | str, + rdtype: dns.rdatatype.RdataType | str, + ttl: int, + text_rdatas: Collection[str], + idna_codec: dns.name.IDNACodec | None = None, + origin: dns.name.Name | None = None, + relativize: bool = True, + relativize_to: dns.name.Name | None = None, +) -> Rdataset: + """Create an rdataset with the specified class, type, and TTL, and with + the specified list of rdatas in text format. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder to use; if ``None``, the default IDNA 2003 + encoder/decoder is used. + + *origin*, a ``dns.name.Name`` (or ``None``), the + origin to use for relative names. + + *relativize*, a ``bool``. If true, name will be relativized. + + *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use + when relativizing names. If not set, the *origin* value will be used. + + Returns a ``dns.rdataset.Rdataset`` object. + """ + + rdclass = dns.rdataclass.RdataClass.make(rdclass) + rdtype = dns.rdatatype.RdataType.make(rdtype) + r = Rdataset(rdclass, rdtype) + r.update_ttl(ttl) + for t in text_rdatas: + rd = dns.rdata.from_text( + r.rdclass, r.rdtype, t, origin, relativize, relativize_to, idna_codec + ) + r.add(rd) + return r + + +def from_text( + rdclass: dns.rdataclass.RdataClass | str, + rdtype: dns.rdatatype.RdataType | str, + ttl: int, + *text_rdatas: Any, +) -> Rdataset: + """Create an rdataset with the specified class, type, and TTL, and with + the specified rdatas in text format. + + Returns a ``dns.rdataset.Rdataset`` object. + """ + + return from_text_list(rdclass, rdtype, ttl, cast(Collection[str], text_rdatas)) + + +def from_rdata_list(ttl: int, rdatas: Collection[dns.rdata.Rdata]) -> Rdataset: + """Create an rdataset with the specified TTL, and with + the specified list of rdata objects. + + Returns a ``dns.rdataset.Rdataset`` object. + """ + + if len(rdatas) == 0: + raise ValueError("rdata list must not be empty") + r = None + for rd in rdatas: + if r is None: + r = Rdataset(rd.rdclass, rd.rdtype) + r.update_ttl(ttl) + r.add(rd) + assert r is not None + return r + + +def from_rdata(ttl: int, *rdatas: Any) -> Rdataset: + """Create an rdataset with the specified TTL, and with + the specified rdata objects. + + Returns a ``dns.rdataset.Rdataset`` object. + """ + + return from_rdata_list(ttl, cast(Collection[dns.rdata.Rdata], rdatas)) diff --git a/venv/lib/python3.12/site-packages/dns/rdatatype.py b/venv/lib/python3.12/site-packages/dns/rdatatype.py new file mode 100644 index 0000000..211d810 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdatatype.py @@ -0,0 +1,338 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS Rdata Types.""" + +from typing import Dict + +import dns.enum +import dns.exception + + +class RdataType(dns.enum.IntEnum): + """DNS Rdata Type""" + + TYPE0 = 0 + NONE = 0 + A = 1 + NS = 2 + MD = 3 + MF = 4 + CNAME = 5 + SOA = 6 + MB = 7 + MG = 8 + MR = 9 + NULL = 10 + WKS = 11 + PTR = 12 + HINFO = 13 + MINFO = 14 + MX = 15 + TXT = 16 + RP = 17 + AFSDB = 18 + X25 = 19 + ISDN = 20 + RT = 21 + NSAP = 22 + NSAP_PTR = 23 + SIG = 24 + KEY = 25 + PX = 26 + GPOS = 27 + AAAA = 28 + LOC = 29 + NXT = 30 + SRV = 33 + NAPTR = 35 + KX = 36 + CERT = 37 + A6 = 38 + DNAME = 39 + OPT = 41 + APL = 42 + DS = 43 + SSHFP = 44 + IPSECKEY = 45 + RRSIG = 46 + NSEC = 47 + DNSKEY = 48 + DHCID = 49 + NSEC3 = 50 + NSEC3PARAM = 51 + TLSA = 52 + SMIMEA = 53 + HIP = 55 + NINFO = 56 + CDS = 59 + CDNSKEY = 60 + OPENPGPKEY = 61 + CSYNC = 62 + ZONEMD = 63 + SVCB = 64 + HTTPS = 65 + DSYNC = 66 + SPF = 99 + UNSPEC = 103 + NID = 104 + L32 = 105 + L64 = 106 + LP = 107 + EUI48 = 108 + EUI64 = 109 + TKEY = 249 + TSIG = 250 + IXFR = 251 + AXFR = 252 + MAILB = 253 + MAILA = 254 + ANY = 255 + URI = 256 + CAA = 257 + AVC = 258 + AMTRELAY = 260 + RESINFO = 261 + WALLET = 262 + TA = 32768 + DLV = 32769 + + @classmethod + def _maximum(cls): + return 65535 + + @classmethod + def _short_name(cls): + return "type" + + @classmethod + def _prefix(cls): + return "TYPE" + + @classmethod + def _extra_from_text(cls, text): + if text.find("-") >= 0: + try: + return cls[text.replace("-", "_")] + except KeyError: # pragma: no cover + pass + return _registered_by_text.get(text) + + @classmethod + def _extra_to_text(cls, value, current_text): + if current_text is None: + return _registered_by_value.get(value) + if current_text.find("_") >= 0: + return current_text.replace("_", "-") + return current_text + + @classmethod + def _unknown_exception_class(cls): + return UnknownRdatatype + + +_registered_by_text: Dict[str, RdataType] = {} +_registered_by_value: Dict[RdataType, str] = {} + +_metatypes = {RdataType.OPT} + +_singletons = { + RdataType.SOA, + RdataType.NXT, + RdataType.DNAME, + RdataType.NSEC, + RdataType.CNAME, +} + + +class UnknownRdatatype(dns.exception.DNSException): + """DNS resource record type is unknown.""" + + +def from_text(text: str) -> RdataType: + """Convert text into a DNS rdata type value. + + The input text can be a defined DNS RR type mnemonic or + instance of the DNS generic type syntax. + + For example, "NS" and "TYPE2" will both result in a value of 2. + + Raises ``dns.rdatatype.UnknownRdatatype`` if the type is unknown. + + Raises ``ValueError`` if the rdata type value is not >= 0 and <= 65535. + + Returns a ``dns.rdatatype.RdataType``. + """ + + return RdataType.from_text(text) + + +def to_text(value: RdataType) -> str: + """Convert a DNS rdata type value to text. + + If the value has a known mnemonic, it will be used, otherwise the + DNS generic type syntax will be used. + + Raises ``ValueError`` if the rdata type value is not >= 0 and <= 65535. + + Returns a ``str``. + """ + + return RdataType.to_text(value) + + +def is_metatype(rdtype: RdataType) -> bool: + """True if the specified type is a metatype. + + *rdtype* is a ``dns.rdatatype.RdataType``. + + The currently defined metatypes are TKEY, TSIG, IXFR, AXFR, MAILA, + MAILB, ANY, and OPT. + + Returns a ``bool``. + """ + + return (256 > rdtype >= 128) or rdtype in _metatypes + + +def is_singleton(rdtype: RdataType) -> bool: + """Is the specified type a singleton type? + + Singleton types can only have a single rdata in an rdataset, or a single + RR in an RRset. + + The currently defined singleton types are CNAME, DNAME, NSEC, NXT, and + SOA. + + *rdtype* is an ``int``. + + Returns a ``bool``. + """ + + if rdtype in _singletons: + return True + return False + + +# pylint: disable=redefined-outer-name +def register_type( + rdtype: RdataType, rdtype_text: str, is_singleton: bool = False +) -> None: + """Dynamically register an rdatatype. + + *rdtype*, a ``dns.rdatatype.RdataType``, the rdatatype to register. + + *rdtype_text*, a ``str``, the textual form of the rdatatype. + + *is_singleton*, a ``bool``, indicating if the type is a singleton (i.e. + RRsets of the type can have only one member.) + """ + + _registered_by_text[rdtype_text] = rdtype + _registered_by_value[rdtype] = rdtype_text + if is_singleton: + _singletons.add(rdtype) + + +### BEGIN generated RdataType constants + +TYPE0 = RdataType.TYPE0 +NONE = RdataType.NONE +A = RdataType.A +NS = RdataType.NS +MD = RdataType.MD +MF = RdataType.MF +CNAME = RdataType.CNAME +SOA = RdataType.SOA +MB = RdataType.MB +MG = RdataType.MG +MR = RdataType.MR +NULL = RdataType.NULL +WKS = RdataType.WKS +PTR = RdataType.PTR +HINFO = RdataType.HINFO +MINFO = RdataType.MINFO +MX = RdataType.MX +TXT = RdataType.TXT +RP = RdataType.RP +AFSDB = RdataType.AFSDB +X25 = RdataType.X25 +ISDN = RdataType.ISDN +RT = RdataType.RT +NSAP = RdataType.NSAP +NSAP_PTR = RdataType.NSAP_PTR +SIG = RdataType.SIG +KEY = RdataType.KEY +PX = RdataType.PX +GPOS = RdataType.GPOS +AAAA = RdataType.AAAA +LOC = RdataType.LOC +NXT = RdataType.NXT +SRV = RdataType.SRV +NAPTR = RdataType.NAPTR +KX = RdataType.KX +CERT = RdataType.CERT +A6 = RdataType.A6 +DNAME = RdataType.DNAME +OPT = RdataType.OPT +APL = RdataType.APL +DS = RdataType.DS +SSHFP = RdataType.SSHFP +IPSECKEY = RdataType.IPSECKEY +RRSIG = RdataType.RRSIG +NSEC = RdataType.NSEC +DNSKEY = RdataType.DNSKEY +DHCID = RdataType.DHCID +NSEC3 = RdataType.NSEC3 +NSEC3PARAM = RdataType.NSEC3PARAM +TLSA = RdataType.TLSA +SMIMEA = RdataType.SMIMEA +HIP = RdataType.HIP +NINFO = RdataType.NINFO +CDS = RdataType.CDS +CDNSKEY = RdataType.CDNSKEY +OPENPGPKEY = RdataType.OPENPGPKEY +CSYNC = RdataType.CSYNC +ZONEMD = RdataType.ZONEMD +SVCB = RdataType.SVCB +HTTPS = RdataType.HTTPS +DSYNC = RdataType.DSYNC +SPF = RdataType.SPF +UNSPEC = RdataType.UNSPEC +NID = RdataType.NID +L32 = RdataType.L32 +L64 = RdataType.L64 +LP = RdataType.LP +EUI48 = RdataType.EUI48 +EUI64 = RdataType.EUI64 +TKEY = RdataType.TKEY +TSIG = RdataType.TSIG +IXFR = RdataType.IXFR +AXFR = RdataType.AXFR +MAILB = RdataType.MAILB +MAILA = RdataType.MAILA +ANY = RdataType.ANY +URI = RdataType.URI +CAA = RdataType.CAA +AVC = RdataType.AVC +AMTRELAY = RdataType.AMTRELAY +RESINFO = RdataType.RESINFO +WALLET = RdataType.WALLET +TA = RdataType.TA +DLV = RdataType.DLV + +### END generated RdataType constants diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/AFSDB.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/AFSDB.py new file mode 100644 index 0000000..06a3b97 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/AFSDB.py @@ -0,0 +1,45 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.mxbase + + +@dns.immutable.immutable +class AFSDB(dns.rdtypes.mxbase.UncompressedDowncasingMX): + """AFSDB record""" + + # Use the property mechanism to make "subtype" an alias for the + # "preference" attribute, and "hostname" an alias for the "exchange" + # attribute. + # + # This lets us inherit the UncompressedMX implementation but lets + # the caller use appropriate attribute names for the rdata type. + # + # We probably lose some performance vs. a cut-and-paste + # implementation, but this way we don't copy code, and that's + # good. + + @property + def subtype(self): + "the AFSDB subtype" + return self.preference + + @property + def hostname(self): + "the AFSDB hostname" + return self.exchange diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/AMTRELAY.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/AMTRELAY.py new file mode 100644 index 0000000..dc9fa87 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/AMTRELAY.py @@ -0,0 +1,89 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.exception +import dns.immutable +import dns.rdata +import dns.rdtypes.util + + +class Relay(dns.rdtypes.util.Gateway): + name = "AMTRELAY relay" + + @property + def relay(self): + return self.gateway + + +@dns.immutable.immutable +class AMTRELAY(dns.rdata.Rdata): + """AMTRELAY record""" + + # see: RFC 8777 + + __slots__ = ["precedence", "discovery_optional", "relay_type", "relay"] + + def __init__( + self, rdclass, rdtype, precedence, discovery_optional, relay_type, relay + ): + super().__init__(rdclass, rdtype) + relay = Relay(relay_type, relay) + self.precedence = self._as_uint8(precedence) + self.discovery_optional = self._as_bool(discovery_optional) + self.relay_type = relay.type + self.relay = relay.relay + + def to_text(self, origin=None, relativize=True, **kw): + relay = Relay(self.relay_type, self.relay).to_text(origin, relativize) + return ( + f"{self.precedence} {self.discovery_optional:d} {self.relay_type} {relay}" + ) + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + precedence = tok.get_uint8() + discovery_optional = tok.get_uint8() + if discovery_optional > 1: + raise dns.exception.SyntaxError("expecting 0 or 1") + discovery_optional = bool(discovery_optional) + relay_type = tok.get_uint8() + if relay_type > 0x7F: + raise dns.exception.SyntaxError("expecting an integer <= 127") + relay = Relay.from_text(relay_type, tok, origin, relativize, relativize_to) + return cls( + rdclass, rdtype, precedence, discovery_optional, relay_type, relay.relay + ) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + relay_type = self.relay_type | (self.discovery_optional << 7) + header = struct.pack("!BB", self.precedence, relay_type) + file.write(header) + Relay(self.relay_type, self.relay).to_wire(file, compress, origin, canonicalize) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + (precedence, relay_type) = parser.get_struct("!BB") + discovery_optional = bool(relay_type >> 7) + relay_type &= 0x7F + relay = Relay.from_wire_parser(relay_type, parser, origin) + return cls( + rdclass, rdtype, precedence, discovery_optional, relay_type, relay.relay + ) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/AVC.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/AVC.py new file mode 100644 index 0000000..a27ae2d --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/AVC.py @@ -0,0 +1,26 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2016 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.txtbase + + +@dns.immutable.immutable +class AVC(dns.rdtypes.txtbase.TXTBase): + """AVC record""" + + # See: IANA dns parameters for AVC diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CAA.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CAA.py new file mode 100644 index 0000000..8c62e62 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CAA.py @@ -0,0 +1,67 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.exception +import dns.immutable +import dns.rdata +import dns.tokenizer + + +@dns.immutable.immutable +class CAA(dns.rdata.Rdata): + """CAA (Certification Authority Authorization) record""" + + # see: RFC 6844 + + __slots__ = ["flags", "tag", "value"] + + def __init__(self, rdclass, rdtype, flags, tag, value): + super().__init__(rdclass, rdtype) + self.flags = self._as_uint8(flags) + self.tag = self._as_bytes(tag, True, 255) + if not tag.isalnum(): + raise ValueError("tag is not alphanumeric") + self.value = self._as_bytes(value) + + def to_text(self, origin=None, relativize=True, **kw): + return f'{self.flags} {dns.rdata._escapify(self.tag)} "{dns.rdata._escapify(self.value)}"' + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + flags = tok.get_uint8() + tag = tok.get_string().encode() + value = tok.get_string().encode() + return cls(rdclass, rdtype, flags, tag, value) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(struct.pack("!B", self.flags)) + l = len(self.tag) + assert l < 256 + file.write(struct.pack("!B", l)) + file.write(self.tag) + file.write(self.value) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + flags = parser.get_uint8() + tag = parser.get_counted_bytes() + value = parser.get_remaining() + return cls(rdclass, rdtype, flags, tag, value) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CDNSKEY.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CDNSKEY.py new file mode 100644 index 0000000..b613409 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CDNSKEY.py @@ -0,0 +1,33 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.dnskeybase # lgtm[py/import-and-import-from] + +# pylint: disable=unused-import +from dns.rdtypes.dnskeybase import ( # noqa: F401 lgtm[py/unused-import] + REVOKE, + SEP, + ZONE, +) + +# pylint: enable=unused-import + + +@dns.immutable.immutable +class CDNSKEY(dns.rdtypes.dnskeybase.DNSKEYBase): + """CDNSKEY record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CDS.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CDS.py new file mode 100644 index 0000000..8312b97 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CDS.py @@ -0,0 +1,29 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.dsbase + + +@dns.immutable.immutable +class CDS(dns.rdtypes.dsbase.DSBase): + """CDS record""" + + _digest_length_by_type = { + **dns.rdtypes.dsbase.DSBase._digest_length_by_type, + 0: 1, # delete, RFC 8078 Sec. 4 (including Errata ID 5049) + } diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CERT.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CERT.py new file mode 100644 index 0000000..4d5e5bd --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CERT.py @@ -0,0 +1,113 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import base64 +import struct + +import dns.dnssectypes +import dns.exception +import dns.immutable +import dns.rdata +import dns.tokenizer + +_ctype_by_value = { + 1: "PKIX", + 2: "SPKI", + 3: "PGP", + 4: "IPKIX", + 5: "ISPKI", + 6: "IPGP", + 7: "ACPKIX", + 8: "IACPKIX", + 253: "URI", + 254: "OID", +} + +_ctype_by_name = { + "PKIX": 1, + "SPKI": 2, + "PGP": 3, + "IPKIX": 4, + "ISPKI": 5, + "IPGP": 6, + "ACPKIX": 7, + "IACPKIX": 8, + "URI": 253, + "OID": 254, +} + + +def _ctype_from_text(what): + v = _ctype_by_name.get(what) + if v is not None: + return v + return int(what) + + +def _ctype_to_text(what): + v = _ctype_by_value.get(what) + if v is not None: + return v + return str(what) + + +@dns.immutable.immutable +class CERT(dns.rdata.Rdata): + """CERT record""" + + # see RFC 4398 + + __slots__ = ["certificate_type", "key_tag", "algorithm", "certificate"] + + def __init__( + self, rdclass, rdtype, certificate_type, key_tag, algorithm, certificate + ): + super().__init__(rdclass, rdtype) + self.certificate_type = self._as_uint16(certificate_type) + self.key_tag = self._as_uint16(key_tag) + self.algorithm = self._as_uint8(algorithm) + self.certificate = self._as_bytes(certificate) + + def to_text(self, origin=None, relativize=True, **kw): + certificate_type = _ctype_to_text(self.certificate_type) + algorithm = dns.dnssectypes.Algorithm.to_text(self.algorithm) + certificate = dns.rdata._base64ify(self.certificate, **kw) # pyright: ignore + return f"{certificate_type} {self.key_tag} {algorithm} {certificate}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + certificate_type = _ctype_from_text(tok.get_string()) + key_tag = tok.get_uint16() + algorithm = dns.dnssectypes.Algorithm.from_text(tok.get_string()) + b64 = tok.concatenate_remaining_identifiers().encode() + certificate = base64.b64decode(b64) + return cls(rdclass, rdtype, certificate_type, key_tag, algorithm, certificate) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + prefix = struct.pack( + "!HHB", self.certificate_type, self.key_tag, self.algorithm + ) + file.write(prefix) + file.write(self.certificate) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + (certificate_type, key_tag, algorithm) = parser.get_struct("!HHB") + certificate = parser.get_remaining() + return cls(rdclass, rdtype, certificate_type, key_tag, algorithm, certificate) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CNAME.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CNAME.py new file mode 100644 index 0000000..665e407 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CNAME.py @@ -0,0 +1,28 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.nsbase + + +@dns.immutable.immutable +class CNAME(dns.rdtypes.nsbase.NSBase): + """CNAME record + + Note: although CNAME is officially a singleton type, dnspython allows + non-singleton CNAME rdatasets because such sets have been commonly + used by BIND and other nameservers for load balancing.""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CSYNC.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CSYNC.py new file mode 100644 index 0000000..103486d --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/CSYNC.py @@ -0,0 +1,68 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2004-2007, 2009-2011, 2016 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.exception +import dns.immutable +import dns.name +import dns.rdata +import dns.rdatatype +import dns.rdtypes.util + + +@dns.immutable.immutable +class Bitmap(dns.rdtypes.util.Bitmap): + type_name = "CSYNC" + + +@dns.immutable.immutable +class CSYNC(dns.rdata.Rdata): + """CSYNC record""" + + __slots__ = ["serial", "flags", "windows"] + + def __init__(self, rdclass, rdtype, serial, flags, windows): + super().__init__(rdclass, rdtype) + self.serial = self._as_uint32(serial) + self.flags = self._as_uint16(flags) + if not isinstance(windows, Bitmap): + windows = Bitmap(windows) + self.windows = tuple(windows.windows) + + def to_text(self, origin=None, relativize=True, **kw): + text = Bitmap(self.windows).to_text() + return f"{self.serial} {self.flags}{text}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + serial = tok.get_uint32() + flags = tok.get_uint16() + bitmap = Bitmap.from_text(tok) + return cls(rdclass, rdtype, serial, flags, bitmap) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(struct.pack("!IH", self.serial, self.flags)) + Bitmap(self.windows).to_wire(file) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + (serial, flags) = parser.get_struct("!IH") + bitmap = Bitmap.from_wire_parser(parser) + return cls(rdclass, rdtype, serial, flags, bitmap) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DLV.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DLV.py new file mode 100644 index 0000000..6c134f1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DLV.py @@ -0,0 +1,24 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.dsbase + + +@dns.immutable.immutable +class DLV(dns.rdtypes.dsbase.DSBase): + """DLV record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DNAME.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DNAME.py new file mode 100644 index 0000000..bbf9186 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DNAME.py @@ -0,0 +1,27 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.nsbase + + +@dns.immutable.immutable +class DNAME(dns.rdtypes.nsbase.UncompressedNS): + """DNAME record""" + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + self.target.to_wire(file, None, origin, canonicalize) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DNSKEY.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DNSKEY.py new file mode 100644 index 0000000..6d961a9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DNSKEY.py @@ -0,0 +1,33 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.dnskeybase # lgtm[py/import-and-import-from] + +# pylint: disable=unused-import +from dns.rdtypes.dnskeybase import ( # noqa: F401 lgtm[py/unused-import] + REVOKE, + SEP, + ZONE, +) + +# pylint: enable=unused-import + + +@dns.immutable.immutable +class DNSKEY(dns.rdtypes.dnskeybase.DNSKEYBase): + """DNSKEY record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DS.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DS.py new file mode 100644 index 0000000..58b3108 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DS.py @@ -0,0 +1,24 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.dsbase + + +@dns.immutable.immutable +class DS(dns.rdtypes.dsbase.DSBase): + """DS record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DSYNC.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DSYNC.py new file mode 100644 index 0000000..e8d1394 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/DSYNC.py @@ -0,0 +1,72 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import struct + +import dns.enum +import dns.exception +import dns.immutable +import dns.rdata +import dns.rdatatype +import dns.rdtypes.util + + +class UnknownScheme(dns.exception.DNSException): + """Unknown DSYNC scheme""" + + +class Scheme(dns.enum.IntEnum): + """DSYNC SCHEME""" + + NOTIFY = 1 + + @classmethod + def _maximum(cls): + return 255 + + @classmethod + def _unknown_exception_class(cls): + return UnknownScheme + + +@dns.immutable.immutable +class DSYNC(dns.rdata.Rdata): + """DSYNC record""" + + # see: draft-ietf-dnsop-generalized-notify + + __slots__ = ["rrtype", "scheme", "port", "target"] + + def __init__(self, rdclass, rdtype, rrtype, scheme, port, target): + super().__init__(rdclass, rdtype) + self.rrtype = self._as_rdatatype(rrtype) + self.scheme = Scheme.make(scheme) + self.port = self._as_uint16(port) + self.target = self._as_name(target) + + def to_text(self, origin=None, relativize=True, **kw): + target = self.target.choose_relativity(origin, relativize) + return ( + f"{dns.rdatatype.to_text(self.rrtype)} {Scheme.to_text(self.scheme)} " + f"{self.port} {target}" + ) + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + rrtype = dns.rdatatype.from_text(tok.get_string()) + scheme = Scheme.make(tok.get_string()) + port = tok.get_uint16() + target = tok.get_name(origin, relativize, relativize_to) + return cls(rdclass, rdtype, rrtype, scheme, port, target) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + three_ints = struct.pack("!HBH", self.rrtype, self.scheme, self.port) + file.write(three_ints) + self.target.to_wire(file, None, origin, False) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + (rrtype, scheme, port) = parser.get_struct("!HBH") + target = parser.get_name(origin) + return cls(rdclass, rdtype, rrtype, scheme, port, target) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/EUI48.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/EUI48.py new file mode 100644 index 0000000..c843be5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/EUI48.py @@ -0,0 +1,30 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2015 Red Hat, Inc. +# Author: Petr Spacek +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 RED HAT DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.euibase + + +@dns.immutable.immutable +class EUI48(dns.rdtypes.euibase.EUIBase): + """EUI48 record""" + + # see: rfc7043.txt + + byte_len = 6 # 0123456789ab (in hex) + text_len = byte_len * 3 - 1 # 01-23-45-67-89-ab diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/EUI64.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/EUI64.py new file mode 100644 index 0000000..f6d7e25 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/EUI64.py @@ -0,0 +1,30 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2015 Red Hat, Inc. +# Author: Petr Spacek +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 RED HAT DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.euibase + + +@dns.immutable.immutable +class EUI64(dns.rdtypes.euibase.EUIBase): + """EUI64 record""" + + # see: rfc7043.txt + + byte_len = 8 # 0123456789abcdef (in hex) + text_len = byte_len * 3 - 1 # 01-23-45-67-89-ab-cd-ef diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/GPOS.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/GPOS.py new file mode 100644 index 0000000..d79f4a0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/GPOS.py @@ -0,0 +1,126 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.exception +import dns.immutable +import dns.rdata +import dns.tokenizer + + +def _validate_float_string(what): + if len(what) == 0: + raise dns.exception.FormError + if what[0] == b"-"[0] or what[0] == b"+"[0]: + what = what[1:] + if what.isdigit(): + return + try: + (left, right) = what.split(b".") + except ValueError: + raise dns.exception.FormError + if left == b"" and right == b"": + raise dns.exception.FormError + if not left == b"" and not left.decode().isdigit(): + raise dns.exception.FormError + if not right == b"" and not right.decode().isdigit(): + raise dns.exception.FormError + + +@dns.immutable.immutable +class GPOS(dns.rdata.Rdata): + """GPOS record""" + + # see: RFC 1712 + + __slots__ = ["latitude", "longitude", "altitude"] + + def __init__(self, rdclass, rdtype, latitude, longitude, altitude): + super().__init__(rdclass, rdtype) + if isinstance(latitude, float) or isinstance(latitude, int): + latitude = str(latitude) + if isinstance(longitude, float) or isinstance(longitude, int): + longitude = str(longitude) + if isinstance(altitude, float) or isinstance(altitude, int): + altitude = str(altitude) + latitude = self._as_bytes(latitude, True, 255) + longitude = self._as_bytes(longitude, True, 255) + altitude = self._as_bytes(altitude, True, 255) + _validate_float_string(latitude) + _validate_float_string(longitude) + _validate_float_string(altitude) + self.latitude = latitude + self.longitude = longitude + self.altitude = altitude + flat = self.float_latitude + if flat < -90.0 or flat > 90.0: + raise dns.exception.FormError("bad latitude") + flong = self.float_longitude + if flong < -180.0 or flong > 180.0: + raise dns.exception.FormError("bad longitude") + + def to_text(self, origin=None, relativize=True, **kw): + return ( + f"{self.latitude.decode()} {self.longitude.decode()} " + f"{self.altitude.decode()}" + ) + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + latitude = tok.get_string() + longitude = tok.get_string() + altitude = tok.get_string() + return cls(rdclass, rdtype, latitude, longitude, altitude) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + l = len(self.latitude) + assert l < 256 + file.write(struct.pack("!B", l)) + file.write(self.latitude) + l = len(self.longitude) + assert l < 256 + file.write(struct.pack("!B", l)) + file.write(self.longitude) + l = len(self.altitude) + assert l < 256 + file.write(struct.pack("!B", l)) + file.write(self.altitude) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + latitude = parser.get_counted_bytes() + longitude = parser.get_counted_bytes() + altitude = parser.get_counted_bytes() + return cls(rdclass, rdtype, latitude, longitude, altitude) + + @property + def float_latitude(self): + "latitude as a floating point value" + return float(self.latitude) + + @property + def float_longitude(self): + "longitude as a floating point value" + return float(self.longitude) + + @property + def float_altitude(self): + "altitude as a floating point value" + return float(self.altitude) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/HINFO.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/HINFO.py new file mode 100644 index 0000000..06ad348 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/HINFO.py @@ -0,0 +1,64 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.exception +import dns.immutable +import dns.rdata +import dns.tokenizer + + +@dns.immutable.immutable +class HINFO(dns.rdata.Rdata): + """HINFO record""" + + # see: RFC 1035 + + __slots__ = ["cpu", "os"] + + def __init__(self, rdclass, rdtype, cpu, os): + super().__init__(rdclass, rdtype) + self.cpu = self._as_bytes(cpu, True, 255) + self.os = self._as_bytes(os, True, 255) + + def to_text(self, origin=None, relativize=True, **kw): + return f'"{dns.rdata._escapify(self.cpu)}" "{dns.rdata._escapify(self.os)}"' + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + cpu = tok.get_string(max_length=255) + os = tok.get_string(max_length=255) + return cls(rdclass, rdtype, cpu, os) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + l = len(self.cpu) + assert l < 256 + file.write(struct.pack("!B", l)) + file.write(self.cpu) + l = len(self.os) + assert l < 256 + file.write(struct.pack("!B", l)) + file.write(self.os) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + cpu = parser.get_counted_bytes() + os = parser.get_counted_bytes() + return cls(rdclass, rdtype, cpu, os) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/HIP.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/HIP.py new file mode 100644 index 0000000..dc7948a --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/HIP.py @@ -0,0 +1,85 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2010, 2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import base64 +import binascii +import struct + +import dns.exception +import dns.immutable +import dns.rdata +import dns.rdatatype + + +@dns.immutable.immutable +class HIP(dns.rdata.Rdata): + """HIP record""" + + # see: RFC 5205 + + __slots__ = ["hit", "algorithm", "key", "servers"] + + def __init__(self, rdclass, rdtype, hit, algorithm, key, servers): + super().__init__(rdclass, rdtype) + self.hit = self._as_bytes(hit, True, 255) + self.algorithm = self._as_uint8(algorithm) + self.key = self._as_bytes(key, True) + self.servers = self._as_tuple(servers, self._as_name) + + def to_text(self, origin=None, relativize=True, **kw): + hit = binascii.hexlify(self.hit).decode() + key = base64.b64encode(self.key).replace(b"\n", b"").decode() + text = "" + servers = [] + for server in self.servers: + servers.append(server.choose_relativity(origin, relativize)) + if len(servers) > 0: + text += " " + " ".join(x.to_unicode() for x in servers) + return f"{self.algorithm} {hit} {key}{text}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + algorithm = tok.get_uint8() + hit = binascii.unhexlify(tok.get_string().encode()) + key = base64.b64decode(tok.get_string().encode()) + servers = [] + for token in tok.get_remaining(): + server = tok.as_name(token, origin, relativize, relativize_to) + servers.append(server) + return cls(rdclass, rdtype, hit, algorithm, key, servers) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + lh = len(self.hit) + lk = len(self.key) + file.write(struct.pack("!BBH", lh, self.algorithm, lk)) + file.write(self.hit) + file.write(self.key) + for server in self.servers: + server.to_wire(file, None, origin, False) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + (lh, algorithm, lk) = parser.get_struct("!BBH") + hit = parser.get_bytes(lh) + key = parser.get_bytes(lk) + servers = [] + while parser.remaining() > 0: + server = parser.get_name(origin) + servers.append(server) + return cls(rdclass, rdtype, hit, algorithm, key, servers) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/ISDN.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/ISDN.py new file mode 100644 index 0000000..6428a0a --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/ISDN.py @@ -0,0 +1,78 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.exception +import dns.immutable +import dns.rdata +import dns.tokenizer + + +@dns.immutable.immutable +class ISDN(dns.rdata.Rdata): + """ISDN record""" + + # see: RFC 1183 + + __slots__ = ["address", "subaddress"] + + def __init__(self, rdclass, rdtype, address, subaddress): + super().__init__(rdclass, rdtype) + self.address = self._as_bytes(address, True, 255) + self.subaddress = self._as_bytes(subaddress, True, 255) + + def to_text(self, origin=None, relativize=True, **kw): + if self.subaddress: + return ( + f'"{dns.rdata._escapify(self.address)}" ' + f'"{dns.rdata._escapify(self.subaddress)}"' + ) + else: + return f'"{dns.rdata._escapify(self.address)}"' + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + address = tok.get_string() + tokens = tok.get_remaining(max_tokens=1) + if len(tokens) >= 1: + subaddress = tokens[0].unescape().value + else: + subaddress = "" + return cls(rdclass, rdtype, address, subaddress) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + l = len(self.address) + assert l < 256 + file.write(struct.pack("!B", l)) + file.write(self.address) + l = len(self.subaddress) + if l > 0: + assert l < 256 + file.write(struct.pack("!B", l)) + file.write(self.subaddress) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + address = parser.get_counted_bytes() + if parser.remaining() > 0: + subaddress = parser.get_counted_bytes() + else: + subaddress = b"" + return cls(rdclass, rdtype, address, subaddress) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/L32.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/L32.py new file mode 100644 index 0000000..f51e5c7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/L32.py @@ -0,0 +1,42 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import struct + +import dns.immutable +import dns.ipv4 +import dns.rdata + + +@dns.immutable.immutable +class L32(dns.rdata.Rdata): + """L32 record""" + + # see: rfc6742.txt + + __slots__ = ["preference", "locator32"] + + def __init__(self, rdclass, rdtype, preference, locator32): + super().__init__(rdclass, rdtype) + self.preference = self._as_uint16(preference) + self.locator32 = self._as_ipv4_address(locator32) + + def to_text(self, origin=None, relativize=True, **kw): + return f"{self.preference} {self.locator32}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + preference = tok.get_uint16() + nodeid = tok.get_identifier() + return cls(rdclass, rdtype, preference, nodeid) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(struct.pack("!H", self.preference)) + file.write(dns.ipv4.inet_aton(self.locator32)) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + preference = parser.get_uint16() + locator32 = parser.get_remaining() + return cls(rdclass, rdtype, preference, locator32) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/L64.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/L64.py new file mode 100644 index 0000000..a47da19 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/L64.py @@ -0,0 +1,48 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import struct + +import dns.immutable +import dns.rdata +import dns.rdtypes.util + + +@dns.immutable.immutable +class L64(dns.rdata.Rdata): + """L64 record""" + + # see: rfc6742.txt + + __slots__ = ["preference", "locator64"] + + def __init__(self, rdclass, rdtype, preference, locator64): + super().__init__(rdclass, rdtype) + self.preference = self._as_uint16(preference) + if isinstance(locator64, bytes): + if len(locator64) != 8: + raise ValueError("invalid locator64") + self.locator64 = dns.rdata._hexify(locator64, 4, b":") + else: + dns.rdtypes.util.parse_formatted_hex(locator64, 4, 4, ":") + self.locator64 = locator64 + + def to_text(self, origin=None, relativize=True, **kw): + return f"{self.preference} {self.locator64}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + preference = tok.get_uint16() + locator64 = tok.get_identifier() + return cls(rdclass, rdtype, preference, locator64) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(struct.pack("!H", self.preference)) + file.write(dns.rdtypes.util.parse_formatted_hex(self.locator64, 4, 4, ":")) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + preference = parser.get_uint16() + locator64 = parser.get_remaining() + return cls(rdclass, rdtype, preference, locator64) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/LOC.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/LOC.py new file mode 100644 index 0000000..6c7fe5e --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/LOC.py @@ -0,0 +1,347 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.exception +import dns.immutable +import dns.rdata + +_pows = tuple(10**i for i in range(0, 11)) + +# default values are in centimeters +_default_size = 100.0 +_default_hprec = 1000000.0 +_default_vprec = 1000.0 + +# for use by from_wire() +_MAX_LATITUDE = 0x80000000 + 90 * 3600000 +_MIN_LATITUDE = 0x80000000 - 90 * 3600000 +_MAX_LONGITUDE = 0x80000000 + 180 * 3600000 +_MIN_LONGITUDE = 0x80000000 - 180 * 3600000 + + +def _exponent_of(what, desc): + if what == 0: + return 0 + exp = None + for i, pow in enumerate(_pows): + if what < pow: + exp = i - 1 + break + if exp is None or exp < 0: + raise dns.exception.SyntaxError(f"{desc} value out of bounds") + return exp + + +def _float_to_tuple(what): + if what < 0: + sign = -1 + what *= -1 + else: + sign = 1 + what = round(what * 3600000) + degrees = int(what // 3600000) + what -= degrees * 3600000 + minutes = int(what // 60000) + what -= minutes * 60000 + seconds = int(what // 1000) + what -= int(seconds * 1000) + what = int(what) + return (degrees, minutes, seconds, what, sign) + + +def _tuple_to_float(what): + value = float(what[0]) + value += float(what[1]) / 60.0 + value += float(what[2]) / 3600.0 + value += float(what[3]) / 3600000.0 + return float(what[4]) * value + + +def _encode_size(what, desc): + what = int(what) + exponent = _exponent_of(what, desc) & 0xF + base = what // pow(10, exponent) & 0xF + return base * 16 + exponent + + +def _decode_size(what, desc): + exponent = what & 0x0F + if exponent > 9: + raise dns.exception.FormError(f"bad {desc} exponent") + base = (what & 0xF0) >> 4 + if base > 9: + raise dns.exception.FormError(f"bad {desc} base") + return base * pow(10, exponent) + + +def _check_coordinate_list(value, low, high): + if value[0] < low or value[0] > high: + raise ValueError(f"not in range [{low}, {high}]") + if value[1] < 0 or value[1] > 59: + raise ValueError("bad minutes value") + if value[2] < 0 or value[2] > 59: + raise ValueError("bad seconds value") + if value[3] < 0 or value[3] > 999: + raise ValueError("bad milliseconds value") + if value[4] != 1 and value[4] != -1: + raise ValueError("bad hemisphere value") + + +@dns.immutable.immutable +class LOC(dns.rdata.Rdata): + """LOC record""" + + # see: RFC 1876 + + __slots__ = [ + "latitude", + "longitude", + "altitude", + "size", + "horizontal_precision", + "vertical_precision", + ] + + def __init__( + self, + rdclass, + rdtype, + latitude, + longitude, + altitude, + size=_default_size, + hprec=_default_hprec, + vprec=_default_vprec, + ): + """Initialize a LOC record instance. + + The parameters I{latitude} and I{longitude} may be either a 4-tuple + of integers specifying (degrees, minutes, seconds, milliseconds), + or they may be floating point values specifying the number of + degrees. The other parameters are floats. Size, horizontal precision, + and vertical precision are specified in centimeters.""" + + super().__init__(rdclass, rdtype) + if isinstance(latitude, int): + latitude = float(latitude) + if isinstance(latitude, float): + latitude = _float_to_tuple(latitude) + _check_coordinate_list(latitude, -90, 90) + self.latitude = tuple(latitude) # pyright: ignore + if isinstance(longitude, int): + longitude = float(longitude) + if isinstance(longitude, float): + longitude = _float_to_tuple(longitude) + _check_coordinate_list(longitude, -180, 180) + self.longitude = tuple(longitude) # pyright: ignore + self.altitude = float(altitude) + self.size = float(size) + self.horizontal_precision = float(hprec) + self.vertical_precision = float(vprec) + + def to_text(self, origin=None, relativize=True, **kw): + if self.latitude[4] > 0: + lat_hemisphere = "N" + else: + lat_hemisphere = "S" + if self.longitude[4] > 0: + long_hemisphere = "E" + else: + long_hemisphere = "W" + text = ( + f"{self.latitude[0]} {self.latitude[1]} " + f"{self.latitude[2]}.{self.latitude[3]:03d} {lat_hemisphere} " + f"{self.longitude[0]} {self.longitude[1]} " + f"{self.longitude[2]}.{self.longitude[3]:03d} {long_hemisphere} " + f"{(self.altitude / 100.0):0.2f}m" + ) + + # do not print default values + if ( + self.size != _default_size + or self.horizontal_precision != _default_hprec + or self.vertical_precision != _default_vprec + ): + text += ( + f" {self.size / 100.0:0.2f}m {self.horizontal_precision / 100.0:0.2f}m" + f" {self.vertical_precision / 100.0:0.2f}m" + ) + return text + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + latitude = [0, 0, 0, 0, 1] + longitude = [0, 0, 0, 0, 1] + size = _default_size + hprec = _default_hprec + vprec = _default_vprec + + latitude[0] = tok.get_int() + t = tok.get_string() + if t.isdigit(): + latitude[1] = int(t) + t = tok.get_string() + if "." in t: + (seconds, milliseconds) = t.split(".") + if not seconds.isdigit(): + raise dns.exception.SyntaxError("bad latitude seconds value") + latitude[2] = int(seconds) + l = len(milliseconds) + if l == 0 or l > 3 or not milliseconds.isdigit(): + raise dns.exception.SyntaxError("bad latitude milliseconds value") + if l == 1: + m = 100 + elif l == 2: + m = 10 + else: + m = 1 + latitude[3] = m * int(milliseconds) + t = tok.get_string() + elif t.isdigit(): + latitude[2] = int(t) + t = tok.get_string() + if t == "S": + latitude[4] = -1 + elif t != "N": + raise dns.exception.SyntaxError("bad latitude hemisphere value") + + longitude[0] = tok.get_int() + t = tok.get_string() + if t.isdigit(): + longitude[1] = int(t) + t = tok.get_string() + if "." in t: + (seconds, milliseconds) = t.split(".") + if not seconds.isdigit(): + raise dns.exception.SyntaxError("bad longitude seconds value") + longitude[2] = int(seconds) + l = len(milliseconds) + if l == 0 or l > 3 or not milliseconds.isdigit(): + raise dns.exception.SyntaxError("bad longitude milliseconds value") + if l == 1: + m = 100 + elif l == 2: + m = 10 + else: + m = 1 + longitude[3] = m * int(milliseconds) + t = tok.get_string() + elif t.isdigit(): + longitude[2] = int(t) + t = tok.get_string() + if t == "W": + longitude[4] = -1 + elif t != "E": + raise dns.exception.SyntaxError("bad longitude hemisphere value") + + t = tok.get_string() + if t[-1] == "m": + t = t[0:-1] + altitude = float(t) * 100.0 # m -> cm + + tokens = tok.get_remaining(max_tokens=3) + if len(tokens) >= 1: + value = tokens[0].unescape().value + if value[-1] == "m": + value = value[0:-1] + size = float(value) * 100.0 # m -> cm + if len(tokens) >= 2: + value = tokens[1].unescape().value + if value[-1] == "m": + value = value[0:-1] + hprec = float(value) * 100.0 # m -> cm + if len(tokens) >= 3: + value = tokens[2].unescape().value + if value[-1] == "m": + value = value[0:-1] + vprec = float(value) * 100.0 # m -> cm + + # Try encoding these now so we raise if they are bad + _encode_size(size, "size") + _encode_size(hprec, "horizontal precision") + _encode_size(vprec, "vertical precision") + + return cls(rdclass, rdtype, latitude, longitude, altitude, size, hprec, vprec) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + milliseconds = ( + self.latitude[0] * 3600000 + + self.latitude[1] * 60000 + + self.latitude[2] * 1000 + + self.latitude[3] + ) * self.latitude[4] + latitude = 0x80000000 + milliseconds + milliseconds = ( + self.longitude[0] * 3600000 + + self.longitude[1] * 60000 + + self.longitude[2] * 1000 + + self.longitude[3] + ) * self.longitude[4] + longitude = 0x80000000 + milliseconds + altitude = int(self.altitude) + 10000000 + size = _encode_size(self.size, "size") + hprec = _encode_size(self.horizontal_precision, "horizontal precision") + vprec = _encode_size(self.vertical_precision, "vertical precision") + wire = struct.pack( + "!BBBBIII", 0, size, hprec, vprec, latitude, longitude, altitude + ) + file.write(wire) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + ( + version, + size, + hprec, + vprec, + latitude, + longitude, + altitude, + ) = parser.get_struct("!BBBBIII") + if version != 0: + raise dns.exception.FormError("LOC version not zero") + if latitude < _MIN_LATITUDE or latitude > _MAX_LATITUDE: + raise dns.exception.FormError("bad latitude") + if latitude > 0x80000000: + latitude = (latitude - 0x80000000) / 3600000 + else: + latitude = -1 * (0x80000000 - latitude) / 3600000 + if longitude < _MIN_LONGITUDE or longitude > _MAX_LONGITUDE: + raise dns.exception.FormError("bad longitude") + if longitude > 0x80000000: + longitude = (longitude - 0x80000000) / 3600000 + else: + longitude = -1 * (0x80000000 - longitude) / 3600000 + altitude = float(altitude) - 10000000.0 + size = _decode_size(size, "size") + hprec = _decode_size(hprec, "horizontal precision") + vprec = _decode_size(vprec, "vertical precision") + return cls(rdclass, rdtype, latitude, longitude, altitude, size, hprec, vprec) + + @property + def float_latitude(self): + "latitude as a floating point value" + return _tuple_to_float(self.latitude) + + @property + def float_longitude(self): + "longitude as a floating point value" + return _tuple_to_float(self.longitude) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/LP.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/LP.py new file mode 100644 index 0000000..379c862 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/LP.py @@ -0,0 +1,42 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import struct + +import dns.immutable +import dns.rdata + + +@dns.immutable.immutable +class LP(dns.rdata.Rdata): + """LP record""" + + # see: rfc6742.txt + + __slots__ = ["preference", "fqdn"] + + def __init__(self, rdclass, rdtype, preference, fqdn): + super().__init__(rdclass, rdtype) + self.preference = self._as_uint16(preference) + self.fqdn = self._as_name(fqdn) + + def to_text(self, origin=None, relativize=True, **kw): + fqdn = self.fqdn.choose_relativity(origin, relativize) + return f"{self.preference} {fqdn}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + preference = tok.get_uint16() + fqdn = tok.get_name(origin, relativize, relativize_to) + return cls(rdclass, rdtype, preference, fqdn) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(struct.pack("!H", self.preference)) + self.fqdn.to_wire(file, compress, origin, canonicalize) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + preference = parser.get_uint16() + fqdn = parser.get_name(origin) + return cls(rdclass, rdtype, preference, fqdn) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/MX.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/MX.py new file mode 100644 index 0000000..0c300c5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/MX.py @@ -0,0 +1,24 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.mxbase + + +@dns.immutable.immutable +class MX(dns.rdtypes.mxbase.MXBase): + """MX record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NID.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NID.py new file mode 100644 index 0000000..fa0dad5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NID.py @@ -0,0 +1,48 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import struct + +import dns.immutable +import dns.rdata +import dns.rdtypes.util + + +@dns.immutable.immutable +class NID(dns.rdata.Rdata): + """NID record""" + + # see: rfc6742.txt + + __slots__ = ["preference", "nodeid"] + + def __init__(self, rdclass, rdtype, preference, nodeid): + super().__init__(rdclass, rdtype) + self.preference = self._as_uint16(preference) + if isinstance(nodeid, bytes): + if len(nodeid) != 8: + raise ValueError("invalid nodeid") + self.nodeid = dns.rdata._hexify(nodeid, 4, b":") + else: + dns.rdtypes.util.parse_formatted_hex(nodeid, 4, 4, ":") + self.nodeid = nodeid + + def to_text(self, origin=None, relativize=True, **kw): + return f"{self.preference} {self.nodeid}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + preference = tok.get_uint16() + nodeid = tok.get_identifier() + return cls(rdclass, rdtype, preference, nodeid) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(struct.pack("!H", self.preference)) + file.write(dns.rdtypes.util.parse_formatted_hex(self.nodeid, 4, 4, ":")) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + preference = parser.get_uint16() + nodeid = parser.get_remaining() + return cls(rdclass, rdtype, preference, nodeid) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NINFO.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NINFO.py new file mode 100644 index 0000000..b177bdd --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NINFO.py @@ -0,0 +1,26 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.txtbase + + +@dns.immutable.immutable +class NINFO(dns.rdtypes.txtbase.TXTBase): + """NINFO record""" + + # see: draft-reid-dnsext-zs-01 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NS.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NS.py new file mode 100644 index 0000000..c3f34ce --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NS.py @@ -0,0 +1,24 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.nsbase + + +@dns.immutable.immutable +class NS(dns.rdtypes.nsbase.NSBase): + """NS record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NSEC.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NSEC.py new file mode 100644 index 0000000..3c78b72 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NSEC.py @@ -0,0 +1,67 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.exception +import dns.immutable +import dns.name +import dns.rdata +import dns.rdatatype +import dns.rdtypes.util + + +@dns.immutable.immutable +class Bitmap(dns.rdtypes.util.Bitmap): + type_name = "NSEC" + + +@dns.immutable.immutable +class NSEC(dns.rdata.Rdata): + """NSEC record""" + + __slots__ = ["next", "windows"] + + def __init__(self, rdclass, rdtype, next, windows): + super().__init__(rdclass, rdtype) + self.next = self._as_name(next) + if not isinstance(windows, Bitmap): + windows = Bitmap(windows) + self.windows = tuple(windows.windows) + + def to_text(self, origin=None, relativize=True, **kw): + next = self.next.choose_relativity(origin, relativize) + text = Bitmap(self.windows).to_text() + return f"{next}{text}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + next = tok.get_name(origin, relativize, relativize_to) + windows = Bitmap.from_text(tok) + return cls(rdclass, rdtype, next, windows) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + # Note that NSEC downcasing, originally mandated by RFC 4034 + # section 6.2 was removed by RFC 6840 section 5.1. + self.next.to_wire(file, None, origin, False) + Bitmap(self.windows).to_wire(file) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + next = parser.get_name(origin) + bitmap = Bitmap.from_wire_parser(parser) + return cls(rdclass, rdtype, next, bitmap) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NSEC3.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NSEC3.py new file mode 100644 index 0000000..6899418 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NSEC3.py @@ -0,0 +1,120 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2004-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import base64 +import binascii +import struct + +import dns.exception +import dns.immutable +import dns.name +import dns.rdata +import dns.rdatatype +import dns.rdtypes.util + +b32_hex_to_normal = bytes.maketrans( + b"0123456789ABCDEFGHIJKLMNOPQRSTUV", b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" +) +b32_normal_to_hex = bytes.maketrans( + b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", b"0123456789ABCDEFGHIJKLMNOPQRSTUV" +) + +# hash algorithm constants +SHA1 = 1 + +# flag constants +OPTOUT = 1 + + +@dns.immutable.immutable +class Bitmap(dns.rdtypes.util.Bitmap): + type_name = "NSEC3" + + +@dns.immutable.immutable +class NSEC3(dns.rdata.Rdata): + """NSEC3 record""" + + __slots__ = ["algorithm", "flags", "iterations", "salt", "next", "windows"] + + def __init__( + self, rdclass, rdtype, algorithm, flags, iterations, salt, next, windows + ): + super().__init__(rdclass, rdtype) + self.algorithm = self._as_uint8(algorithm) + self.flags = self._as_uint8(flags) + self.iterations = self._as_uint16(iterations) + self.salt = self._as_bytes(salt, True, 255) + self.next = self._as_bytes(next, True, 255) + if not isinstance(windows, Bitmap): + windows = Bitmap(windows) + self.windows = tuple(windows.windows) + + def _next_text(self): + next = base64.b32encode(self.next).translate(b32_normal_to_hex).lower().decode() + next = next.rstrip("=") + return next + + def to_text(self, origin=None, relativize=True, **kw): + next = self._next_text() + if self.salt == b"": + salt = "-" + else: + salt = binascii.hexlify(self.salt).decode() + text = Bitmap(self.windows).to_text() + return f"{self.algorithm} {self.flags} {self.iterations} {salt} {next}{text}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + algorithm = tok.get_uint8() + flags = tok.get_uint8() + iterations = tok.get_uint16() + salt = tok.get_string() + if salt == "-": + salt = b"" + else: + salt = binascii.unhexlify(salt.encode("ascii")) + next = tok.get_string().encode("ascii").upper().translate(b32_hex_to_normal) + if next.endswith(b"="): + raise binascii.Error("Incorrect padding") + if len(next) % 8 != 0: + next += b"=" * (8 - len(next) % 8) + next = base64.b32decode(next) + bitmap = Bitmap.from_text(tok) + return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, bitmap) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + l = len(self.salt) + file.write(struct.pack("!BBHB", self.algorithm, self.flags, self.iterations, l)) + file.write(self.salt) + l = len(self.next) + file.write(struct.pack("!B", l)) + file.write(self.next) + Bitmap(self.windows).to_wire(file) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + (algorithm, flags, iterations) = parser.get_struct("!BBH") + salt = parser.get_counted_bytes() + next = parser.get_counted_bytes() + bitmap = Bitmap.from_wire_parser(parser) + return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, bitmap) + + def next_name(self, origin=None): + return dns.name.from_text(self._next_text(), origin) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NSEC3PARAM.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NSEC3PARAM.py new file mode 100644 index 0000000..e867872 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/NSEC3PARAM.py @@ -0,0 +1,69 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import binascii +import struct + +import dns.exception +import dns.immutable +import dns.rdata + + +@dns.immutable.immutable +class NSEC3PARAM(dns.rdata.Rdata): + """NSEC3PARAM record""" + + __slots__ = ["algorithm", "flags", "iterations", "salt"] + + def __init__(self, rdclass, rdtype, algorithm, flags, iterations, salt): + super().__init__(rdclass, rdtype) + self.algorithm = self._as_uint8(algorithm) + self.flags = self._as_uint8(flags) + self.iterations = self._as_uint16(iterations) + self.salt = self._as_bytes(salt, True, 255) + + def to_text(self, origin=None, relativize=True, **kw): + if self.salt == b"": + salt = "-" + else: + salt = binascii.hexlify(self.salt).decode() + return f"{self.algorithm} {self.flags} {self.iterations} {salt}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + algorithm = tok.get_uint8() + flags = tok.get_uint8() + iterations = tok.get_uint16() + salt = tok.get_string() + if salt == "-": + salt = "" + else: + salt = binascii.unhexlify(salt.encode()) + return cls(rdclass, rdtype, algorithm, flags, iterations, salt) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + l = len(self.salt) + file.write(struct.pack("!BBHB", self.algorithm, self.flags, self.iterations, l)) + file.write(self.salt) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + (algorithm, flags, iterations) = parser.get_struct("!BBH") + salt = parser.get_counted_bytes() + return cls(rdclass, rdtype, algorithm, flags, iterations, salt) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/OPENPGPKEY.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/OPENPGPKEY.py new file mode 100644 index 0000000..ac1841c --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/OPENPGPKEY.py @@ -0,0 +1,53 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2016 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import base64 + +import dns.exception +import dns.immutable +import dns.rdata +import dns.tokenizer + + +@dns.immutable.immutable +class OPENPGPKEY(dns.rdata.Rdata): + """OPENPGPKEY record""" + + # see: RFC 7929 + + def __init__(self, rdclass, rdtype, key): + super().__init__(rdclass, rdtype) + self.key = self._as_bytes(key) + + def to_text(self, origin=None, relativize=True, **kw): + return dns.rdata._base64ify(self.key, chunksize=None, **kw) # pyright: ignore + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + b64 = tok.concatenate_remaining_identifiers().encode() + key = base64.b64decode(b64) + return cls(rdclass, rdtype, key) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(self.key) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + key = parser.get_remaining() + return cls(rdclass, rdtype, key) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/OPT.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/OPT.py new file mode 100644 index 0000000..d343dfa --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/OPT.py @@ -0,0 +1,77 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.edns +import dns.exception +import dns.immutable +import dns.rdata + +# We don't implement from_text, and that's ok. +# pylint: disable=abstract-method + + +@dns.immutable.immutable +class OPT(dns.rdata.Rdata): + """OPT record""" + + __slots__ = ["options"] + + def __init__(self, rdclass, rdtype, options): + """Initialize an OPT rdata. + + *rdclass*, an ``int`` is the rdataclass of the Rdata, + which is also the payload size. + + *rdtype*, an ``int`` is the rdatatype of the Rdata. + + *options*, a tuple of ``bytes`` + """ + + super().__init__(rdclass, rdtype) + + def as_option(option): + if not isinstance(option, dns.edns.Option): + raise ValueError("option is not a dns.edns.option") + return option + + self.options = self._as_tuple(options, as_option) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + for opt in self.options: + owire = opt.to_wire() + file.write(struct.pack("!HH", opt.otype, len(owire))) + file.write(owire) + + def to_text(self, origin=None, relativize=True, **kw): + return " ".join(opt.to_text() for opt in self.options) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + options = [] + while parser.remaining() > 0: + (otype, olen) = parser.get_struct("!HH") + with parser.restrict_to(olen): + opt = dns.edns.option_from_wire_parser(otype, parser) + options.append(opt) + return cls(rdclass, rdtype, options) + + @property + def payload(self): + "payload size" + return self.rdclass diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/PTR.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/PTR.py new file mode 100644 index 0000000..98c3616 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/PTR.py @@ -0,0 +1,24 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.nsbase + + +@dns.immutable.immutable +class PTR(dns.rdtypes.nsbase.NSBase): + """PTR record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RESINFO.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RESINFO.py new file mode 100644 index 0000000..76c8ea2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RESINFO.py @@ -0,0 +1,24 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.txtbase + + +@dns.immutable.immutable +class RESINFO(dns.rdtypes.txtbase.TXTBase): + """RESINFO record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RP.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RP.py new file mode 100644 index 0000000..a66cfc5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RP.py @@ -0,0 +1,58 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.exception +import dns.immutable +import dns.name +import dns.rdata + + +@dns.immutable.immutable +class RP(dns.rdata.Rdata): + """RP record""" + + # see: RFC 1183 + + __slots__ = ["mbox", "txt"] + + def __init__(self, rdclass, rdtype, mbox, txt): + super().__init__(rdclass, rdtype) + self.mbox = self._as_name(mbox) + self.txt = self._as_name(txt) + + def to_text(self, origin=None, relativize=True, **kw): + mbox = self.mbox.choose_relativity(origin, relativize) + txt = self.txt.choose_relativity(origin, relativize) + return f"{str(mbox)} {str(txt)}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + mbox = tok.get_name(origin, relativize, relativize_to) + txt = tok.get_name(origin, relativize, relativize_to) + return cls(rdclass, rdtype, mbox, txt) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + self.mbox.to_wire(file, None, origin, canonicalize) + self.txt.to_wire(file, None, origin, canonicalize) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + mbox = parser.get_name(origin) + txt = parser.get_name(origin) + return cls(rdclass, rdtype, mbox, txt) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RRSIG.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RRSIG.py new file mode 100644 index 0000000..5556cba --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RRSIG.py @@ -0,0 +1,155 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import base64 +import calendar +import struct +import time + +import dns.dnssectypes +import dns.exception +import dns.immutable +import dns.rdata +import dns.rdatatype + + +class BadSigTime(dns.exception.DNSException): + """Time in DNS SIG or RRSIG resource record cannot be parsed.""" + + +def sigtime_to_posixtime(what): + if len(what) <= 10 and what.isdigit(): + return int(what) + if len(what) != 14: + raise BadSigTime + year = int(what[0:4]) + month = int(what[4:6]) + day = int(what[6:8]) + hour = int(what[8:10]) + minute = int(what[10:12]) + second = int(what[12:14]) + return calendar.timegm((year, month, day, hour, minute, second, 0, 0, 0)) + + +def posixtime_to_sigtime(what): + return time.strftime("%Y%m%d%H%M%S", time.gmtime(what)) + + +@dns.immutable.immutable +class RRSIG(dns.rdata.Rdata): + """RRSIG record""" + + __slots__ = [ + "type_covered", + "algorithm", + "labels", + "original_ttl", + "expiration", + "inception", + "key_tag", + "signer", + "signature", + ] + + def __init__( + self, + rdclass, + rdtype, + type_covered, + algorithm, + labels, + original_ttl, + expiration, + inception, + key_tag, + signer, + signature, + ): + super().__init__(rdclass, rdtype) + self.type_covered = self._as_rdatatype(type_covered) + self.algorithm = dns.dnssectypes.Algorithm.make(algorithm) + self.labels = self._as_uint8(labels) + self.original_ttl = self._as_ttl(original_ttl) + self.expiration = self._as_uint32(expiration) + self.inception = self._as_uint32(inception) + self.key_tag = self._as_uint16(key_tag) + self.signer = self._as_name(signer) + self.signature = self._as_bytes(signature) + + def covers(self): + return self.type_covered + + def to_text(self, origin=None, relativize=True, **kw): + return ( + f"{dns.rdatatype.to_text(self.type_covered)} " + f"{self.algorithm} {self.labels} {self.original_ttl} " + f"{posixtime_to_sigtime(self.expiration)} " + f"{posixtime_to_sigtime(self.inception)} " + f"{self.key_tag} " + f"{self.signer.choose_relativity(origin, relativize)} " + f"{dns.rdata._base64ify(self.signature, **kw)}" # pyright: ignore + ) + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + type_covered = dns.rdatatype.from_text(tok.get_string()) + algorithm = dns.dnssectypes.Algorithm.from_text(tok.get_string()) + labels = tok.get_int() + original_ttl = tok.get_ttl() + expiration = sigtime_to_posixtime(tok.get_string()) + inception = sigtime_to_posixtime(tok.get_string()) + key_tag = tok.get_int() + signer = tok.get_name(origin, relativize, relativize_to) + b64 = tok.concatenate_remaining_identifiers().encode() + signature = base64.b64decode(b64) + return cls( + rdclass, + rdtype, + type_covered, + algorithm, + labels, + original_ttl, + expiration, + inception, + key_tag, + signer, + signature, + ) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + header = struct.pack( + "!HBBIIIH", + self.type_covered, + self.algorithm, + self.labels, + self.original_ttl, + self.expiration, + self.inception, + self.key_tag, + ) + file.write(header) + self.signer.to_wire(file, None, origin, canonicalize) + file.write(self.signature) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + header = parser.get_struct("!HBBIIIH") + signer = parser.get_name(origin) + signature = parser.get_remaining() + return cls(rdclass, rdtype, *header, signer, signature) # pyright: ignore diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RT.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RT.py new file mode 100644 index 0000000..5a4d45c --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/RT.py @@ -0,0 +1,24 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.mxbase + + +@dns.immutable.immutable +class RT(dns.rdtypes.mxbase.UncompressedDowncasingMX): + """RT record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SMIMEA.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SMIMEA.py new file mode 100644 index 0000000..55d87bf --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SMIMEA.py @@ -0,0 +1,9 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import dns.immutable +import dns.rdtypes.tlsabase + + +@dns.immutable.immutable +class SMIMEA(dns.rdtypes.tlsabase.TLSABase): + """SMIMEA record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SOA.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SOA.py new file mode 100644 index 0000000..3c7cd8c --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SOA.py @@ -0,0 +1,78 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.exception +import dns.immutable +import dns.name +import dns.rdata + + +@dns.immutable.immutable +class SOA(dns.rdata.Rdata): + """SOA record""" + + # see: RFC 1035 + + __slots__ = ["mname", "rname", "serial", "refresh", "retry", "expire", "minimum"] + + def __init__( + self, rdclass, rdtype, mname, rname, serial, refresh, retry, expire, minimum + ): + super().__init__(rdclass, rdtype) + self.mname = self._as_name(mname) + self.rname = self._as_name(rname) + self.serial = self._as_uint32(serial) + self.refresh = self._as_ttl(refresh) + self.retry = self._as_ttl(retry) + self.expire = self._as_ttl(expire) + self.minimum = self._as_ttl(minimum) + + def to_text(self, origin=None, relativize=True, **kw): + mname = self.mname.choose_relativity(origin, relativize) + rname = self.rname.choose_relativity(origin, relativize) + return f"{mname} {rname} {self.serial} {self.refresh} {self.retry} {self.expire} {self.minimum}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + mname = tok.get_name(origin, relativize, relativize_to) + rname = tok.get_name(origin, relativize, relativize_to) + serial = tok.get_uint32() + refresh = tok.get_ttl() + retry = tok.get_ttl() + expire = tok.get_ttl() + minimum = tok.get_ttl() + return cls( + rdclass, rdtype, mname, rname, serial, refresh, retry, expire, minimum + ) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + self.mname.to_wire(file, compress, origin, canonicalize) + self.rname.to_wire(file, compress, origin, canonicalize) + five_ints = struct.pack( + "!IIIII", self.serial, self.refresh, self.retry, self.expire, self.minimum + ) + file.write(five_ints) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + mname = parser.get_name(origin) + rname = parser.get_name(origin) + return cls(rdclass, rdtype, mname, rname, *parser.get_struct("!IIIII")) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SPF.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SPF.py new file mode 100644 index 0000000..1df3b70 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SPF.py @@ -0,0 +1,26 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.txtbase + + +@dns.immutable.immutable +class SPF(dns.rdtypes.txtbase.TXTBase): + """SPF record""" + + # see: RFC 4408 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SSHFP.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SSHFP.py new file mode 100644 index 0000000..3f08f3a --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/SSHFP.py @@ -0,0 +1,67 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2005-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import binascii +import struct + +import dns.immutable +import dns.rdata +import dns.rdatatype + + +@dns.immutable.immutable +class SSHFP(dns.rdata.Rdata): + """SSHFP record""" + + # See RFC 4255 + + __slots__ = ["algorithm", "fp_type", "fingerprint"] + + def __init__(self, rdclass, rdtype, algorithm, fp_type, fingerprint): + super().__init__(rdclass, rdtype) + self.algorithm = self._as_uint8(algorithm) + self.fp_type = self._as_uint8(fp_type) + self.fingerprint = self._as_bytes(fingerprint, True) + + def to_text(self, origin=None, relativize=True, **kw): + kw = kw.copy() + chunksize = kw.pop("chunksize", 128) + fingerprint = dns.rdata._hexify( + self.fingerprint, chunksize=chunksize, **kw # pyright: ignore + ) + return f"{self.algorithm} {self.fp_type} {fingerprint}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + algorithm = tok.get_uint8() + fp_type = tok.get_uint8() + fingerprint = tok.concatenate_remaining_identifiers().encode() + fingerprint = binascii.unhexlify(fingerprint) + return cls(rdclass, rdtype, algorithm, fp_type, fingerprint) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + header = struct.pack("!BB", self.algorithm, self.fp_type) + file.write(header) + file.write(self.fingerprint) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + header = parser.get_struct("BB") + fingerprint = parser.get_remaining() + return cls(rdclass, rdtype, header[0], header[1], fingerprint) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TKEY.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TKEY.py new file mode 100644 index 0000000..f9189b1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TKEY.py @@ -0,0 +1,135 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import base64 +import struct + +import dns.exception +import dns.immutable +import dns.rdata + + +@dns.immutable.immutable +class TKEY(dns.rdata.Rdata): + """TKEY Record""" + + __slots__ = [ + "algorithm", + "inception", + "expiration", + "mode", + "error", + "key", + "other", + ] + + def __init__( + self, + rdclass, + rdtype, + algorithm, + inception, + expiration, + mode, + error, + key, + other=b"", + ): + super().__init__(rdclass, rdtype) + self.algorithm = self._as_name(algorithm) + self.inception = self._as_uint32(inception) + self.expiration = self._as_uint32(expiration) + self.mode = self._as_uint16(mode) + self.error = self._as_uint16(error) + self.key = self._as_bytes(key) + self.other = self._as_bytes(other) + + def to_text(self, origin=None, relativize=True, **kw): + _algorithm = self.algorithm.choose_relativity(origin, relativize) + key = dns.rdata._base64ify(self.key, 0) + other = "" + if len(self.other) > 0: + other = " " + dns.rdata._base64ify(self.other, 0) + return f"{_algorithm} {self.inception} {self.expiration} {self.mode} {self.error} {key}{other}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + algorithm = tok.get_name(relativize=False) + inception = tok.get_uint32() + expiration = tok.get_uint32() + mode = tok.get_uint16() + error = tok.get_uint16() + key_b64 = tok.get_string().encode() + key = base64.b64decode(key_b64) + other_b64 = tok.concatenate_remaining_identifiers(True).encode() + other = base64.b64decode(other_b64) + + return cls( + rdclass, rdtype, algorithm, inception, expiration, mode, error, key, other + ) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + self.algorithm.to_wire(file, compress, origin) + file.write( + struct.pack("!IIHH", self.inception, self.expiration, self.mode, self.error) + ) + file.write(struct.pack("!H", len(self.key))) + file.write(self.key) + file.write(struct.pack("!H", len(self.other))) + if len(self.other) > 0: + file.write(self.other) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + algorithm = parser.get_name(origin) + inception, expiration, mode, error = parser.get_struct("!IIHH") + key = parser.get_counted_bytes(2) + other = parser.get_counted_bytes(2) + + return cls( + rdclass, rdtype, algorithm, inception, expiration, mode, error, key, other + ) + + # Constants for the mode field - from RFC 2930: + # 2.5 The Mode Field + # + # The mode field specifies the general scheme for key agreement or + # the purpose of the TKEY DNS message. Servers and resolvers + # supporting this specification MUST implement the Diffie-Hellman key + # agreement mode and the key deletion mode for queries. All other + # modes are OPTIONAL. A server supporting TKEY that receives a TKEY + # request with a mode it does not support returns the BADMODE error. + # The following values of the Mode octet are defined, available, or + # reserved: + # + # Value Description + # ----- ----------- + # 0 - reserved, see section 7 + # 1 server assignment + # 2 Diffie-Hellman exchange + # 3 GSS-API negotiation + # 4 resolver assignment + # 5 key deletion + # 6-65534 - available, see section 7 + # 65535 - reserved, see section 7 + SERVER_ASSIGNMENT = 1 + DIFFIE_HELLMAN_EXCHANGE = 2 + GSSAPI_NEGOTIATION = 3 + RESOLVER_ASSIGNMENT = 4 + KEY_DELETION = 5 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TLSA.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TLSA.py new file mode 100644 index 0000000..4dffc55 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TLSA.py @@ -0,0 +1,9 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import dns.immutable +import dns.rdtypes.tlsabase + + +@dns.immutable.immutable +class TLSA(dns.rdtypes.tlsabase.TLSABase): + """TLSA record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TSIG.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TSIG.py new file mode 100644 index 0000000..7942382 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TSIG.py @@ -0,0 +1,160 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import base64 +import struct + +import dns.exception +import dns.immutable +import dns.rcode +import dns.rdata + + +@dns.immutable.immutable +class TSIG(dns.rdata.Rdata): + """TSIG record""" + + __slots__ = [ + "algorithm", + "time_signed", + "fudge", + "mac", + "original_id", + "error", + "other", + ] + + def __init__( + self, + rdclass, + rdtype, + algorithm, + time_signed, + fudge, + mac, + original_id, + error, + other, + ): + """Initialize a TSIG rdata. + + *rdclass*, an ``int`` is the rdataclass of the Rdata. + + *rdtype*, an ``int`` is the rdatatype of the Rdata. + + *algorithm*, a ``dns.name.Name``. + + *time_signed*, an ``int``. + + *fudge*, an ``int`. + + *mac*, a ``bytes`` + + *original_id*, an ``int`` + + *error*, an ``int`` + + *other*, a ``bytes`` + """ + + super().__init__(rdclass, rdtype) + self.algorithm = self._as_name(algorithm) + self.time_signed = self._as_uint48(time_signed) + self.fudge = self._as_uint16(fudge) + self.mac = self._as_bytes(mac) + self.original_id = self._as_uint16(original_id) + self.error = dns.rcode.Rcode.make(error) + self.other = self._as_bytes(other) + + def to_text(self, origin=None, relativize=True, **kw): + algorithm = self.algorithm.choose_relativity(origin, relativize) + error = dns.rcode.to_text(self.error, True) + text = ( + f"{algorithm} {self.time_signed} {self.fudge} " + + f"{len(self.mac)} {dns.rdata._base64ify(self.mac, 0)} " + + f"{self.original_id} {error} {len(self.other)}" + ) + if self.other: + text += f" {dns.rdata._base64ify(self.other, 0)}" + return text + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + algorithm = tok.get_name(relativize=False) + time_signed = tok.get_uint48() + fudge = tok.get_uint16() + mac_len = tok.get_uint16() + mac = base64.b64decode(tok.get_string()) + if len(mac) != mac_len: + raise SyntaxError("invalid MAC") + original_id = tok.get_uint16() + error = dns.rcode.from_text(tok.get_string()) + other_len = tok.get_uint16() + if other_len > 0: + other = base64.b64decode(tok.get_string()) + if len(other) != other_len: + raise SyntaxError("invalid other data") + else: + other = b"" + return cls( + rdclass, + rdtype, + algorithm, + time_signed, + fudge, + mac, + original_id, + error, + other, + ) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + self.algorithm.to_wire(file, None, origin, False) + file.write( + struct.pack( + "!HIHH", + (self.time_signed >> 32) & 0xFFFF, + self.time_signed & 0xFFFFFFFF, + self.fudge, + len(self.mac), + ) + ) + file.write(self.mac) + file.write(struct.pack("!HHH", self.original_id, self.error, len(self.other))) + file.write(self.other) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + algorithm = parser.get_name() + time_signed = parser.get_uint48() + fudge = parser.get_uint16() + mac = parser.get_counted_bytes(2) + (original_id, error) = parser.get_struct("!HH") + other = parser.get_counted_bytes(2) + return cls( + rdclass, + rdtype, + algorithm, + time_signed, + fudge, + mac, + original_id, + error, + other, + ) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TXT.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TXT.py new file mode 100644 index 0000000..6d4dae2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/TXT.py @@ -0,0 +1,24 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.txtbase + + +@dns.immutable.immutable +class TXT(dns.rdtypes.txtbase.TXTBase): + """TXT record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/URI.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/URI.py new file mode 100644 index 0000000..021391d --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/URI.py @@ -0,0 +1,79 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# Copyright (C) 2015 Red Hat, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.exception +import dns.immutable +import dns.name +import dns.rdata +import dns.rdtypes.util + + +@dns.immutable.immutable +class URI(dns.rdata.Rdata): + """URI record""" + + # see RFC 7553 + + __slots__ = ["priority", "weight", "target"] + + def __init__(self, rdclass, rdtype, priority, weight, target): + super().__init__(rdclass, rdtype) + self.priority = self._as_uint16(priority) + self.weight = self._as_uint16(weight) + self.target = self._as_bytes(target, True) + if len(self.target) == 0: + raise dns.exception.SyntaxError("URI target cannot be empty") + + def to_text(self, origin=None, relativize=True, **kw): + return f'{self.priority} {self.weight} "{self.target.decode()}"' + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + priority = tok.get_uint16() + weight = tok.get_uint16() + target = tok.get().unescape() + if not (target.is_quoted_string() or target.is_identifier()): + raise dns.exception.SyntaxError("URI target must be a string") + return cls(rdclass, rdtype, priority, weight, target.value) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + two_ints = struct.pack("!HH", self.priority, self.weight) + file.write(two_ints) + file.write(self.target) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + (priority, weight) = parser.get_struct("!HH") + target = parser.get_remaining() + if len(target) == 0: + raise dns.exception.FormError("URI target may not be empty") + return cls(rdclass, rdtype, priority, weight, target) + + def _processing_priority(self): + return self.priority + + def _processing_weight(self): + return self.weight + + @classmethod + def _processing_order(cls, iterable): + return dns.rdtypes.util.weighted_processing_order(iterable) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/WALLET.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/WALLET.py new file mode 100644 index 0000000..ff46476 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/WALLET.py @@ -0,0 +1,9 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import dns.immutable +import dns.rdtypes.txtbase + + +@dns.immutable.immutable +class WALLET(dns.rdtypes.txtbase.TXTBase): + """WALLET record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/X25.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/X25.py new file mode 100644 index 0000000..2436ddb --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/X25.py @@ -0,0 +1,57 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.exception +import dns.immutable +import dns.rdata +import dns.tokenizer + + +@dns.immutable.immutable +class X25(dns.rdata.Rdata): + """X25 record""" + + # see RFC 1183 + + __slots__ = ["address"] + + def __init__(self, rdclass, rdtype, address): + super().__init__(rdclass, rdtype) + self.address = self._as_bytes(address, True, 255) + + def to_text(self, origin=None, relativize=True, **kw): + return f'"{dns.rdata._escapify(self.address)}"' + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + address = tok.get_string() + return cls(rdclass, rdtype, address) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + l = len(self.address) + assert l < 256 + file.write(struct.pack("!B", l)) + file.write(self.address) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + address = parser.get_counted_bytes() + return cls(rdclass, rdtype, address) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/ZONEMD.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/ZONEMD.py new file mode 100644 index 0000000..acef4f2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/ZONEMD.py @@ -0,0 +1,64 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import binascii +import struct + +import dns.immutable +import dns.rdata +import dns.rdatatype +import dns.zonetypes + + +@dns.immutable.immutable +class ZONEMD(dns.rdata.Rdata): + """ZONEMD record""" + + # See RFC 8976 + + __slots__ = ["serial", "scheme", "hash_algorithm", "digest"] + + def __init__(self, rdclass, rdtype, serial, scheme, hash_algorithm, digest): + super().__init__(rdclass, rdtype) + self.serial = self._as_uint32(serial) + self.scheme = dns.zonetypes.DigestScheme.make(scheme) + self.hash_algorithm = dns.zonetypes.DigestHashAlgorithm.make(hash_algorithm) + self.digest = self._as_bytes(digest) + + if self.scheme == 0: # reserved, RFC 8976 Sec. 5.2 + raise ValueError("scheme 0 is reserved") + if self.hash_algorithm == 0: # reserved, RFC 8976 Sec. 5.3 + raise ValueError("hash_algorithm 0 is reserved") + + hasher = dns.zonetypes._digest_hashers.get(self.hash_algorithm) + if hasher and hasher().digest_size != len(self.digest): + raise ValueError("digest length inconsistent with hash algorithm") + + def to_text(self, origin=None, relativize=True, **kw): + kw = kw.copy() + chunksize = kw.pop("chunksize", 128) + digest = dns.rdata._hexify( + self.digest, chunksize=chunksize, **kw # pyright: ignore + ) + return f"{self.serial} {self.scheme} {self.hash_algorithm} {digest}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + serial = tok.get_uint32() + scheme = tok.get_uint8() + hash_algorithm = tok.get_uint8() + digest = tok.concatenate_remaining_identifiers().encode() + digest = binascii.unhexlify(digest) + return cls(rdclass, rdtype, serial, scheme, hash_algorithm, digest) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + header = struct.pack("!IBB", self.serial, self.scheme, self.hash_algorithm) + file.write(header) + file.write(self.digest) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + header = parser.get_struct("!IBB") + digest = parser.get_remaining() + return cls(rdclass, rdtype, header[0], header[1], header[2], digest) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__init__.py b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__init__.py new file mode 100644 index 0000000..cc39f86 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__init__.py @@ -0,0 +1,71 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""Class ANY (generic) rdata type classes.""" + +__all__ = [ + "AFSDB", + "AMTRELAY", + "AVC", + "CAA", + "CDNSKEY", + "CDS", + "CERT", + "CNAME", + "CSYNC", + "DLV", + "DNAME", + "DNSKEY", + "DS", + "DSYNC", + "EUI48", + "EUI64", + "GPOS", + "HINFO", + "HIP", + "ISDN", + "L32", + "L64", + "LOC", + "LP", + "MX", + "NID", + "NINFO", + "NS", + "NSEC", + "NSEC3", + "NSEC3PARAM", + "OPENPGPKEY", + "OPT", + "PTR", + "RESINFO", + "RP", + "RRSIG", + "RT", + "SMIMEA", + "SOA", + "SPF", + "SSHFP", + "TKEY", + "TLSA", + "TSIG", + "TXT", + "URI", + "WALLET", + "X25", + "ZONEMD", +] diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/AFSDB.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/AFSDB.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1486c3b55b6209988cc794c934603abd52ebaf64 GIT binary patch literal 1052 zcmbVKOK;Oa5Z<-(aElW`RjRgXLJn{UQez&6UJz1C1&6A=R7jAE<=ERi@QYoi)Dcxd zssuUpSpGuQf58zh?J1IxkPs(ssm!S-cGeC_B~Gm5o%z0*o%zPQUkin4#MhV4-&^}R zgnj}TEsn*CKXG%6EM#FD)v>Tr9TVIcWu%E<{>4e?8M359WXb%DGLf9R9iibW-%+n( zK&NuTou;9BKE&0`FF;#n$_XQ-Ail?)V?>1Nx@6(HOfsP4Z-p$!@&wL>nQAu5u0Gvf zU+byDR46e$YMFSV1Na~&UK9=9qRvEc|KO-9`}lq>7_ypOPIyiEEM* z9Ay}>cNmncTyAPD&mo%W*^N*$TSgFs+AeW-HM`x={4i*F?!(&s2O4V!WZ5^&S4NXC z&2pJWEhx^k)vcGB=vVW@D4+K16%s0icX%&W7Z^8cK3W;|!h zV}WZpFsMTI_z-TB&^)vhGtw#UM#?CwS4^ah@Lfop)VAN>KU0?IHO_BU^O2(KP+Zre zXiX-hVW*M2u3MhTX~n0WPiPQQcw!XFo*+?5sGu2OVVmP0Tjtv#`jMF% zE}~rVLXxt#VwrnG@P`UTQc?-dSCy#1Z=u$9oNiz=Y!b;FMl-;rH&b)=8U`cL z)Ho-fhN4^=n}6<_p2J@UW5inb-ngd0+HQ06MWS?;cN(j|R30VsKc-7O06&+l@ogw$ zjN{t~-~5Ia`29-R52g2|0iHj{^S`AzT>ALrXrsR|P|N3Pd7!SGt1B_$G@naaxPS!F F;}1-G={f)a literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/AMTRELAY.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/AMTRELAY.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9acda948a0611a92fb6e793ee17ab867f5996a70 GIT binary patch literal 4203 zcmbVPU2GHC6}~f`8IOOGIDtUoki{V^!cbyJvnc`!%U@S4yQa&}YVj7$8qbh8IG*gC z31G_+X%%T5X;%JjB?l&Q25(+8A29DfV0L=pC=HD{O`f%FxK)@fl*OJJ z-ZUN+3{jG@$#F%J48J6etMX(9<)9>eJekbcBOJDdI6>73h3YwqxcidO05sHx73Wm= z7Fst{Gh{W;GkxM2-|{WX@LOsgBD9*4wF(z}&pM>*V?M8!q= zqWi-z^e?MwhIV3?GoKEf+NNbzjLOXXB1scD8_!P{JGuAuH**jh3tNs@+(B37BU zfQZn`w>uUca4xkA)Y=D7eK$?k1>$R+^;X2L`OOPEo`}(>q0ZZ53$Is0JB$2k__ccj zcLpAYtKp#{_i5YewjB@l+}~5)d#t+cc#(S|Ze0y;y|?SmE_1LmT>5bC!y@;Y-$ZrF z0Wlmz&~#{$+8>|-5V+I}EUMe4Y_wjp=Cm)ZL zgYU0-Sf6dJM(4t}Go5x6>;z=)8FTFgBX)5pX3F~m<6=RSnX4xN-CK3{eVf_0nO$Yp zaoS%oy6bQS(a}zcKXAIQ;FCT7_vtQW!09e-#vs3VwG?~>Pk}Qj%OQ?DzJ?P#x_Xsv$L{OcZ9$^904(Wh9q4sB($L2(3Tgm#43zl}7u4>l2SBfV0o62F z6UnCT(xtgezm|&r)!xXxOLs2amC9mxHPE}jd==<>+R<6sGq-1@V|S%vclk$$s~tzm z!6Q$5`^wvfs=aUE%+4-%gFxpz_f=raD~nE5JKin_-v$>dwav9H_#Yl$+O!;gbg3FT zSLFZE*0CDgaQprFL^*nJIq|sjZ#{qRsd?Cte~q|K;>ISpG#buy63%jxrWsFjdXt$v ztjvSUP4V@3H|EA9*x4>J+nmY@yp!xfWMrB~JHc@gh^a1MVK-;%h8DC(izCIQtCLQADU*u&HubgSY&%{@cS(#PCzGr6}K0XVrQAUj9zLD(+el2P@*>($I3(qv6Mg{w{uC zMZ!kdxZL_XIR3wJtIRmY`voxO6h9^C2sN`51ewhP6FM`1e+Ai-2Lr+^+Z?2MvLql! zxxf)!e&T_lgo4;CW;XB>7pHH)4Jbw<8igv#8-8RR%er&`^>_gzWV$$7Q<_LpO`%p^ zdvrUhF*B-T#)^(e__k3a5i3F-1y#QL`()B^(t9whrJ#cJzCXIsKUC=- zTDnl}KUfhX#qjL}Fvh~JCFXvt+B=A(A$HuBD`FVT9qL>O?WlxyEaaDlmxrsNWB(f` zMA`UxK>qnSRPmo#d0>#@2L?s2qv%+MSCV8k1z!@7u4x%n*B~>*KoGM_cJIG)4kM7>22BCrs-%WaHQP3w=#CuM0f0rFi_- z>DkjO!7Y{GmX+Z4N^tu+0rgslu>2>zzwBLMx++Z9Iy=nxi`uQ+Y!1_?8i6jn72EwS I4wx7C511}l!TXfQ=2EjFO;KT-l zBwjIyAdP-TzXJV#ngW}^l{4Tf)?|Mrr?8Ri@K&W)Ot*NHmYAU9Zws7}VV6YH@jm`t?98wM^^4 z+!sFOO`m!^2wR9tl|71k6kDN(=ZC~hBJc;$oWV^&i^g=euVQsPQIPJi!+9Fbs zMq^S-`xS%ZplbRe8v04n(paSRgWC4)b{VsJBZp-FWXLe0uk2dy^XuLiQh7 zgv9eeCZ1sCeGrs^^ZaSPOQt$x>QfnT7=<+R(>hGz$*v|1TS~GAe$vZMdc-LCYOgQFYwLo?cL$J zK`NuWnfdxASmi1|_ zJ@V~Q$g-GSh=(S+<3T(Yy@U*Q1sNQyNp^L^*@7I-y5lw%%(vUDY_!BE{C!Gg^fu2Q z+IpyU6UaAU{}^Q%1HnF-EH?+={W+86t7w)nxD_cY7?Q!SFj)~tqR5CE!V1&*f@H)D z5oA#uG5U;1k0e$_cbIJ76jI_IckSxc@&)Kpqt`HT%~?~|Tr+Q{t`^<-JTcu;I4k>n zB}K5FCx*g#!mOpuIUeU~bDnTpvx-;`@17n-fImG=>z~m5h^ZkL#-Oc=74#O!D@=vm zW&<9BDrkYO+1D!EHWRQaNDXg?>Vn_*w+e4C1{>Zr=y&W@_=UuNZG^(Ra{AoY9Sir=SRMoUi zS5>{JsyS-WwB1QUcN@?%B^Nt{-li8Z9nFIEUhM1iimwxrCnN$ACF1c8wr0HuF?36F z98V;MTLSCUj=$K~1B+_oHmeRMeeLHQLGwDJJ zyl-DTcm6`!F6?sciZ{*0o1xJvO)gy<`I)Bv#FDhn3IYa#0dwU z@C0ILt_B{)j;;NJXkdx7)}vh2CQ_fQ8XH z>;_rv#pu*=iD}PyBDVFsft8rY>6SwVV7FutIsnc5Epm*K$0?cUI!AT$`x4lpAwpdh zw7_A}S(48ME=y3Cb;zed+4(Yb%V2@W2 zdo&(E3`_9Q)@D-i5Bew=x6U9|dl4ACI>VN|a$M{}UyI$7lMI2=d-&2Ai~X zkRsB_cA+9xBHe5!PlB4l5nA260h;N0zG%DHQ2mnaB`AyFoCek1o>L@p8Wel-UMy(D z!6Zn1x`VFw99mrhhlHps`&mtWq6X{A!<}63;a-M?&hyYMqYvaKcH~q;PBrC|H0OtR zl4lypGtJ~EO?Hr-YRIRW@|jQ0a)r%IL;Otxs%0`S2Iq~__lm9MFuYe{s+!9iMT^ek zs`~9ByiNj1L{*Kv?muZ%$I82os`~E&G6Gxk`usOY4#T^`Afvz`7il+1Wr_9+MaPz< zstQAffa}mQaK%SE!c$e+eTE8NhVG*k^bh{h13?NSEp2K%^T%hk@a_+Bs5$jY`6^s0!_CodF}s~!|)V1IR$PgjaAoHG`&$# z?Wa36S9DG`bY3Q0GeiBxZyh;(BAYMlm%{^Fl0*BsG~A(M`((*3*pDLx5QCC9l=8R#`30x?Gx@@$749aTI;~o96mXueKg+2J-jAw)>{LMx7Pv{J{wAz4JfJ-p|%nZ)Nq?7F}612bInV L;Jr&-P;~nTfbgOI literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/CDS.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/CDS.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfc3f01d51a4849b30b730eb265883b5dc83464f GIT binary patch literal 820 zcma)4&1=*^6rV}5*=%gI6hFi6Dxn-nPV3Prk`!u~iSw;l1Dc&HLtkG7KHT`}pDO@Qs4d4`Ixn z+JNdEfJ4L(!#*-`ttAt4?9Du85upFYN$4?R(mrA`#AxT78|c+NT;#pEi?*#j|SVN-SySN^x2clmtMDfZ_7=8iCvnC#*sY-+jE zYnAe&?VT4oh?@y_qJ$+91f5_IgI=f9C=IQEQ(Eej1`!+i0?$$Ud}R5}Pn%K}IY7%S zOv`q{vOMN#sj}3zQeLXtJ6l38QReRQG^0Kbce5e2b1EF)qe^sn_JjSp|QY&p*?NJgL{X$q2ugQo=M=*M{i=YeXm1R%W! zVjq21u1ysAhWc4I-mbr1AM2}yzB<;sh29U57);ZWXZ2>Bj>xo!kou{Xj z2Gr`iK`_cJ+vkb6gYv52n)>}_-p+5IOCnqxM`FwpC07Kh^DY27)9z2V`XE^ZQ>;DR x0x^*>#?>u^7r&sDiHfAwtF@PFW4v78<)4y@)r0PzLsSUpN=DMksc@)k`~rR%tQ!CT literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/CERT.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/CERT.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..344fd8b2abf60bac0e5ef54cf17a00b06a04f838 GIT binary patch literal 4469 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/CSYNC.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/CSYNC.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b15d713e20ecaf0c45f049d54602961d843b18da GIT binary patch literal 3320 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/DNSKEY.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/DNSKEY.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a02952b1476a2a7e29edc414b5c7c1d130ac9eb8 GIT binary patch literal 709 zcmZuv&ui2`6n-<4?AF+b6s+rEQK*+an4x+t1;yZ@l8OsL)5DNW26wl~W=*n%coPch z$z%TpsegqRK~H;H7{P;{ytPCSdU7T|$i+Fl?|U!rd++7Vd^1f0C~k7B*`fR5TFoTR3a`Bhd3fGi8NP>bXP~f(R96$xA?yph9?k^69_b^xz^@3YW+piN#K($k-Zl}P@-*a(* zT#{oYf9wqR?1R^4g?AF}$4TG}G_l6m%!@cV;`YV zeJ&k3jw4R}I2`5FAA4Dr(_=n6rr~5n=Xo}cXAi9H`!tL^l=Sf!gucJ>OwEqcB;MXCY#b#t4^Ovb50OK z#a%!*zC-_SM??4mq&eRJtiSEQ>90_4iF$u9LcMpx4~Op$SLW8z+*+B|(zFVY;er6Z Kb)gcn zFaZIAxL^_OsYN8|%`@f|=-<;QdCi)=@L=+DYe`7&-6`tk(a7l`yD9f}O{iB8>qBR*it_|Bk8{4vKp%9f;E@0C|wlsGwk>Za! zyRt=r3{*fsD4>C?LqHWlQ6-nk&>;#?AU!s{6lj6M1XD=a8i9-C;+taX5ctx*H@izx ztkU*@csujv&AjFN`QGeb{eCY2`pcg_pH0hze1Q{(BzsUFiG97 zL?dxdCEy`0&KF!^-t_-8HqWoeZk+E0YilXL|it>om(dLWE=xjc#MU(l=-9j`un=p(*^uCt6AI+rh zM&}F0Y(DpD^a;60X>@f5Dh~fIZ?&KAF*V+%Zgjz`>c>#9}JDkX;H`79x%S z(~SMKW%4x8&HmaUQ+Nn7)d_)^WUcmkl?5xxinf^4=8aT7rzA58 zU0)he3d~l>XF}+yzjJx~so3?EixZF4c_Ib(akMYN2xS9I#zxj* zc+R2Fql1v@KL_@9TkvN95kBDJLYds-AO+;OsLBv}E-q1V%Zj&57P+`@nuI${k)_9yoh4LD=BWxLBT^i{u^fb&)H&w(XqeNRgz`|7}#{x9zkn62)->F)%cY(&+X!Dhe%o z9N5M418s^hEs8C=jZ;AvL)=D1sYRCoJt`CkJr0ks&lL5Ad5t2hq;e?(pv12vbcL!3 zBY{q%eW1pY!xXZKIgKKg0`!2+LMmsRIZp>rAfo`$hazr{`b}V8FOw&-f7M-;dpAyOp88Y{ z?}c944UJYqqg$a`=#{eYE8mk)e`RicZu3+v6vp{pPj6*veQI;8)`N2o?5Jedvzz0! z(09thJ3gz_8$kmrm>?Li`aZAYTXYOupPA#o?H~(BfoJ&Rk>m>xZ$wJ3RN&Uo6qZ-LbXi7 zNZn5v1(Sz1VR{<*V!DmIVrYv-GjUkTuy}_>dJ1frQl6$}QaRJ>oU^2vuDJ)y1_ND| z0#*V(C{hg&+@M>B5GSIzNS}pAcPF4da zH*fy#&ii+2fisUg&+d%;e*ELuN3lPi`}5^bF4xXYJ%06#$8Y?2FK}$Fuv#e3Z3H*Y zZ;bBqod4uL&wIR9GPEKDRvSYI7gTsFKBS#K2MP`yjScD6^K4ZI=$zn`-2VeD`2Xt^ zxyYvxmD|X%R`S65QTQShMZgU^rAu%DD>B?Gx{GqrQ}k}z2|R71h)hp|1&VPoJ@;rn z%QC?90=z1^K~uRItH*pB6`0h*4pT-AgpII|;uVl$Y12_u?8nreGbRbKkzrg^~$Zi*a14pD0R3k|@&;vHu1kxjr z1f-3Y;Aj%EZSOCMMefM<(`YHIsWot=dBTy20(aOVuezMmIcO|lKK4QwxpDQzIJ5x? z!oQF-pyk4?vMp++_<+KN(?S-6jRz^Jg(V9jgnOxsX3EKYcAjdw&N9L5NG5XmTq>E! zqyRCz#w^t|Xj_J^qaUo;I~P0qyhiV^=RW{NeH<9Hj^llmJL`8gLt8x`_C4y`Q9qvh zXbzC!;wKkt$0y6)J-K7eyXt*-=~HclPi{#&;@{=-{~6f^K+J*> z=oWms5nB-0q;gK7{|vIAB$lL-8#L{LbF}c;ycU$?Mj1kJVktJu5Kx+42JNt`QFP#S zdI3eA_R5mx6I9ozRa~sSA8yDxwj#@!z%74vvD(1^Dc0_#P)kj<$j4x>{u(g2H;=r$ zJ94%q^Iww_UhE z=$X8sD+;?VP;40#zX>T;KgCy~H-N#_#=2=FwM<4);F2B&DZ8J)ZlMLkx4sAr`X?)F5gJ&^+ff&craA>QtTJ9DMHj)>mwa GIriVxB4*|Q literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/EUI48.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/EUI48.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0e8edaa99ded655ad3906319732566dfb26ac13 GIT binary patch literal 707 zcmZuuO^ee|6umDWO&n4?B06yrchPF5#fq;jBI|&T_#{Oe6evP=qxE@j8Y>_@P{0=&*X>?atT6+T`ld>xn&Wv<`Nn zMrCg6Sshz;`o2F(Y{%o7j5NC_v~o;`kNS=s^X%MTlUCL;ttx7`$Tqr`dz=^jiy^Cs zPW5}sce)=nk&1_mw=pbaLP&lc$fa+vzNmquzS}t1n33j`G=EDqvibhuhux#ynbDdW Wt(mbkHMVjwUi6Drp?kaILEALAabilW`_ndRjyZ7?mmwJ5-@O=LCy*E(+ehN~Z zS_$$gf@5I7hzAxavSblX29=|_f%$)$1doA9N5EvfqyJ+r;h@;ztUfT#a=}jIm3C5= zEG;E%Wg%GCEwv&^CCRTLI0jy9ZApw+GLtNYD@-1%08AO^+PuD1U4vrHL|_3qEX617iHZ^6HYxIWO~A1 zCjxzh96Q13Q?nQP+;l_FNlmwBCrN4^@ZiAo`i?nFlU^9y?%cd(#{Gof7~1Yj`vs3p z7Q|-6l5~jpLHD^?EZZ5T5&j(%VFN+DiQx!-D3_NytY3J$_qDMxy*ha_wP%gi;a=3J z%xyiZW6Mt8_eY8Ccs!GlW*3E4j_L4G-?3w!o%?Ij%37vXMGY6(Mb~PN^P+z-WF66| zesA?o_oF6K@sRN@hNVmh$*%*s^bIzbHIUSITZdb7(wvdzZ>dJM-#`4YKiQuft(no9 U8#^;&CkK>s8Kf(}grS)62P1x?ng9R* literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/GPOS.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/GPOS.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64552289c91db6a11cdca9d805927593d33f0f1b GIT binary patch literal 6089 zcmd5=TTmP475?`ot?n`sHg;kob2ADyYHCW2@3%O1o07D4oQ8?BBWo9s5EA)!F&17j z#9_w5(~dT5;_Z&N_R_)=ulsxM`oMg{#UrNWtP1&T-$9WrnC z8C@p_eO5%<&a&>+ko&h6ndJwA3~Rz*U=Cab#`K6 zI`{UFqJ zP98`@p{s$DJXwm7;x4oMy9J~7VqJPtk z{_*|=f8(6Lah7i^IX(Gf?+g`oedgRcb#8j+&DUm}pKXto0-?gO!twm&JPQ)n)OXae zw@bW#iDtN_Qq8mPyKlOu+ln>K(+t(IvTwEA5`%Vjg53INmTLZVmX#wg3}x_HqiI)LYroT6WJR&d;sN94zlE$M~6tD^6N zzPqCD&!fv!SKus)1~isj^00@L5e$}vMX)SB9aoadpeSjsbT$KqNSb^+Z4eWc;K#wX zbV${}>VHlFsM=c}2LSvr@Ks8s@0f53&P7J(pUNnCO(1* zE@=UiZBOsgSXoMUYfdaC(s5bV90ugC*2{K-N_{n%9$15*3k!lwt{p;*{*B@=nTXuN zuuqQ`)|S2nB)J)a-b08Byl>n&$Jb14nQofrql=#ELhpy%yr=1-=kEUS_wxL6Cl!EY>_Tb>@~oe`dXLrO=ki!xM+6n`chmtuKb! z^L!~>Gub)OIo&ZM-aSza@5{T^$}I9brY_I(&7{tem6`ogO(f6$$XD802bJII1wspf zy14+*N)TxpuOFOhn{IgTz+zSPv8rR%+ffRJ7lI9Q!G=6n@&pU* z*AGv}bKnxe`{ifHor7NbYg@*HBCnzk+)8m#Q3LGOajp z3(`>-^|NCw0r=HQMAOPu{F>qt){$UUt-D*Q?@EdvsBJXAuPQR)E=t|TxOls zK!CIj0wnFQSDwq#|36swI;&Z=Zu`aaf3fa$RztxHEy{G>LvZTD^v>f^Cnga{w{MLb z!tj9691tc46YvxwFPRF&aTtihb|=%2z9zE$16Yz}-Dfm!BA&@+k_mX&002>GnOJ9~ zcktjv!cY=RR%A1~Z-AWq2^0|6ynzBc>6`Esy*u&%G(mWO4CVd#p%U-C;U0JYtbOvp z1UyKlzh4aPf;eN63vH8!CJxQ>JB{)F34hUB`_;yS-_7%N|Iz$^=69}j(XWPn+y?08 z*nsb}(GPM$)mcVAycf0bPU9?EMWr)SLQ}CK?37W zt+*1@F^*iTV2m5CE<;}hnZ#Q82ow-$LR%L?EpwrkVrUORz-IE+Y6lPT$Uqjd#Rxpo2c;;DBcNFKkK2^) z05X~0h^x2(n!_Pu%lcE^()boImP1gCq6ZEXtS+3JyfAU07^t7+>kT{kx;9a7KPflf zg`Vb;fM2x827l;I&k@`lL7`z7**5QP4$IG84FuOPfN7*CrVryz*g z1dt(%m(y8A76d(I$IpXW&7&vw{Sy2pB;tK!w&SEYMR@!G&caJY$?0~3$-(272*YQ- z2NF^`EeKHxw*XbYqoc-?U<(l?1^F3Fz6Qm^G4yw~`aX{~wU3^8z;5OaEPsRL+)G~M z*!20!XYVr(?gX{WKyQiGn?WyExAZLZ@6&9VMVe%c|4W+Y8i+jnTm&u1Qw&<8O>4Ai zhTH=pr=O+1vZx0igmq~?_}Qz~{uxexKN+-SfB&EozX-9zMMN()sifYL3z0on=apTDNk!3{suw{DdmpV(|-@gF4zN7X4 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/HINFO.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/HINFO.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84dd993df4c2ada6d5ad240a2791e769d7d10832 GIT binary patch literal 2954 zcmcgtO>7fK6rS1j+H1!@iJg+Pg$mLVa2sMmNP&V7EfOUaID({7@qxwJ-6RgS*PYpr zI5JXNMa3yai6tsWYAd;?2BiloRqDN`{v4XfR7tm0Ma79*ia8)I?VH{84=A9vhmPzw z^M2;d?3?$!_oL6}K`?&!_V=+HB0_fUf*yFF;R#)TA)m437=B@RzEsD9bIPAE~xTtK)%*nPxl?%XL zf%~f{h7|<%ps6n>P8Ir3-(D~?$)CMa%2n8h)t>T)B+qc~@A?F3P2Qe2>Ot1iW> zhz%M~9!=p`(;y1_4B=#d^u)ll*PON!Rn~}N@N#Ct;4~@=4Oe4pcDu?4Kt9Grq}P;l zLJnO6eGcciWzG^~ltbf<`)%tSuV96%#v_NM>K8xB-tm~dqk3!>h56{v-M@ej3x+@^ zGAc1dNlK&>x+EEHDNd!~tgceRu0dE{gq?u<2 zwq-jTGY?Nk#E7<1eG!__)jgTGd@(+% z(uk6#5$gaNc|CeAVn(?;lO-&Dr~MXNy>SP~=xab8%%I;y-<)ezY+r0Eb>0%g>!G&d z(ZbQvizR(&upH{iJ3bQEgIkK-h3-E5I_U2 zzuLI{QlO}fOS393m~?M0NqG4i-ccKo?+9L^*!k4q?@R%?kdK ztA^pS>mS&N8~}#_FXzgM%QiU7&=D5lFg84?_>`1X)1&$r<3?3oqB=>WN5gJFdXf?! z*b!F93|`kRk~YS+GuBs+prmW{NK-dxSt!k6#nU}Pw3HRmfECf2;AOC;eL!Z=hKK^e z`Se_RiO;9Y{;oW~?rWYuKX<-F^XJRHSMuC1f#AY{#S^9DOTluWYenp`0@+weYJt=t zfKZ2OVT6jaNlw?oDdYqNUjz%vy@lS#IlNi6>$N8v-y}m7E+N6+*l4 zTG(`qB$BEj%33NzR7%ZUGrV#KG@nGTL zEpe;0-Boa1J@xOlA1EC7S$zH}N%{_4?@4H{Hfwm|O{+mo@2sI*$T{mMzk#Cw=@tfQ zS(`}fs$zA#u#3C^$2K6_nTZps5)0h5W=C1FKLX=s6SXYXlC@)cO9Pr4$Q@stX^+apOS zO_@ltwO5kforouGjZ2agO*Y%RM3b6MCCO~2zRG?|?{r9wMa$PSQ@E=GEo&S&>^ zSxqJ-DU3+~q?FZ;V`c>-tah4&xuU0lJeWm)IJQ8|3brje7JM`P_Z_^jx8lLV+jzr? zoGrHp2Ji8Lu&2TUv%#^IUlg`(v;upN<88%#wlXusbRYJRPH=Acpr3WCQ?i=T6Iz15n(|#OipaHI!#H4Im+gyJke5VH<_BtS_4WZy@U|*5)3HN31{|CC7ke zUrUbz+2Aq8m6s6q-$AXvvR^Y$_$E%cvhBTXYq)(Cx8LV>;ywA}^Zj%E^8<4O6$C7N MDfjH3Okp1KH-!vdKL7v# literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/HIP.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/HIP.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a0934c713173db9044ddaf4594260973a971f6a GIT binary patch literal 4820 zcmb_gO>7&-6`tk(aQV0Tr6gOnEZK@{BCcrJimcR%5?hXc;#vvnAV8G5TZ+4uNKquS zyOJegX}E=fP>2ItNL$J<3N!7&GSnhE>fA#Q&BgTEkgyJl6zQS4$)XRbFYTMzAJPh| zQwQSgyqP!e&Ch$^d!w&HAwPohx4-;-;&u?BZ!zH%&Tf?d1dRfckVK`>sP&~rDMEWj zJ>*M|GUUtJU(Wt|M;Vz();l6A-&%bhbQVdT8%UyIPGg1FX(gMTR^_`wTkRFQYcw!N zMVRj}H4?Y`$j9bGxanLN5eN%ReVl$Cgixj~I`ve$pU(4^0ue{bFoRi`4yGl~>pOnM@rnZ8(R zJfkGEi7Atwl;=#ZDl0RxqQ<>dt7V~$7hK+g>bFz@Y3|^HS4W@0xK8Pw6_3?TAsr?0 zoNYy?B}(!*N2DXsIRwn_+r|u}&a6;YFYvnZ{)cQ6KQQg!HZDvIa;0C zbT%c!OyEz)reuZ35_ywR<2~GZSHWr_Zjmv6ON~=*z znHoJg@?jK=qbED3fZw6{ASRqWc0-T)3#h)oj=ttY3*Hjnw$xfY_?VAuG_@9n7Ke%l zirVreqp3H~+~hZQ?Y-A=r(@Y?>^cgxP{m_39VQ&D?S*TL*NWrI+LyTpIivNBJo8zw z3>r|}wFw~b*#+QN{wvr4SebOvj(cIfk5&&|i zlLkn}hqA4>l55x(v2%NkJ8EVnI>(Kp9C%SJdpwOUA3!L_>)vG3HuXU2d@@|4x5^Wp zHB#v2D-6qz=d_%e^T0A6g4JPP+Fjvy zT6dAe>I|vbyGZBmqTh1>Mc$DzzDB>&0=0=0d`d5<&bWKGKw}#?2EXzkR2L8hcdw(% z0Jk|xr;^p2Fpi6}w%<_&3ijHe|R-k?7ctu z#o+y+(*EIptJng6y?YN85lnt8k&dbHM8fn=$g`=$l{pI_I0*n)l1*+brpo<&rf;mj zPfp`53oE>eoK3~z7Cx9ioQgAqbdZV$W(6iX{u%WBi zP&`(ArTFo3(=xj(Er0N^uJ0k=M=bHBc!$9i+hBPXYz=IW-pS^2Ud_dt8*(h2Iqph zzb0k{on3*SMi}G)&&Iif4j8BVbpMJSxi2AQ52%2M>l>Ff(F&+t&|~_h)3!gEemtaV zN+LaOa<&h36CUs>+s#6lN0Fyu5U+vF>qKAJ3yOqHI-XJ$oZKlGU`<-fOe(Kp@-QYt z6(16{j1`VaV8%CUc@PUTQN*U=`A z!fl0%ix)qCH}4~^)maici%s`CzUVNzde^%KN?ij+*G~-LM74LR_paT!X6%cu@9Qt^ z>o@id7{cq|Q^cLZZR_EKrSL%`d}s@KdYHvKUpiA9Uf#7jyw+;eoqWijw7kU4!eG&^mt={TRH8G)gBtPKx%V9V5~po1 zAkQr>CkJR>!*_O$))EDFSTkZ*tkcl|nQBc9JMUnJSv%~8WW|EJNw)!h1px&z0B=pd zf=2=5%^t_Ez-!uYbc3Aq1=Drv)VVW&`wv(zvX1Q!kfy~1~+Ov3!yp_)3*30;6nH)~PF`hcb!qs{Lq}c96OWu653$InZKZVKa z=TKco-*j}ZcSK7a(N)gqI9}r0^NslrmpmJ8wxSrqe!$P|G{IA+(*RuVeI#^mgxd?^ zqFDUE2p_J((Z?7^QC--|e+aW7bnk?!5zol`_>DON)yNqu?gjA^6Q3YFG5w-Am64`X zxE&P5kEdfPd&Db>QYKDbp`x0~XsRfZ_nUGAwrd8+V{uA`_n)M^0X@ojT)l_KLiSTK zE~iqW7@-vW&kPlt`&;C26^vx0!eLmw0M++5&_9^wEgm%-zkdEX!?HtV!N#MIQZ78?Vs@?jQ5ifJ)xgf#K1+4NPISc`%E8RjFp9w19E$@%#~>r97~ z7-+>CSFxc$a!FC;ZbS)BQQcD%`W7`m#eelsZ*s`PezNyhd)KM964mzHGeDi9@&|53 W79zKz3(+#dG#r3u*E2FiwEY+4WY`M; literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/ISDN.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/ISDN.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3bab45645c0419fb7cfbdb9df782e9adb08e470 GIT binary patch literal 3421 zcmcguO>7&-6`t82a!D>lk)lP>s#BQ=DzEK`apXi+B~)pX+O?3fNM)cX(PF#WUCEUA zqs*=(Q=t;I5E{y9DzR`u1#M6Py~t33wg99b85K7<0#EPgHlBr(r^}~TVI@JY@2ny zob%<=jLyu7?hu{tadiy3f;46wX)N^V>iD|aT(_%rzI$v--*c+cz9N>m2h>WbPM!U6 z{7~~(V7>$K>nM#i1aaatt1%io%cQw7n!y^kE~I(Qr}67J?a{qHr1>?^I&RM(XaUU& zZ9d(riJEYv#a~7@a5^xBq@cxJoq9F35}ckhCE#~b%W3_2t|nGu{cA|&RAZesaXY9>miz1 zgpO?&=qKAvvTV>U1GKC?dM$aqkkgZDAv;}6s@E0MEGC!q{8BQTnNBVg&Fh8y@X&=9 zl19eVpIcDWd1Y2Nl3LzKwk9x=-%tHKNjH3Gp-3o>SAw?X#+!gDzH?WQ4rBe*w<>S#%E!Lwi?)SXZKzJm2FB*HSHFP^vk6zmE z8NCy3usA&4VA)_-!;gAnw~T7BQap&At;NpnUi@(U_IN#ZX}=d7_j|_n#W50wdDWw- zrEsy2_#IFHF6^9>PB7cWrJ7YQv>yeVcCM1+z^7CBd!L@cb{}Kt59vG&T;s zj)Rvw%W)uE_2n6jTlFyjXi7OHZ^^gkz!s9orykI-C2aX~$_?2p%jMR-np`yu7@t|3zu5SZzl()YbSrr<_fc&dN}EAb^t%8xXE1QOFSeL zJPc+M{RWrtajb8)VRl?XHKxS7_l7$z+#kn|^z>*jdMs|b^G_6ad^>J-{#D50kN)o~ zcuHIt72!|fQV$ShV1+sT0~@-Mhn$9$w9v&b6S!t951@EaH7$+K%$Y)d~OBo7hJJ*{-Q3AkMUd|BRy_y_z940n?4+3r`F|@^1#fn%L zPnGdusC#Sp=5o2KynHB#oBj>|t(U4}m9cGe=k1mFCO+pw~RlGlv7(Hhhq2U9&cg6Eg^_E$EyYlwm z#leObiQ%U}cpZZMTK?jX=P#Fe0Mry5;jdvq4{#B)>S=QkU-BH`A~t1b&kj{$tT5#p zwXm2sb*=R_v3za%k-X$tn&J-8cor05=%mFp?H7tc&(dRL(ruFm&79oov2vorDMzZW zKu6<;AlA_RNZ&!^Y%OxO9yt%x5(pmz`fGvydf?O!{w#1B$mAe?p%%YTZ&Ap%zKTL5 zCUuQI!%`qpmnEE%MEVBHD>)tBV18N76|}`H)kRr;V^PUEEnZpH3ab4~lZ|Y_G-TO+ z6_Jw=o)xfPU^yM0RT?=1HRJ^{#CLyRbC|kX3Q!C-9D;0Do-L3kq!={Hn;!ORr$B-*aaEo%|lT40Uw5#w#G;&d0dcd6 r2QmCRr+$6v0Qc2!-$P~?kCulwM>j?{CpIRU2uxTh6aNowuqXK+=9s+r literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/L32.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/L32.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..500c541643c6e18716f355c08a35f82bd39b8ec6 GIT binary patch literal 2508 zcma)7-A@!(6uE(o-=D1kVdGEoqy7-N&hewl4jeQ5W=={WZSgR?XA&VVl6 zHZ&n|Nop*K4K}7JKB?iQe?uSJ7wFnR$ELCIsc+Qv!T8j3XJ(hJlC~$=bMN`Mcklh( z-#PPBI-NowKmPFhbf<*SBWBb@jwjU{Fj++yVa`VRXyo#IXbbTu=6S-;gnQMNN8?^T z9=(Myzl5*=YnuO=&#al27k%qOy~5rx*nu-!_((1$3HqsS$5FI9M$>UtQ`b&oCSW3u?{J#E@85Y2S$i$%?xHhjOR%@b!{v#pETY|)=~o#COQ$28CK z$!oKQdC8a}o`xMyi~hjV-pGBV0qjGwMap=$(9De51=_*mjevT(h<;Pj%ZW{;>*oHo z!TXB3-Sf(K+Be$W!ZxT1yqyA+ruB7;N4Izv?)s zRR%GP&&w?+5QxN&))>8oz7>EheC`?7r}Dels3J{6ULd>FnzEcAMTu?r*1WYq0{+rv zkMV@TMKdfb;k&v|uJ{X0?544?A0l2as718h-npu+Xd9V>YX{ceypy|~+vp$OXdT&5 zMkvF>On|Bhdy0^PAHoD~B+SHAswJ?oRtldbAl`^aoK%l1JYd2oTS}Cab#`o!bBHQ1 zsCEUZNe$>||zo7ZV*SDI}xpxR_Yj zV?1a0gc5<2oTP3cf+@{ls+&EK>uI1C(T;*TyH_u)T&Q#mlvCT8>{h0KGt*zm43y)3`e7C~397}%f=-OpeEblhIh`E|HvVmKhVE+J9aKF>+5;%l`E zmB07y4A@Zi>aR15RL=?^*(6X*w*c)Bx&?dCpxHDW*Rf2)28MeK(}tH*;Cc1S z7A0{d$JRl}@=k&Rf-B9}Q_HCvqpK4u6F2>}^LO;ydS&17`^so7G`u>#GQPEMU~}KV z8ouM)b|CzVGWsu%_`mos{RjW$I{&4T`~v?e<6e+v#HXZSK#O&z)C7GQybZjUXASBR z8d1OAK0Q{nAxgD2&=yjf0i{PP7P57sH+TnOUCj4~9WQNl3~qJ|Ryqy=-?Kei*+ZMz zLzV2|a&njN$x7>q4dp}#6>#9ZD&)@RK;_OvW!%g@4~|hF;pagwxOmQH^JZQDWX`bT zNJ7`KYl0ivzGu6>r|V&RrduydERy z>9DjZtD{h{cJG}9^>hjSA!ebR+h4i)=|}WFy|W=Z9)f_PYHb?@7auE z!#AJ=YDs|)PbHp75`76XpKv>au!WTh^)wOgKB={TB;_|L`|Ih7vLW literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/L64.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/L64.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a00fbef32556a225fdb19a8d5c31fdd0ea070b09 GIT binary patch literal 2966 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 D&@0@G literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/LP.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/LP.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75ee1f6b53edf6a30ae5b785719de834fa25fb76 GIT binary patch literal 2457 zcmah~-EY%Y6uBm1NUY8JhOpVrxgi7-^C^cjtgR@aS*ttBmt8VN)0h{P-C2AA); zh_XqaKX;)VJ9l9ao4SJyKR$)cF%z3b-Sow=YerEAINM7EjXq%WGZ0^KHRQHKRmnql zAlKtOewh#49P-d)=lL+(6PCF^^^n?nVVWCjmuoAY*pb_=RYLc<(QL$*$n=zneOXnl zqUEZpA6GS^PFqFyXb#hC2!W%h6--Qd07pb(+GBnc8@jC#;zzLImY@brW+{G5Rarf=PnFSl`!Q@xeN5xWA}iA90T!e4*f1CtI<_?U}<+f^H9#N@7e#I z^0jhrsIh0HBHm7|?;NO|Up!ww)Y$n-MZ6Ph!YQP;K?H+dQWyN1pMa=QSYh9~COc1r z!Op9j72zDMv}N6e?(xLC=oit*Ki-O$O`pGdot-5Ve1g}TLoT)w z=-z6g*(k)fgG%%yFVbr2S(yizDSTeklb1u-E+R|^%6;CCkDG2tN?)creN5We6#S22B0yqlWIO{7%U*(4~TUBb|!B0wxQhMhuySpqO?TYDgt90xInHe|Fb zUAw$^xzYb}CBB~OUGS>j(wX|%2T6wCuc_WI-e9)aNR6z>BLQ^nmV+W~=)MI5pyRfK z&TYfB9e;p5Xp+|1vRWgf)$D9^Y&VZ7v$re;ya0nD1fD}B%FQx=;C(tGu48CPKSEqQ zt-HQRdvg;^#@5$AFC8 zjD8dmH8@f3YjLu}nSob&fOZ!945$Uin6{~&P}OVGnjJ=>s%khoxM9o_+i{7ivU!Zt@F;#4 zoBRb6#<_tHfCmpzafHT_;dendZCh2d9PS1$p~K@edv8oZWP&y*IS=C5Jo;1YgU;*S zzcjPbotgXKsVGQ?n{i%x6}*w2M;~5%B1lrMDS)xTQ*8$?o#t5Z2`_|^r&ME{VD+;y zJ_ObMBz#_mtwLei)vf?(1u*>>^~%RDG$5z=Rj! K(|^+dEB_BWdkG2v literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/MX.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/MX.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55e09232f92ec85a5e031cb662c5a469f58620f1 GIT binary patch literal 619 zcmZutzl$0{6n-=7scRHMyaYvrAlF!&LvrmAIGaE~YqQO;?hH|H*EPHAk-hHVAa_+t z^Dl(_7b%mea5~5{e2pRmmugh2m@=1|0QQ|qIbBfPs(?>VxMtC?RO5WLQLAZ zUyAjeOw*2JBz=fr0a9;mQ9&#trfnmky}_l~8$1o9;|HSO&Lu_8r?w|KPfDD7zL>iT zmpOksx7{pK;9U3)=bs=|{kN~!$oC|3e0LZ#XJkiF%r4~gg1O@%o5j({pPqD{J!j!K zlE*XKIk(@)kcnx?0ujYC#Ap52tT*V);vk0&wRwUdJV0{|KXcVUT}JEeVp74<&e-$j zkv()}VjxW(DJ^T)@!k#XP$uTToI(~#@>wk38+0*eH_uef^@InAw#C!k$6o|hr0RxY z7tO{XgrrA69)E}WCJ*%Bee$-c&y{ZW}AJu{6=PmS!sjlv4v}{kKZc GGyVcgq=~`+ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NID.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NID.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f2521051951ab4142a8599a1155aa2d01b2abb1 GIT binary patch literal 2959 zcmb7GTWlOx89sB}*_-W+FG<`a*ma#|C}iq{#!*{PqNbt6>XwoS!9H}D%$&7nGrMEX zjFYTcBmpXh$c`9q1cvM*Wn=E`Vqova$7C0JtOFt}Yd(bP%bX$KN%Z?25@ z*2$`JI9c`P@C$+^ZHti%FcQD~9aS#gRY18sVArk~j%`gup16xUItbj;54fk*!9ens z7AXr9UAJqtuj@fZH#~jGuK6>k7{VT8Y|pNFzJdOfF4cYN1%g9sLFR{svqYa~%w>UK z)x1Drmf;&gN?)MY>~fu)I7syP`SvC2g_pFSxan z$7fDxp6%0bE*j=#W1e~%+*ONjrJXJQScBL-zF21%8Ou3j5-iZ)-midsu|js0{CcXX z9N65yb@aBP-WfmguJ)Gp!Pi^kPpyf+8Qjf|zEOUM-p)R@JGk$SQ@{S%rtz1-ukDUa z>`czI#!l=Eo!A{e{I2?z+8%$RIsU{4=Wm|9>9@w8T|4*s+&^=J*XP#f+POo`+@W^v z$!6}!n`5^Mx6a=3Zs(r6Gd_Vv9k>QDyKxQ-t}(JYr-5u>mcab*cpHiqXyRPz*S<}D zCqM%8#RuGk%J-mFMRpME0@-EuybW$<)G>VfioHw&{_<510|?P)7?(lHcXgj$^Ov&- zT+E=xI|O8f+}Ss_q217S3Xg70Zk>C7?!CFKZ|@Aw>?kwr2pE_tAj#+z29Usmgi4&? zi2=?@b^3V<^!;$eMqRza10+~T8mWeoB+3QyAy-uSATv+>ILLzmT-z3{`F7c+Oic%Z z>39rNkv#$=5PbJCn?&|eWb-{hbl>d(=V9qVWWo`_lP)v28Ud6?fV%7i*m@d}6>?7@ zV+S{0z42;mbb2jwr!dkkOf?Hrt-|!0{5J@u+0FA?-@7Td24{Da*$B1%a)?=uICTC6 z1W@DlLe1|YRYCtfG{O);^dA~Y*-VNdl$pIx$2){$uWq+zhwGNwWG9yKO1ISZq|mXT zXC?mY-K0)bvAE1nzQ7Q6hKT^4lddvI^+5J~wq*K&h&dm5Xc(==knBf-#W$=hsuYF0 zShgJ+D5hJ5_T_n@zk{4<)ZChFLIGW-9-`joTLrwYU$q&Hi$=^H11s+ZAQ1dY?s{fD z^X93IXKp;R>2F~9yQ zn}z9C;qm{L9Z=FPszUK%5lHcT^o$%fxSCO=VT04FuC?UgI;ZPDT{4_llhSp|H9-vB z#&cZX)Ag_`u*cv&K{jkaRSI3jVt7OLEhJcdgM8ecOzJqgu5t{kpNHk^OjrmQW?e|| zpTv6!$QP^RAL0lk|Gp!e*LH@Et-N?&OiPoUF-|&hPbPBV)0Z!NE=bbpjsVO(9#;dr z^a=-Cwmzb;&m_Ih0}e2WgZ=O?oI=hOPN{*qPHk z;}JBLet_LPJo7^mosV_>f%0sG-gnq}VDX9G86furj^jEq;RZeYqrfLdG@h6B5f^@Klx{9EJB4S(|G|Q3Z4%?bEHA%&|34%H~>c1fV z7mhl0DnxM5$xVrqlXuq?6};iS@B7~O?!8YgQU>&G(22|xexnJfK*5nR<^_u>d3wEb=w7axqY7r`C-UKu3 zRFes%BpKk~8kpSLR46jlNGYmu*4QxCwoi1KrhI05J~fJ&I7dj`av33UA0e(sn~uOb zA%{)NNh=y5)Uye>g+xsG+QJpjW!Uzda)j-Q6^0Q$V)Y~JRLi&#g%z*9P@0>^K{aHv z4a+{T_E~^wJ-|K~`x2Wyx?yPWEXJoIqUk>T#=qtC_`TQlnz^Qo8 zV1M1C59P literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NS.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NS.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4101730cb9b31ab30a582a0d2ff433530eb988f1 GIT binary patch literal 619 zcmZutze^)Q6n--sldKw$iyo06h}fhsgJ*jPXCqi_lj^n^Hamk!+#hjw6WHqx1kdV9 z{TIal!cwbL!X8|4sjlwz(~cA*UqNsQQg3ZjL2M(XEhD2==bLGDHY4eVk+52&tjc-d_>%K%k#j#3BTwOF z&QC{4^10UotoJx+!yePLiZ-EQ2xg`dv0mlU^9?wRb0(bcpzi^_5wj_Are~*if5~2;v1aXYi*~kJM$f-l}FbEN%CFf0Q^~ zPi6+v?1R#ZcI_bUIyKri3+Ef(Hy%i1LK^=mA*(;?KbsfL2eUab(QZx5tsGF!4WR4)RDz!I E3^qQAssI20 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NSEC.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NSEC.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f255cbb4b9c3e48e3c563d6998afae614631fea GIT binary patch literal 3063 zcmb7G&2JM&6rcU{I*GGM$Y(_W5koMPxFi*bs)d#o5U99PgH+WVwq|#eY*>5U*-gOd zMg%Dmwgd{Os*(y-O{E@!a^Vl?smJyb%T%UIRHf>vw-j?gocd;V*AA4nFo!p9=Dm44 z^PAs$vp>gTO$gdgKmIoHT@;}|$RKQDpw#v;2rVKFX^e@|4DH#V=YpOOdLih=G@~<< ztzoD}|Drh-okkivk2DU}v^9J~ZL%ZO8vSHvLl3o8&ev9hiFDH|?tzCr2(YF*jM(TW zdmDPF{o{MF0+SH`B<7SFI*}gmqpd~`w}^C#Vw%;MG*@5~oF|?zoNOlV@u|_1@23*H zCn-uUlhqZ)Yf_Y~rA?VcZ&8%br!r<>i8_V6PTlUJ8VUdO!{mgO)sw1aUMwWliHzeE zlGA!_I%yggllg)(VdX}K4h<)5!_g1sGwP+xxNax4oSnoP31%nXOMRLodl=Ov%C{)%)TfqLQnV-0iU)4y33>zk|)?xc^wm)7&*fM zYDh}Prd+@z=e#DvHgdL;$*DTMLQim}@}`buFeXHgw{>&Oi(pMPGq&xC{`J$P)WwS` z3NvG@H;P;2`%lp^6w=aG2rByt=-UG_F%GXMz=U|b)*+S{1*1?6a6J z!R+X*cI+;9>|P$JbPN_FzseoAj)47xmBETUye19%K-NhHf$BgOVeo3i)c|&l3E^ST zOOyA7X%JGIK?RgxQ>PNV&on~I%Lev~gkK#5pP>AhVd|cwTG>3-ZJWaEwWyh#l{3_g zY0T(0IVpveLJB1)2o_d{V0aYV>=6(EtB9hpg=i^SmHNt3-%@;;Umjc0?@Mp4ORYtR z0A7){SEYDaiZ7r4McV%#kZL}p|H94iz_Y?j^HQwylJJ^`@X+eK!~-z$YiS#041rC_ zXRxi~LO&v7`o%Czjw*^}`4slW24^n7sb} z-pFJ5RC?S0c{_$rz>r{Le*t2XV;H6uM@-j4)cFv}4^bOcdLThq;d=kI{wmX3W_lm9 z4AWB_UAR!XP>uDMWBt`wyc~OII?iGbadf(>39vnkUIax If<`y@H^rQC`2YX_ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NSEC3.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NSEC3.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbb4fa956d422ad721775af09958469452a4cc53 GIT binary patch literal 6367 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 ZoLL-L7+4%!7~CdM8?*n*hN^gr{{?O--Jt*g literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NSEC3PARAM.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/NSEC3PARAM.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7afbae45eb7966aca3cfad36c73c265afa66920 GIT binary patch literal 3382 zcmai0QEc1B8NL%qiKHycw(QhS<|IXuW(*b!X`FO+f}zfw#4ZqJNV;y*hH^AX+q5al zBdH`Zp4L6h+e?A&#h$uZV~3$z_tG~n%0rQt?LSha zR8PqW;_koy|L?!M|Ns8~?vJq;kKp_04}YH>1L`v}@V{__sJ{h787WA?I!b#zPGff* zN>gr6H+m*bsnlGDZ=%_^oTSfWP0gOo zILx#z%~%ev*{UhoTGp@}+LClzjvT#XdaYalP+LTEGOu`w#3 zIWlM8SD-er$0{Jvcgo{*=@wnuHw1R0KHcmKOpC4$6=FBu)bXi8Siwrj4;}1-^(A>E zP^lL`{|}4>&S9+Ff@(UfC~Aggi=xAck|pLe!~VKSj%Jc|9NuT1Id2k&d0JC>TeVDL zSYW}Msy^*ROhwiu%W}e|V&}nfV8fl~D#K*aRT4#uOg`#;%a3j(XR{eKDQESmd{Um3 zY&)O4r5d-Ax;B+u$lJ47DODNX=-sfYy=RN`Yg`>HBRL2@42a)I zQ0sqzrcA;H{r*qE^Irw0Q%Y!wHcs2Y{J8)c%^B@2_z;9F{;$NL-Ndn-#Iefw{mFZi)x^+l;@nQ+Ts84r zalF=ZpgdF>+U|b3(p?$8KYDMp^0f`=efl@?U&ptT&u`(a<68%|;}hHLgnQmE6%pcB zw}?Cd0^&@~HHrZYgD;3gniNCJyhDXU==HWt*BYV-65q zE@uSLPz?eNA?i@FZkgR6HhYK;WoPHjuaMy&8OEC65bbQcI%7pa&#NshfZ?XiY{r93 zZwq7s?hs6}G-#I59*g?=%hyWRUcX-CYF&wPtQ31aUSw*(g?do!J5l6o@t(V{tiDo> zA1TrRvbD26xll{=HSl#}Cvl>p+@HTU|I17@F5wHOYiP2hlX8M}iy+oRhgV5hfRT7xGu1C&7Y*h`(6%O_f!m@Z zzDAe168Fq~H|Sq=zSsEy{X71*{MPt~*MEO~``a&76W{rm z{cb~ZZMZyA8u>Fj_`hKfD#tXe^SH{PVBf_*0EjFxOW`F_L!MM4*dSY(LYQQ1OSXcu zY2s`l6l5iFZ#T(SIh!+VRT15LUf@hpPg@c)ydasDYI-?I!tcHnn)kd#VC;DqTF=Q{ zZk`b@i{9+TL;I7kMK}fqEaLfP^r?{C6-IZ2(GSj7g-bi^GsS_m%j=`Hc;9aPP7PGNkNmkBH$D}AK}eoNEsF0JiI7ovPw=TbcZPZC@1L+NkkNttn5CE zL`%=wmMFSU3iAl;%89zqn2ZW96U96Q9CMg7BSaQ!JaS}J*F{mlW&*et$>irWdk+IA>@qK6=?P-jB))q!d;)B*k`Es6Y_WMg;9uk pb@0c7ySRS`_dg7s!V`EcQ64A_l#i5-)DaoOF+&6Yc17+w{{>tF!f^lq literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/OPENPGPKEY.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/OPENPGPKEY.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c96c6585dcf4fc587d07366282446a54814b5ec GIT binary patch literal 2297 zcmaJ?&uo zWTaGzL~@{zLuC#ao3GrB&z_RaBgMOECw;sc&}IYnMhH#c$rc_vY=) z_r7`gxlqU>7(e~^$80%^&~vfqj!dF-z6Pa*Y-D2>RpJ>}q-ZTCvy#keMWRx5u$!Vc zZ)cOxC&-qTkS)U=y%*cnsyn(`Z(f&UeLXQ%at&-Ke~DGuN^H@T#G%e-u)GWBr>KH$ z1n0yRc^OS(__9_}?5r&>;fhK#Ib>_LvV_yjSvzN|;L~Wv&fA$jPi`6A#+CdvWDJD) z%U3RxuY7Xl(+f8icJ}lGj9NakEv4^FLKnyCTm|tJZlNG`t|(1(PerJSo6@=zn=xvl zD%jo}O?e&1wk9IoYoZ2vgv*8!s(ijqS*Q`>cuqh_m?I`9Q;mS~P@ba=Lk$&9-RUq3 zr*lot!wj>72J8_`fM^m~3E|??2Vg9|zFL~~YqVtf?o^{>&6+{bDBYyq&64X(mFkUP z*7r^xfA2(zI{`gbH?28yhVqi_@lu?cm(G=MlzQMCuQ!+m*%yZUY~2GN00%z^;?*L0 zsufnUZEg3%U2BJb(Tpdg6Kmre!-toZds+vgkd;CJ3vuQF_;o5E0N?~DgsKUs zU`|K1zc`5%An)=%jYEPhz#RuSTsBmhZ3bqTCsQV; zr%yT4jR;diVd6+(#%Ioq)gmflfrgCMYe` zl&dK|X$nAt# z_c!&kZT;+qKCvu6)kik#WlrP5B7!V!UGiww&aGsEwZ_m3zA0*`SdDBfi zSwd{zA|&dDY!tp14n*y@Mxm+NY#c1i5aO8d6_Vy`QP(BJz)bwpyFdg7Oe-4@3G<3=G1EQfjI?)%ZZS<8%ELaE~y7(V__~0Vk2UsdR6qy zDE}iAe@nE%*V@?_vI+JPEX5W1B#12;W867}u>Ks4{4V}>fP`~P>fNy~#y0WpHs1YG d+JlV;!>!TP(boRe{T&2LxT!Syw{S!s`3Juk__6>1 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/OPT.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/OPT.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f117e36e639a681ba96c0aeda53193731bb8ccca GIT binary patch literal 3511 zcma(TTWl29_1<~x%j@+MFObD7F^#$uyrDFtja-+2AqfblDrqHFNyo#?*gMO5XM1Oi zy_0?~zeqKm+j*r5$ph0b8LGv5)t^&KCTi&khx`X}y2H6O<(pN=$g zuzd^eZ;%|-3EY$BL|vGmIcb56Q(c^ojE z`?du#Lv!&!(>z?1Yvdi7 zkQ%KCcLWbj!P@g=R;rN#oa`Pou_gtSHKGLHN-9D|tp4kzlIfUg(X1M2wUp);(^W?u zjK$LaIKp(TsM_|Cqew9}W|o|>v9xKYoxG7|NX(DZ);P!f05M0K+|zke%OkZ~v@K3h zRx3qI)zdcUd{m87DH~s{1y3JQ(#q^x5xLXORJq70$HuNy9K#+PYu-kqE!IIR_rR~A z0{|O0&uYZ+ElWtD)h|ACA&7Q=k5gb_yUmFPXr12iWxh#k1pF_IRu6hNMT?Xy2X3pE z>_G#8caV|Ym}#3O+fhrJ;fiqLN_g&uUgnYF#(tz0r;M|VSu8`{kpJbVD7}3lo3|#7 ztY#IjRI*xLb(~7}no+uzEt*%d<%*NHN+$-7e=}>Fj&ZE4YFE_>!_I>AEYs1xcJ}$v z%UOtm!E(iosgYiZVDrB@G~uJ@f;?L4#Dvv1*I0}V8qkzCoHDjUp=1d`pT0wTHp7)Ej?wonu%bST}%by!Gk>fU%DO_;QXs>?6^{Vt6gG z_hxeCsr5+afh0!vZ^g*o)bblQ-dLHqRjDVxvmm__XINXb*3`m6h>6s20W1?pJeYTn z5%Surgv`n{xq!60Mo=DRLpA!iv~|J(Lm?8Zc4nP|R9=pEaJX{XgMBR&1YffZ`oTz{ zcsx$y4hJP~|5zdw4A^b)(2Fl-!rb6)*s+vplNqi&%^-r@knON3&2c3RC0DjEgj}&` zlz^cZF5*h#X3=mXnl)Kwh7B=nF>}H!xp7S`StS#q2#SC!LX-hFO1AN<{ldeY2*Kei z^*!nDo&`GlB7ixv*^^uzx-s;Ch|&IqGn>81nzqq#i`i<9D zp1C#fo6POZ?=O9_=Xbe!^3;0#d7t3=)L-HU?uUWh^K`4XF#X%$*Ek0N8XGM%%LE|& zfsF}bpjk8mJRu{^HV}9N%78e!P1BE*BGh;SevKgj7YOAs^%|IPmV&PyqcMn{U(sa> z-s(ed{aT!+x9KPp1@x0+%s3gr6$YPXhe4=~mrJL)O}C>%6Gq9HDYM~fKcG|1Mm+_na`5Lw+3#C@3+0%_I~?Xa`@jin&-{^DGHjTf@PMxw3WE`*^_YS#+Xr5 z9rGGGTDUsRIncpgihct~s%2m4~Ci? z)PhT7fE*__ofas$40neZ`u2=?nM_lNPKL3<_5sL*-6%6ARVe(W2{$%jIEtV0ZIG)D zGc^a&lVQhWSdHAI*D#cEW=--^p_El-LyZrq<+4%IGf`L6iZRp-pgtnifcl-7&=WpaU`cAI( zo&0#}lks}r+3VsLBA(rg_biPrj@~@69zVDhB^`TORMZoP@5e}I&-G_-o?CgL-f?s_ za`cNZa&2+B+crkU9}k`G5kKvbPp86ItpJTOU;!%FPQC)?gc@ zc-iKLeKIfO{LOhJ!hShNwnC({cd52mTa7%ylMI4k^c-}7SJ)9C$h2dtE~%4-qPQ_d znY8q&BCg|#@{=jG=%0iYMYl9SLukijtH-8SB`Ck=Rz=L?QHt60py7!@wrKyF1rOaYxDZ7|$5ZJ=k L2>b5i0ax-LL1+v# literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/PTR.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/PTR.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..becd8375e53b9fd46376bcbeb1269a6f343a3499 GIT binary patch literal 622 zcmZutziZn-6n-Z;wxh%lnkFO=L%>-?h>x~wDb!1cfC+7vYB^-x5!unNNOC~g3Wl~* z$Mjz)`7bgilP7}$9a=hcb5SQxeRq~=4Y}dH@B7}*dv}e70r+ljzPUejfFF`9UbO_p z7X(+pffElbqP%7iK?aMAMFskYG!0$>*G9l~tf~K(T*86cVObxnFLTMRaYNcj?8m|hW8O6@d7ZJq_61|P!I&TN zp(pV=W9LKLDH{T_|7)D44Y?I%xZ z(oe-vWIJc}sYoaf5*qU~ix9u;zN5HDJIdk;c2s5?L9&8o1m7!kI}!Qnznoeb%By9p+I`c(ocX*}*<4LnLo};Dpa1+tyopqv zG3lV0>V%Nu0La!o>`tqot$b*{Z%)Y0nC$$~2-*7B`h585aANF@jlGG{8XK(wP%d=P Kc7MwRHRCS|HHzv0 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/RESINFO.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/RESINFO.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55ce499a849945ba5d726455de06fd495210284f GIT binary patch literal 637 zcmZutJ4+l<6h3!mb=JoYf+%Z1h+s2C_LA6&pr9em3W*)m94=u+YkGWl~w?+`D&41W$3!_kHKd`BkaR0@}|Xx6P3Wa4*TkrZp(O zBDet#oCM$!6%CgN(wQ|J2stbu9cS=5?o(ml^ANp4#{9{G%YgFLRRrOl|j zQG5H2CaqMw>3M#~I}!=y-Gs(G&3cHp>z}E8ca89_3I?i}t))mT1C=}tMX zV1KO@hW*rQ1R^()=8IC(Gi&Kd+VB#Q&-^R0bS}3hN$cS7U#3hOA4b)3hNU8H6fs)XasKE(^iokZgySC)4wCe2I zvCKMg!4L@=IQZhfp~v_m8_(39|Jq{J!f`) z^P6veipQe}+K)f{n!70?^gB`7Efff?uR&NvI?@>vWf?lM!N>(8pJfbYhR)Sa_s7@S zSabpD>@w0h$ceoiPqdlD-)-xhZ(9#sS+UGW{2xTiXn~CmK^$s*2I_72Z-f4GmerXo zr?WaY&1U%unqqW*SHEsY{&>lkRH`T%gpWydXF9kU&ILOF*g-L7Pn3#HFssPvIIDt-?m(=|xa>OX zn;GPyneO}`+U3?6Uv!bu-sxVkd{-=4pSCi(&txRQ6YSZNfjv=C^2NNPC?Kh}QdA2D zChKBy_)^H@ZNr@M!dTZ#)waD5)}1osbwa#|qEMHj*d%3`w51mp<(yS8WX&=s%d(bJ z9j7eM8O1r-%umXtvXirl?~c85T(dsr{>`@1Aa? znyI4|{*Ks!ETnaS1BL9Q2fo(#(5(_EVSKgCbY}$4?&DhIA+H1RZuYry?$f&5O+^*826HluwyBXG4Cuy0ro&X3J&`MbW3PEnW~km<*nqg&E&DQ z6C2~rMgK}#IZ!|Mt=2evFIUe#NRB=b zM{ys_rFFIs3gHjEBRU9h6x>X}bx=Fa%l=?K^Q6? zTnA5_HXKTQDdKUOY2!H9F)2cDk*(`^A5r%cHNFd>;#ge>M!-wCPdXpO$P~5;K8!Jd zv7;S>Q2Tx8me7ufdQ&yG>ekOUE^fpF82f5w)vTXrjIS*=6Q?WOFUkIu_aNefwZqNi z*aLCQhmJ6}-8#^@VEKP6)&XeXUH}R$Jb~PvoQ$t$@~gRJA5w|)$w_E*BaaQgm#aZ%WdcAbb& zhjN6jFH;U5fK{Y0J_th>*Fq%~+XnXOMlhhb+2o2)G}^@KrBouoDt^X7s?D!1;|WN! zW$2dBljLCSR`phWu9+Mm_jI6URjv9|GjVh;azT-~Dsh>s8R#;*D7vMA86F1Pv>aPeXa&bZ@GD*qeJctEJODZ#1`8e`okV=` z;3d%v(^M3R!AY>%qy(I$Jir7*+NFu(Bj}zlqu=-x+@k(;eQ+hY^x-p}6GmE5PLOt@ zDAf1l<10@&L6BPLejB2_bv-B1FQv>Zo31*Dv@MVS+g5y!Vm62C1+w#8Wj!h3&CJe-Z~> F;V(^F6q^75 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/RRSIG.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/RRSIG.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..151dc08548395acf85170363f09dfdbf5cf67581 GIT binary patch literal 6584 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 Gvws5-mn_Br literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/RT.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/RT.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6ee879e97233fb5e3e8d78096dbb73bde6c0ad5 GIT binary patch literal 637 zcmZut&ubGw6n-P@cM2fWb4c>t%D zkxu1@W<9UeOdusGZXvh;X|8stAdZ#Mj+N8?;H}*sY$ei*64C2a@;c|C8%WOcWzK_0 zOnr@4Ie#^E{W4SITtpt{@1fB1ub;3{6iDVp{xDK`Qssrvvd@N54!ia znHs0^UhH}=-2(F!6I6Qdze13tg z*O{Fwh_36q^H)4fnn?9m)jql_ix5)W1#<09#flVktY8my6`=(YLA%t&bU4!7VOx`?CaDlNK~N{h`Y(w8 zg`-ZL3K1N1a;wD2$#+TW5AcTfzVCbAyZ3mHs+vXc9`2tT&yZgQ8NGA@!W#fR#1O+K z(r}a|4RhQYIYu3j|I;M2jhJ+fm<&D2FLMH0(GGw0*5qF<*qt0}cS%Xh__&aI6Rct+ zT9j}?!YKef#G|b>iD6ClrGgx!t9v_pTcuMq&WjE=Y=>0}sX(E$ty`SZAWNxbv#u%d zB&Ek)-ApPON||j?dW%9a=d(f@w#A8In{}TU4c+s6a>Cmu#BA0{$M+g`d%d{2M%<>y zS30_Jq#tsZu(nGa=J_4KrOE+`S1ERUCk3BIAp^i2gK&;sQge>DQluA@K!x^V)3Umr zUN?Cl0}W<`mV8!`UtJCn$f1m+ K`FCN6W_$riVUQ01 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/SOA.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/SOA.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5594281cf72163099f7762608ef5e1f81924cb21 GIT binary patch literal 3794 zcmbVPU2NON9Y21Gq9|KQ2yTAS#kBbP(U;g}$DKCc5*F>lnKJ3)~0G%?@kVcs(9kf)M_GKn) z*|6o(v`*)w`Vh^wosLFVkVY>eje(f1rfnKfW2)>g_Gm|jyJ5-uoLRm^<7KJpY2^zadrFlIPMOswj7OBPzf+lJ_ z*rIwwi)oP-i@1btQ|Z`MBugH1b>g{s5vr3|S1qi`5s%9kGI`zOu;1{uj*X1zMX;X5 zx;+JY-N8kV*Kf}lSob1%qhRD`^QwO$;Zj&=$x3Qz3ZXZtGIBx#Ld&9y-rx{&DVJWO z12u(Qlmp%V;xcOxPl}6_`p$InWm|m7{C(LX?L7nb*lyqPvRiU1SE_x(AWOvm)zYh# zC#pT)_v69eESZ(jYVQvY`oAI7;5(4o04)e{_$ND3qXE=q0yhx$mt1Q^Xl%VIcUXvP zz`GfJXdLIVZvhp$oL{3{1NO}^LQ4bW2P_Sk9)5_T*!oV2hzH%%K<-ykhv3a{~Ip>yxM?lEgbkx z6zpxdxM3F$vBwUvONZFwG+Ye=c{kz;Zqyasm@B#QwUAG*B1|rl%y`12?)Wt4iKG{h zn-?K{;5E6+V^q_|NpQwJP>c|6xd{Me{|myG zF_eA^3$ZPzfZhb@uDC4j2m{-~!0OYLe!$Cx&xMPf^nXtdl*dYAyHavRD^HiED^I_B z;q43S$qxqJAK19|(JLRmvU&Q`;ZKGEY(3&qbPwL$IGJGOM_&ko2XRMs*vFj<9e4yg zFjhU^fs;7QgKzK!aN#5i{%zPzqv|8RL;Z>WJpvYy;R9qEpMoYJkl?iB2tEy}7T(}z zG8mHJfI1opkWG*qvOz!t3IE=c<5z-+`DB9)TC_idIQv4TiUEi`jVpjdNyN*mWZNgmI`Xf*KE^rY(?=?D*PSzG%x0-aCse4G7Uck z8hnmaqr?^u6FgNnO+}F@+zVQpJWm(>jlhHr{Az$0#-RFU5&e@*J^&89PA$CfkQKQz zwSI;(s67$!N4}c4`hbx*vBrR~N0Z*;Ja=L*0pbIi;a;SC%R|5GFWVno7V!{_?ZqKM zJgeVU^%=*o3LXPupfmFMStoOoTqok<|D1`E9@a8W#)~%XT%h58K@9%j@1bBKk}^CJ zE(DwS6MO|kvQqmpRC^3XQMJ>E>iQZbza)R%U!nxu0GfM!@VA3IRR1>B|BxP`UZj@3 ZcUNAP@1~YhHAG}s5dGM{eUm@We*jZ?`Ktf` literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/SPF.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/SPF.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fd5f088b85979e8f5befd038645f3bbc8b533d5 GIT binary patch literal 625 zcmZut&r2IY6n?XtxN$X5h!zzB1)D>V?x0?!P!IM{2tqyRUWU!i&?Ma-ad#73Z-UVF z3`9qo^lFo!GoT>Dch5W(l?V0t6DO_bMcB1mtVF|9!VKTU)8z_nB0I@VPFNiJYd?eJXh%|GXoea8)zpE$6M zlr&5ClC|7IQ;rlQe}Ui}gxcEHII;CqYwDT)ad+p~MDr>VZW!@avy@dB3mjiCmYIzC zAs=`W&og#7aJ(X7Fvdfdu}jG1gr^U*6Z(R>q1R5S+i{X4r9%-6sn>1OewuW`VAERP zpm8@5Z~Bh=+4&@5%7d6jJW2bA-?a`X?qT)QsDvGrd5It{qdA4!(n2JUqx5Dat75Iy z_5DHOv^|mONVA&Mihfpdl(d~#WV3&hj3Sm9)1a}pzqgH9laaHszZx=!=&7V-7?Oal%j7&u@ zzbzf!3iz=Qcj5dxB4;kp8dYEx!0w-@0$WG(l*X(I1x^z*c9kmddQ3!`sBx=QWI@p48V@>A zk7)@lwnr1MqnlJAF^e)ukDHzSY3^rBNq;zsb=AgNhVewjT(Gg>7Rz32z9PHTitZ)m z4QoNi6>M0p8on|)Gz%X&clR&Q{hn$d7sjBi@ip{Y4xt)VqqpclMxh!if$Z^CW45S3 zRzq_54&ODG-7}Zi_Z#e+2szCQAb0T-DVb90MNKv5s!edoir3rs6mrk2Ug6{U!c9SSR~>S$4>}qkda? zbU9nJ%X(I|&ADn;Eh?^C&0g27>siy7%T}sx(Y9WkI`u-2SW_6iJ+kr4XF{ekHu2Gs4~}e# zt+5yC>~HpUM#dZOtiQ9d&>EQnKG_-n!AIvlIJbFX+ii`Xt+SsCqg~htsgYlRA#X$M zc0K4CsK%6f{sT5@VLUD~px?W?H$$VeC^D8=H;Ri=76C ztU=Y7KU2S>mN}R;0Noz~hCwp`!@h?55dDz@l(2bEREvw&HOE-e@2`M`N7T`b;IXP* zsd`Msu6PVM#^bQ2xQZ8(i~3DtzUsTb9}O8E6GI*kxLPnQFOGFnagFP+r$=AAfe(Sw zU*eFP2VsKulNB-7mR~_D{rc@X-8m>Va_hOS3!?GxJ*J45Gpg!A|S7*t6hhmFua`u_pM_eMeNd!j@b93^2C4ays# zZdr7ra}A6XG0YjuA`N+k4g z>=DAI`cWXec0XEzNFb3NL;~r9Xb|VIT@K=5D2Rth@I#pHT!n50?F#77Xyfww<@c}D z#m@dijg#vq-_O=#9cipBooPyETGFd+X}T#*x1?M>aZftZmL{9h%xvEHB+(k0-Vvq)&qw$KD}?qv|2H5Y>qqD>^)fw(qXu|Hr6^NJVv^qnAU&*? zfLy8s75?#7<3e9TH5oi(U|#62s-g^?Ut*43xRBvJ-f{7w>Uu0mw0$Rd+zqIKy6=k+ zzU;hVg0EG(T*12I_^$Mls$$uep(-XpGhfsd4I<-l91GxK`G$e@C{g$LAH($=(m|e1 zk2J>D$8Z09WBTL#o&5In-!J|3QY-y>o$mMG-pd>=m~SJD?QU=jfL$<7(tixV{U z9ymql2u7J0CP{1$I{1o$9UTV%d6DSIHTD$F9tej0m?v*32P>AOJy_TyKTnpQfo=tT zl^$uQk2llDH%_QS`+~%n}Gn?19mAlnXPPB${J3`KXTTG%b z!{o2#q08qmDX#GU1V0UyQr6+8Auh{hTU#^3J@;`kY2>Lhet3X0v7Q7$jK%ABcVV%iglEU16J+9&a5i|pb9e?=az`fz-7Z5>RQD-F4S$JJU!#LxA|MVzp`y8WCw?{2rbe68 h=tFvvx=7W3a%*O7=GK+9D_ul*xFkLHjj!_U{13P1n0WvI literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/TKEY.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/TKEY.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93a47bc07232f0affd34741f5ee7b837ea9d1b4b GIT binary patch literal 5071 zcmbVQZ)_9E6`%F`&#wQ+vB@7G34u};l(-N`f$IU$;ruyrP9cGUOW8W!P2%9#x!nz5 z%Ry9%bdK6n66sn8aTU2%C#TP?o!3tL)D$`Q@*8yN+&+`&3e~1 z;DGKX$?Uv&Z)V=iy!U?d@=u4uilF@CZ~qxv_aO8&CiG&~8XNCJBa1l15it}heu)sF z(uNSJevR7K6e4+Y+^$bi{#NTVpdQ2-W)O$RILoqnYuv53%HI{T*j~}PLUfW~jBA(* zMzlWlqsgI-Ye2pO`tu0u95Qf3h#ZA=e@BFjG~yf?G(vFX3>`9YPR=+(gv`7J`dyr9 zhA6Ev;G`RxfaY-Xq zKQ}B(7kew~w6Q|AG}k7wrV3fzn((}?${CC8w7Ei-()pXnS}J5qI)bcEA6NdSsR~(} z-oJ?~T_Ibt;Uv041Q~~75~n74L7`YS8jngW3$3uo#={f5fX_j(Vsa`PmpTpzc(a1# zncn@K0^XK@^;KvrJDikwQNZ^pV8s}lViftKB&><2FHc#XraTrV zWYsY&E8@9YO6A7We*aivg7-%fvEigYG8UGkr2h&Zzv7QYhy9aDX)F=%Xy1RpFGeN4 zZ899W93JIGKNlDM#pOkRSMY)#+i&}1QovW2a;TJk5PP6YoPp|_8|XMi zkC4XMvI)m@3#HAs&~NP!CXGP>$0!9oC0ZDJ(VuD}V~K>wvjQK3ka#63B^45^QZWf! zSPFx?4TnX(^I&u&srrFJ#`w7ENYz}0u?Sd0#SBq*Gzy_spEboRhRav6^Xd_~>{TN6 zV9k?+dOX}BC0L2SCZ+7E2WZsmz!Lue)eWSXEK6)KRCR6T6X#~WWnvpm>$`j8EGyxoBHMP#nb z%hJcGJE?p7<;K_Z-q$mMf@jImC|_YEBH3xSMETYLLXlZy>#BRU9#yb zR(qMG?WJ8hV3jI(#g#DgrRz9fv5vE}j>`a{35T4tB~7L2v^8x@JJPNd4c}BJ>1GIF zC+I!IPzp85ODg&(R@^EAPr1Fnv)C3TAsQc5%zQk89~9=I(@aJGpsz*~HjjdFZkijG8iL1f$t8HtGkhF1Z*vqDOV%R(zAu{w&Q>`knc*c7i6 z3%p}kSh?u1wECb?X?z(X4m%~>ddBVO5g{>AbUdf(cqKZ32mT4F8)%(EzWQwcLjQ7L zzF+np$k5TJq8n-;Ii&hLZAdh1rbNAlhy_jbwN&lUmw<~0yz5oI>74mX6wCtmd!@J;uN3NvI_X=TD_>{Gy8kVNRCvW0^6$J zGVJOlV+oXm9{}8^rX78Ky}c>Jj$Rdeb=buacQqO4;X}v(8njDNi*mN=xULuRNqWaj$jO`pE8?J2iW1(X!K49ZFkSTIs4|klua$9{%vnvCy~ARMQ_JJPxbac zLj%0MD&m&&t!WcTXdVs&N(G*A8d6_0;%D5nWty7a1Y5Z>dT)ZR6%w`*BAL?WGAdOd z5*ZYJ2zspHw;MwWI1wnJ4z`&Zj7*WlR9xb@V!8uX_5+ZrfD-d$SP&u2RnY`nL{%HZ zaY=pLi8vN%;80;ZZWR%*-Ll2`Qob@!RCEu+XmK2>8|X`Kqnc?z^xg;f(AKfqb}Zj^ z?A}$mttU_I&Cv5l!NSfKaQxq$ckW(#TXr(|#Rg%1Pu{&}x#nAh`89k+LKjqP_zKtH zrqCMREg(96iZO=Z5JLusf=~s!1)LKwH8|JA`6c9+bFgu)Tr){hl6V2eoR%j0vc|nL%F}eUFV)jrm63 z`X1!+{Lfcw+PAUSY#Jlh-N;(aI2Lz?OfjiVnA#C1c4eF zb4FjGsX1rdTEOisg~qKpV_l)vJJYY8y}CYQ722>aN1;+2kY}{>*R=o32u-EF=)}a7 z6ds1aMq`m;CPJ5bg=Q>iI)f7wb=EtmxB^Zdo>D(5Ug0s}B9QnH#BQk8NrE6Yb|J#~ z1#*0iykFp7H^eA|>7DJr*uF~C=ZX3!h8RKkG97bAW{=DrpFO^TFbyYQX!x(nQrG!E DJUjEd literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/TLSA.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/TLSA.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1ff293ca3d7b90bab40b0f1a959165297ab81e5 GIT binary patch literal 631 zcmZutze~eF6uwJh8ygFPKTt$;w1YjgV@0Toi*(V&EJvC{#r#xIkC_y#}^F~qQg zOq^uJ#2h!r9peee|7i-^MNBzIOobW!S91!R$qs+@=G0#<*_}Gl?$VN(jc`GGldNi| zT9R-|;%NXq#FMQ}g<(^Tl!7X>gT4LAsUhR4&u!0V)q*B;N?Vr8DHSfNg&UsJv&e1L0l8 z7{?ie=bq5gdj=^J7t3eM1H9133!e(c^OvRT@>O|Ytn`hQfw9&%)?x&5tRiLcLpCHH EUke_M^#A|> literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/TSIG.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/TSIG.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2182419ea69193b9ec8523fc571f63866bb78774 GIT binary patch literal 5891 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 O81`#w{tA<@s{aCR0Fk}` literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/TXT.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/TXT.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b38010ecaf6be804bec6954e55f0fc8ad551b96 GIT binary patch literal 625 zcmZutziZn-6n-b!k*vf}NF3aPq0nY9AwJryB~ULN0-ijiS`Jxv)OIAxm1I+OD;U~L z9i4w6#5#8Yk#!RJPm~#guGQRq$Xnn#}|yH7Gr+E zN1ntxjJ+Q@ULG+R2)(LV{N17 z`=i+Dcp}x2rn^$h`!(VZamR^7T6#z_@>puDg9c7)V%BP8N%mJmwh=AOSF6vTgu6)P zC8H*qg-!^`o&%};fG3MB(2AdHpK5bbossIDMo8t$v#*C2hjXhovubneU}ha;fO4jT L_V}+%P&4iUG-!-+ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/URI.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/URI.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..098b86d3313474dbb8efd88e9129f0d213d27baf GIT binary patch literal 4150 zcmbUkOKcm*b(YIrE`Op(%aSZv79Gc*ts}8=+BB|Xr-ox&a#0qDRRd)TELPl=yfVpU zW|y|fP=Fc;h=jJVg`$v)qKFa{hzwusUfe?uMUU-;3|+`w8%2ur(wk!W5ct&h_D5Qw z)1m`u_RV|q=FPl6{~C`+2#mk{`5%|%C?TKY!W+B`Z2t~`HKGuODUh7=WpV*~&AQ*9 z`{mp(p9`pg`5y16_O}}@K+Y2-utXFVczWA=J!n4VLGAAy)`9Q1s9dDVh{1p3Dw}t4 z_Q%~vw(GF`0N$aJ9HS7pCzE66K<>v(F1So)8HHUEa-0%Xf=f(}S3_Z<#1w9c@g)dK zT;YKhRzpgU66&Bt6k&Or)m+H(p1gsR1sNA zC{s!4Xnt>VZ1} z2gi^)nFrj?QD^S}KaM()JjJ|dZ?I!8%C`rc3vRM=)H#LmWW&k+&CP_j6Zm2t-2QqJ zl)#lEgwRpbH-0yh&+l$?dXVD3=zXS2#9(%sq7G_9sANZx>AYMj>1KLHO{>MSSrwxe zXH?26wFF7hN}4H2Rz#8wsiKw4iPtFVjp7rmFk)w_rfSd-uGxZGvRI{LSYh>gUM-uN zUb3PWt0hyuewI?5iXkg#sD)W8M3sC&HVliWidh9&{-MrEVM(&-l4Rh?X?rX_`+nxK zUQ{!Ay)aYFLGKOZVV`VviMV?cQ3|uJVh%qv! zvp>$j)5ptIih*5=;lg|7%YZS;Fnzg59t-i6P*WJZHL!l@fgnB{-1}kT*NF|GH8`;x z{CWIg|InJgs;|$s`o~}$#dT@5w0>!0y48OSF^{7?%jS*uR^Dqxhi|?6X*B&fwR>%B zb!;@y}^3#4Cq#=Im3cN&LRAUG8j2uV1|;vqcew2~E} zYC$%&t6EL90#~jXC>-rZ(JifzsY|AM-K<4Xv*(RF71v>&+goo_+dP60G3GC&XeSMmXm-xxLRnuCOHNnzek8a^xd zftM=YX9D0YnC$AIx&V^=l+oUO$4z};=|HwN+>r{36~j&ha@sJdR+ zwNtC7TDuM`3lEchYx`FBwUYand4va62Y$I{GkKtyJkUxW1YB?bX7Ax<@8Ji%;>M-T zvD3}5(+|eZ?1`++tM?0Bo$2OXbc_%*vXSITA33rN%T2d|9Vu1@HjrnnqPOQ@gA0!Zzb|m>^N_+Ou|I^~9~py7Ec!qvGA^`|sR) zr?u-e*jR|&h^$0zzWzYi>%rf`y?+x%ziJylfBX@SeNp~s$JtLH>n?B${MYA+g^=mn zzaH{*>6dfgj`IQt-7&f~5}oDTk8`fh!`bba4rb|5ILj&Y&qcRfO+Q!Zc_+GETgEZc zsalkw=$Gbb9MAA0*b1N1Y0;_T0zHJZb^!BbnHmu9b`0Z1?Eb*^0s~*>Skx;8y>@t_ zQ6-13*5?Op2lh>1G;%O4lFvpCY>tdKN5(fQt&wjxg`wrao3A}g4s0e5Hj@Wi$-~P$ zmfNd8J8^6Or_o_QO2uYMY<%Nd1OO+FW;t;ETOaMJMtKMFw>!ghoX0q~-_dy(8` zvLa)<{cXnfaswINQr9Pysh$T{2Q32Vw6C>6d}BAtdEhj#`GX>u*mYxJWuYPL{l9NU zPrylVH*_uf1|HpUvs9;ir=70#@4T0TzksamTR|Vw5Qdyvb@m&mD_dXF0Ccw9-7?z$ z+K<%zYIc!0?+MzDh;1Wwi-r}fm|DTw<6ITWPlv+cXM;+`fbF7|fXPUnDX0cs+H=6< zSN7L-@6>{Q%nwirBQPzJeWF?q#?sW{W3$^0Oaae8LLcTg)~KtF3?y_OV=gD+22$0ggN6QUH3=!*C-{ zc!nFcdA|rS9^+FRw0*~xE=DOfepVbB^Kt)&f@5JDrxlA86Rl3f8K ziASXO5gCBD1h@}Iemb(r3^tj;r-5V4Tg>u-8{&$1BeRm(Cb$MA1onJklWbA{0a^-Y AhX4Qo literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/WALLET.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/WALLET.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19d1a833bc81895c84742497bd313801f6cc009c GIT binary patch literal 634 zcmZutze~eF6uwK+rfLI%h*c0e+QA;`Dnbk5B3&$~aX8Z4VOx`?CaD-VK~N`0{TIal z!cnJAg$NEhxhZjS@?CzQf;YVPec$_e?@`mn5xj@{=k^o$S3$CuPeF78;2JT+u!VFS zdr8L}ce9MF0{nlPgfk#B3YBACM#7J0e!6Mf!o?c6uw-rDfuEe7@K> z%mZVedxZ5o;xa$z1Fki8NwP|{AGiuMjg1Px%YivVFUpK7t`zH)La0G`wPV`@-)LDp zl!1m*LQ7j#{bRpncsv~YNmA2TsAfU6xznseRx)x-^pA&30QxGdCjVb^r+|u+dUY`G zGR8Q{BRu_t=HBy2%AL=j&W~_uh)bUm#-)p`tG&y;kyajR<&m~9)D|KHawH>Z_CqAZ GGrj?^jE_wK literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/X25.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/X25.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7238729fa4b51cb3a5b9c319aa47bb47baaa2d14 GIT binary patch literal 2339 zcmb7F-ESL35Z}EI`|Q|F>O@US6-WyWTxnzIC#6-zht#Sd8I@EBEX3(@yGbrN`^@g8 zsUx=vC{i7ys)g9BP3SI?99&W z+|JBz#=m5YF|BWaX&{0Kc2F_gaZw6nIog$ISBnyrCNi;$ zjeV;#37tVinnOf}JKHvFV{W1=<~H_aIpmk4qhhLxb>+U03RZL$Y>^#meFEYYct1r& zOc1;i7iA)i;i6JUqnOBZT2Un$QRZ+lL6a#&Qbe7@ZT}iclLY8eG)X#0a+4-qM;CFi zV;JcfU;gmmk(o4*0Y)vC5kF}X!YJpKvguJO@Zw#q4}g4$8^~)b=am||3i=wZNy}1r zj8P3uh&xZLnoKZ};!M<#5yy(ENS6!e@BafAbj4TsRE08MGYq?Idxqhs43irpRgZEe z-0G^YaO#ZuNk%NkEIIYxP&lzBdM>-KFUL)hALH-0=F|6A;eGI z11EeC$fH^GhnAgBu4;WZdY1-oYx-L6uEyoX%S&TFc(;a|y~pdy)l3U4k<|_#1d;9z z_-}m-1VD>7Qall*6%_5YWqQ#>4E?qpLd!T5z6PJ)%20Hv@EGrbEXxp_uQFnKCZvpV z%dFU=)iA*d6L0YoF0;q%vY%$uF+KZ&JwttIa+-_BinOv$VU_e;!=o3ynGTVWND#T; zdx6ZNwe7jc|(^p z$#w{_SBTk-I1SGYQo%J)h*-r6<4uh+w-iR46%n^Z9ZdOiKxWaphPrn&h8BmKxjpsd zZ=Jb?V>eDOooRONU(xo5(TWJ;CF&ML`y(*WUhP<+k+!IQT+>TxO(l2|EV%Rz`W)Br zR=Dumd7Ti!Jh6yWQ{%>s{Xzq8>FK{;gTpqykKpw)((bpQO=X9c!=_ZH8FZGPfJ#hR zp09`&(-R@glu_HEzGk_liWogXLHvwmmff;#nT|jmcDTqO+XO)XKcq3>!CPq#`o)L<(KpxGZzmy(mwA`+Rj@h#h6-j-) zrD658^#n?6zx&?s16ffIv}6$0CBbDBb@zG(#0QcTDIN+gD0ARjPqV$?+0Vjg9HJL3 zTJdbR%(~lWcBwSwnInMKlrRa0XJ8G3gq8uR%ultASdX};QuBHza_@zW9?*^ z#Z&AQh$2+}Hjs50W88WH;cfR&&+p=|3n;`SsaN_x>%W8hR&n1$=~;ZB{`@t4UcZ)~ R&$keW5UI5DZ=nc2@-N*<`85Cl literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/ZONEMD.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/ZONEMD.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ffa318e2968de1628edccfb88cdbf9aad3b34e7 GIT binary patch literal 4180 zcmai1U2GHC6~5yckH>aQGTGE2K#a32yEQ*C5SFD1B?Pgd2rv?%tnf;mPG%Wo_o&yn|r_Wo#VenA|V9juYdk$=Ep&VzQ!H5(2lV683vvi@O-f{Thr3j>ZKw00^GNjFK_Z^h)g0?e*=|>AwyUHquR5x9s7NHJXSkOV z9G%rT{?%lT)zo?}#*2Mw5g{-eJ;ykrAmoM4FZYK_mu6{~f~H zfe}L@4Yr^Z5W`}i#S$u`TSPiMjuIUvoxYNK|MI!Hh~36DNl-O0L7B8JX)>RKR>(+s z$=s3Q^$f@7rc_NfGI^60332X)AcJwoWi1f6J-f|zyxPaTZ0C1%C8}j@Z^hTF-R42tuI+DG z+dCtCV{e;t&@E%Vv%}anfD(!6?<1szjot1#qR*Xun}9hzuD;o}nd9B+ZQ?cGyQjI1 zD`oe(QnPKpvwO1nKq;UN`A{j~LNXg~&UGbYpDV#?J6*Zix~omUx9tYJcf3s$Q4uBl zDGkGFuE)k}{778Z0nLEoSxKDR^D26`SqE(_$8(Z0Wn|*ABB+Wk>xQHl@tc75IG!E% zu%GBQ{rXHn(oBZqWJNYO&J1z9&dtb*F?3ABkTywC(aqqTsz^q$An9huIm=(hZEvFT z{IsND$}-=u4L^b{MqTgHuveKuyyisFkaP_r)eK$db2HNWnx<-ICug6T!$*@e-6W?Z z!wfr0U7nLn5H2+Dy5gVz+MjEkmwb;3}pVVX5 z%G6_KH(p4oC@a_N(L;Ff({Sg~@Z#|Dz?b3OPrCQ4_Yc*(kFR$e-w1a$!m(O7wgQ%Y zmSwo!eQLeq)PLBXYJa7_!4B5g!3KM*#vXfgyfHLZ8yc&#=gagz!fa*o<4hxZpcXyw zd*RVre?0hygZ07FUq(+`%UrH^r`9`Cn4c-iJ8R?chS+tj#SCdujyL34>?XeHn_3)<);t|w zj170KAI}IFDrZQyjJX{SN}c{&VAN-!T0k3-&ZQ3)KlsIsvTvg&S{ph3c z!6yKczDi$%eY3{CxzW+JbaC9sSw6X8drqv!fX7$VTCXL?ctsbl#yxYIbuTY)%kl7B7MJc=7PW zyv7~`yN4#&&?)ZxY-?7q%{Cu0FsWFj9lA;(m-FXC5IwAnp%f@Fr65=w+>WD%L@Gg> zAs{`6bw+ws!5IYr zlmi4M!X_!?bgd80sO^HvBn@?1dk1$R5F<0vgb!z^+puQi2)nLBo4B#T6r9x5ybY%= z2Tq>m5KPb~pjtqi4C?+~HCBn$*?}^>VH3^AdqM2m^J)Jl{rC2kcYwCRo~W@W>g>q| zJ5pmu>g;Hle!>FS_SM*ZE5JerfrUD|@4mM@wlel$>``iMtloKXow;a(r}g>pwOZi$ z3kZ<5c~p~i$OF_~0p~|qoa${Wplw4dplw4etIm8N4Rem(;+8vu6_l$YKp`MlehY2c>NfX8LbJN-J6K<`M-pDdzwjf|@Tt4xw9bZ*~a0 zqAIe$=P+;38HpDGO4}KO0Z4E+Wld@ZHb(&|4ARd)1#yk-t?sVuUcSC^?eh=se`rUC zFO0^>)!N9_Iy(+;&2%h<7DIPWJZ5&e^6~0$W%%#R-tRu3=lum({{MNuNVwMn*WNZ> zxc)m&d!FYPeb^65evx!q%4+GRFXG(mB#f~VuxA)M&oFEfKHEQ?tB+v+^qesPxgnm? z^f9`?YmlwkZZ?8#_@i{pB#tB5PSj<&48CB_o-xPsz#Q+WO;R14@S+iR2X3OLLG z>{QU-Ut{;z*>_sKaK3ZRC>O3$f=&T&@iqaA=X%&?VfP$Y);&d7xQ1m0o>Z;A+k5bvG*l& z%D&Ij+I8sSkJc|hwMh~Lu|*@I>njxb8ufgIK!CajLa5LGQUA~T8^q2UvGav*h`3CY a$Cqv_-dN%mxh;hI@NK@nZ!D9w)&Bs!7ImHg literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/ANY/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8617534bae5989160e5bbd513765af3d3e220c4 GIT binary patch literal 573 zcmXw0&2G~`5VpN`QUz6lH^>2r=0H{?la8kSY=i&DxN~mqqw`72 zw57JvGX1l@)Ei?!K0LC^q^kN%Ecz#_e3S2sbkN^tNuBiTW|#NVHl0_)!3ZoiQO+AU zVty2Ik1c?(a~p`w7<6q%sJ9#`+fiWJJdD7y1@k!?wGb$CymPRe1J$_`8>qFML)zkJ z?&%9?&9hT5r!v6Q%oURW$jp^sy2=qiyzvMD6~qf{Fgyu{KL;XQ2P|<=8v^?a#cd=I z$Iv?%$wKBsFOXcwsXR;?$OwpxLgB;JzaU26W>%+oo~cI!Rf^@fYo;UCphe z>+NMu(`|Xt&~%;DbwjW7%{48H3%YCS^>#BJJ{i%fsPo6WBz>Q(@``4gite*^Llwo{ y(Ks$PMIFb(U2}W%>T;W1mHF%aOKhOytsWr!BD${seyHigKj!$J@v*yqj_hAp!?T+J literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/CH/A.py b/venv/lib/python3.12/site-packages/dns/rdtypes/CH/A.py new file mode 100644 index 0000000..e3e0752 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/CH/A.py @@ -0,0 +1,60 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.immutable +import dns.rdata +import dns.rdtypes.mxbase + + +@dns.immutable.immutable +class A(dns.rdata.Rdata): + """A record for Chaosnet""" + + # domain: the domain of the address + # address: the 16-bit address + + __slots__ = ["domain", "address"] + + def __init__(self, rdclass, rdtype, domain, address): + super().__init__(rdclass, rdtype) + self.domain = self._as_name(domain) + self.address = self._as_uint16(address) + + def to_text(self, origin=None, relativize=True, **kw): + domain = self.domain.choose_relativity(origin, relativize) + return f"{domain} {self.address:o}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + domain = tok.get_name(origin, relativize, relativize_to) + address = tok.get_uint16(base=8) + return cls(rdclass, rdtype, domain, address) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + self.domain.to_wire(file, compress, origin, canonicalize) + pref = struct.pack("!H", self.address) + file.write(pref) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + domain = parser.get_name(origin) + address = parser.get_uint16() + return cls(rdclass, rdtype, domain, address) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/CH/__init__.py b/venv/lib/python3.12/site-packages/dns/rdtypes/CH/__init__.py new file mode 100644 index 0000000..0760c26 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/CH/__init__.py @@ -0,0 +1,22 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""Class CH rdata type classes.""" + +__all__ = [ + "A", +] diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/CH/__pycache__/A.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/CH/__pycache__/A.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55b9054320396228066651361123a72572c65a39 GIT binary patch literal 2532 zcmb7F&2Jk;6rb5IukE;wt)}@bO~0Tf)b2-00ad7`C@oSIB-)b?ESud;V%PSDnN3os z8yciYbtI}Z639js!f+u~IP}6lz!5HqT$He_P*G34g}R5TC*JI?oe)|iM)I5Y=DjyN z{>|^b`85&=AsD~>{KxoDGD44-(HxSmG`<9-f^?+gJj!@8&Ty{H`?HYY3@+#H)lB#1 z7p-yVJkq&&r1Ox|_Rn}`<#zphZH4#NR(w}RF5!gmm|4S`zvfPU9BO<5<_$QvfihS} za88`zbx!BUxQtLnqgWT_<&3BYb#WeNBqI<+dPtY%@zxjgupR(k&hsw?fHJR&97ijwUN^N>nt|d+ODP8!nWAfUL|w~4<{r? zq>~c{ab!iw7PFS3fTmKVs1^*uVx15(C$mNC_?v_=15yNPHs+pRNR68XBc+-7ky1(p*6A@2b7&*h zQR!XkU5_Q#V#(#HyEFCJdu3@O-gT?18mkU|d*g_3TFc!%T92J&7WOUasgD_M^pDF=R! z#&4aj4%LR1&)1`atMZ@+W~&ZiqZXLogYkbEXY)|sK2R80t$lNB*B!&3dW;HdR1vU%d8Qp3c{!PemSME35qWATaa7g+N2w=0^wisRr zugkq_a&Pth+L7gf<1RD%a&lVjR0UhIYpDfWcpeaBO#xr}hP zPXr0;mrev8pk%gCn6%UpU@s4*6J%>dS1r|vdn$VXX|iyYH76&y6)t*F;zuml-S-ai zzQLOGbNP%1;JYLZCi|E4EQn1WW864|@UBN__kGlUA9Zg^h!bz@{e15_?pniLPq_p5 aRGD84E(8~&3(*Dw6aF68{TFk%U-%mo0wUf3 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/CH/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/CH/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97e07568cad3aecce97e0f78e1af9d07e713d7f1 GIT binary patch literal 251 zcmX@j%ge<81g8$&%b3l~!0;HvfdNJ+<1-(SF`XfWA&Mb|F`Y4rv64xXxk|=4C$YF# z!P!HhC?&BZQK6)=AXOn5B$isNcZ<={Pm}2udwhIKesX*~h>@6+6Cb~l;WNn4U;g?T z`MIh3$@w`+mHNpUi6teK`emtkW%@apN%{qqB^mj7#(IWE`o)SPV#fU}j`w{HVgf WsQiJAftBL}GZQycBYP1WPznIgi$=Nt literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/A.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/A.py new file mode 100644 index 0000000..e09d611 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/A.py @@ -0,0 +1,51 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.exception +import dns.immutable +import dns.ipv4 +import dns.rdata +import dns.tokenizer + + +@dns.immutable.immutable +class A(dns.rdata.Rdata): + """A record.""" + + __slots__ = ["address"] + + def __init__(self, rdclass, rdtype, address): + super().__init__(rdclass, rdtype) + self.address = self._as_ipv4_address(address) + + def to_text(self, origin=None, relativize=True, **kw): + return self.address + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + address = tok.get_identifier() + return cls(rdclass, rdtype, address) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(dns.ipv4.inet_aton(self.address)) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + address = parser.get_remaining() + return cls(rdclass, rdtype, address) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/AAAA.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/AAAA.py new file mode 100644 index 0000000..0cd139e --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/AAAA.py @@ -0,0 +1,51 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.exception +import dns.immutable +import dns.ipv6 +import dns.rdata +import dns.tokenizer + + +@dns.immutable.immutable +class AAAA(dns.rdata.Rdata): + """AAAA record.""" + + __slots__ = ["address"] + + def __init__(self, rdclass, rdtype, address): + super().__init__(rdclass, rdtype) + self.address = self._as_ipv6_address(address) + + def to_text(self, origin=None, relativize=True, **kw): + return self.address + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + address = tok.get_identifier() + return cls(rdclass, rdtype, address) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(dns.ipv6.inet_aton(self.address)) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + address = parser.get_remaining() + return cls(rdclass, rdtype, address) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/APL.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/APL.py new file mode 100644 index 0000000..c4ce6e4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/APL.py @@ -0,0 +1,150 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import binascii +import codecs +import struct + +import dns.exception +import dns.immutable +import dns.ipv4 +import dns.ipv6 +import dns.rdata +import dns.tokenizer + + +@dns.immutable.immutable +class APLItem: + """An APL list item.""" + + __slots__ = ["family", "negation", "address", "prefix"] + + def __init__(self, family, negation, address, prefix): + self.family = dns.rdata.Rdata._as_uint16(family) + self.negation = dns.rdata.Rdata._as_bool(negation) + if self.family == 1: + self.address = dns.rdata.Rdata._as_ipv4_address(address) + self.prefix = dns.rdata.Rdata._as_int(prefix, 0, 32) + elif self.family == 2: + self.address = dns.rdata.Rdata._as_ipv6_address(address) + self.prefix = dns.rdata.Rdata._as_int(prefix, 0, 128) + else: + self.address = dns.rdata.Rdata._as_bytes(address, max_length=127) + self.prefix = dns.rdata.Rdata._as_uint8(prefix) + + def __str__(self): + if self.negation: + return f"!{self.family}:{self.address}/{self.prefix}" + else: + return f"{self.family}:{self.address}/{self.prefix}" + + def to_wire(self, file): + if self.family == 1: + address = dns.ipv4.inet_aton(self.address) + elif self.family == 2: + address = dns.ipv6.inet_aton(self.address) + else: + address = binascii.unhexlify(self.address) + # + # Truncate least significant zero bytes. + # + last = 0 + for i in range(len(address) - 1, -1, -1): + if address[i] != 0: + last = i + 1 + break + address = address[0:last] + l = len(address) + assert l < 128 + if self.negation: + l |= 0x80 + header = struct.pack("!HBB", self.family, self.prefix, l) + file.write(header) + file.write(address) + + +@dns.immutable.immutable +class APL(dns.rdata.Rdata): + """APL record.""" + + # see: RFC 3123 + + __slots__ = ["items"] + + def __init__(self, rdclass, rdtype, items): + super().__init__(rdclass, rdtype) + for item in items: + if not isinstance(item, APLItem): + raise ValueError("item not an APLItem") + self.items = tuple(items) + + def to_text(self, origin=None, relativize=True, **kw): + return " ".join(map(str, self.items)) + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + items = [] + for token in tok.get_remaining(): + item = token.unescape().value + if item[0] == "!": + negation = True + item = item[1:] + else: + negation = False + (family, rest) = item.split(":", 1) + family = int(family) + (address, prefix) = rest.split("/", 1) + prefix = int(prefix) + item = APLItem(family, negation, address, prefix) + items.append(item) + + return cls(rdclass, rdtype, items) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + for item in self.items: + item.to_wire(file) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + items = [] + while parser.remaining() > 0: + header = parser.get_struct("!HBB") + afdlen = header[2] + if afdlen > 127: + negation = True + afdlen -= 128 + else: + negation = False + address = parser.get_bytes(afdlen) + l = len(address) + if header[0] == 1: + if l < 4: + address += b"\x00" * (4 - l) + elif header[0] == 2: + if l < 16: + address += b"\x00" * (16 - l) + else: + # + # This isn't really right according to the RFC, but it + # seems better than throwing an exception + # + address = codecs.encode(address, "hex_codec") + item = APLItem(header[0], negation, address, header[1]) + items.append(item) + return cls(rdclass, rdtype, items) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/DHCID.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/DHCID.py new file mode 100644 index 0000000..8de8cdf --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/DHCID.py @@ -0,0 +1,54 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import base64 + +import dns.exception +import dns.immutable +import dns.rdata + + +@dns.immutable.immutable +class DHCID(dns.rdata.Rdata): + """DHCID record""" + + # see: RFC 4701 + + __slots__ = ["data"] + + def __init__(self, rdclass, rdtype, data): + super().__init__(rdclass, rdtype) + self.data = self._as_bytes(data) + + def to_text(self, origin=None, relativize=True, **kw): + return dns.rdata._base64ify(self.data, **kw) # pyright: ignore + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + b64 = tok.concatenate_remaining_identifiers().encode() + data = base64.b64decode(b64) + return cls(rdclass, rdtype, data) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(self.data) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + data = parser.get_remaining() + return cls(rdclass, rdtype, data) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/HTTPS.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/HTTPS.py new file mode 100644 index 0000000..15464cb --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/HTTPS.py @@ -0,0 +1,9 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import dns.immutable +import dns.rdtypes.svcbbase + + +@dns.immutable.immutable +class HTTPS(dns.rdtypes.svcbbase.SVCBBase): + """HTTPS record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/IPSECKEY.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/IPSECKEY.py new file mode 100644 index 0000000..aef93ae --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/IPSECKEY.py @@ -0,0 +1,87 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import base64 +import struct + +import dns.exception +import dns.immutable +import dns.rdata +import dns.rdtypes.util + + +class Gateway(dns.rdtypes.util.Gateway): + name = "IPSECKEY gateway" + + +@dns.immutable.immutable +class IPSECKEY(dns.rdata.Rdata): + """IPSECKEY record""" + + # see: RFC 4025 + + __slots__ = ["precedence", "gateway_type", "algorithm", "gateway", "key"] + + def __init__( + self, rdclass, rdtype, precedence, gateway_type, algorithm, gateway, key + ): + super().__init__(rdclass, rdtype) + gateway = Gateway(gateway_type, gateway) + self.precedence = self._as_uint8(precedence) + self.gateway_type = gateway.type + self.algorithm = self._as_uint8(algorithm) + self.gateway = gateway.gateway + self.key = self._as_bytes(key) + + def to_text(self, origin=None, relativize=True, **kw): + gateway = Gateway(self.gateway_type, self.gateway).to_text(origin, relativize) + key = dns.rdata._base64ify(self.key, **kw) # pyright: ignore + return f"{self.precedence} {self.gateway_type} {self.algorithm} {gateway} {key}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + precedence = tok.get_uint8() + gateway_type = tok.get_uint8() + algorithm = tok.get_uint8() + gateway = Gateway.from_text( + gateway_type, tok, origin, relativize, relativize_to + ) + b64 = tok.concatenate_remaining_identifiers().encode() + key = base64.b64decode(b64) + return cls( + rdclass, rdtype, precedence, gateway_type, algorithm, gateway.gateway, key + ) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + header = struct.pack("!BBB", self.precedence, self.gateway_type, self.algorithm) + file.write(header) + Gateway(self.gateway_type, self.gateway).to_wire( + file, compress, origin, canonicalize + ) + file.write(self.key) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + header = parser.get_struct("!BBB") + gateway_type = header[1] + gateway = Gateway.from_wire_parser(gateway_type, parser, origin) + key = parser.get_remaining() + return cls( + rdclass, rdtype, header[0], gateway_type, header[2], gateway.gateway, key + ) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/KX.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/KX.py new file mode 100644 index 0000000..6073df4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/KX.py @@ -0,0 +1,24 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.mxbase + + +@dns.immutable.immutable +class KX(dns.rdtypes.mxbase.UncompressedDowncasingMX): + """KX record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/NAPTR.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/NAPTR.py new file mode 100644 index 0000000..98bbf4a --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/NAPTR.py @@ -0,0 +1,109 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.exception +import dns.immutable +import dns.name +import dns.rdata +import dns.rdtypes.util + + +def _write_string(file, s): + l = len(s) + assert l < 256 + file.write(struct.pack("!B", l)) + file.write(s) + + +@dns.immutable.immutable +class NAPTR(dns.rdata.Rdata): + """NAPTR record""" + + # see: RFC 3403 + + __slots__ = ["order", "preference", "flags", "service", "regexp", "replacement"] + + def __init__( + self, rdclass, rdtype, order, preference, flags, service, regexp, replacement + ): + super().__init__(rdclass, rdtype) + self.flags = self._as_bytes(flags, True, 255) + self.service = self._as_bytes(service, True, 255) + self.regexp = self._as_bytes(regexp, True, 255) + self.order = self._as_uint16(order) + self.preference = self._as_uint16(preference) + self.replacement = self._as_name(replacement) + + def to_text(self, origin=None, relativize=True, **kw): + replacement = self.replacement.choose_relativity(origin, relativize) + return ( + f"{self.order} {self.preference} " + f'"{dns.rdata._escapify(self.flags)}" ' + f'"{dns.rdata._escapify(self.service)}" ' + f'"{dns.rdata._escapify(self.regexp)}" ' + f"{replacement}" + ) + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + order = tok.get_uint16() + preference = tok.get_uint16() + flags = tok.get_string() + service = tok.get_string() + regexp = tok.get_string() + replacement = tok.get_name(origin, relativize, relativize_to) + return cls( + rdclass, rdtype, order, preference, flags, service, regexp, replacement + ) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + two_ints = struct.pack("!HH", self.order, self.preference) + file.write(two_ints) + _write_string(file, self.flags) + _write_string(file, self.service) + _write_string(file, self.regexp) + self.replacement.to_wire(file, compress, origin, canonicalize) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + (order, preference) = parser.get_struct("!HH") + strings = [] + for _ in range(3): + s = parser.get_counted_bytes() + strings.append(s) + replacement = parser.get_name(origin) + return cls( + rdclass, + rdtype, + order, + preference, + strings[0], + strings[1], + strings[2], + replacement, + ) + + def _processing_priority(self): + return (self.order, self.preference) + + @classmethod + def _processing_order(cls, iterable): + return dns.rdtypes.util.priority_processing_order(iterable) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/NSAP.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/NSAP.py new file mode 100644 index 0000000..d55edb7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/NSAP.py @@ -0,0 +1,60 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import binascii + +import dns.exception +import dns.immutable +import dns.rdata +import dns.tokenizer + + +@dns.immutable.immutable +class NSAP(dns.rdata.Rdata): + """NSAP record.""" + + # see: RFC 1706 + + __slots__ = ["address"] + + def __init__(self, rdclass, rdtype, address): + super().__init__(rdclass, rdtype) + self.address = self._as_bytes(address) + + def to_text(self, origin=None, relativize=True, **kw): + return f"0x{binascii.hexlify(self.address).decode()}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + address = tok.get_string() + if address[0:2] != "0x": + raise dns.exception.SyntaxError("string does not start with 0x") + address = address[2:].replace(".", "") + if len(address) % 2 != 0: + raise dns.exception.SyntaxError("hexstring has odd length") + address = binascii.unhexlify(address.encode()) + return cls(rdclass, rdtype, address) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(self.address) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + address = parser.get_remaining() + return cls(rdclass, rdtype, address) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/NSAP_PTR.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/NSAP_PTR.py new file mode 100644 index 0000000..ce1c663 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/NSAP_PTR.py @@ -0,0 +1,24 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import dns.immutable +import dns.rdtypes.nsbase + + +@dns.immutable.immutable +class NSAP_PTR(dns.rdtypes.nsbase.UncompressedNS): + """NSAP-PTR record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/PX.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/PX.py new file mode 100644 index 0000000..20143bf --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/PX.py @@ -0,0 +1,73 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.exception +import dns.immutable +import dns.name +import dns.rdata +import dns.rdtypes.util + + +@dns.immutable.immutable +class PX(dns.rdata.Rdata): + """PX record.""" + + # see: RFC 2163 + + __slots__ = ["preference", "map822", "mapx400"] + + def __init__(self, rdclass, rdtype, preference, map822, mapx400): + super().__init__(rdclass, rdtype) + self.preference = self._as_uint16(preference) + self.map822 = self._as_name(map822) + self.mapx400 = self._as_name(mapx400) + + def to_text(self, origin=None, relativize=True, **kw): + map822 = self.map822.choose_relativity(origin, relativize) + mapx400 = self.mapx400.choose_relativity(origin, relativize) + return f"{self.preference} {map822} {mapx400}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + preference = tok.get_uint16() + map822 = tok.get_name(origin, relativize, relativize_to) + mapx400 = tok.get_name(origin, relativize, relativize_to) + return cls(rdclass, rdtype, preference, map822, mapx400) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + pref = struct.pack("!H", self.preference) + file.write(pref) + self.map822.to_wire(file, None, origin, canonicalize) + self.mapx400.to_wire(file, None, origin, canonicalize) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + preference = parser.get_uint16() + map822 = parser.get_name(origin) + mapx400 = parser.get_name(origin) + return cls(rdclass, rdtype, preference, map822, mapx400) + + def _processing_priority(self): + return self.preference + + @classmethod + def _processing_order(cls, iterable): + return dns.rdtypes.util.priority_processing_order(iterable) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/SRV.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/SRV.py new file mode 100644 index 0000000..044c10e --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/SRV.py @@ -0,0 +1,75 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import struct + +import dns.exception +import dns.immutable +import dns.name +import dns.rdata +import dns.rdtypes.util + + +@dns.immutable.immutable +class SRV(dns.rdata.Rdata): + """SRV record""" + + # see: RFC 2782 + + __slots__ = ["priority", "weight", "port", "target"] + + def __init__(self, rdclass, rdtype, priority, weight, port, target): + super().__init__(rdclass, rdtype) + self.priority = self._as_uint16(priority) + self.weight = self._as_uint16(weight) + self.port = self._as_uint16(port) + self.target = self._as_name(target) + + def to_text(self, origin=None, relativize=True, **kw): + target = self.target.choose_relativity(origin, relativize) + return f"{self.priority} {self.weight} {self.port} {target}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + priority = tok.get_uint16() + weight = tok.get_uint16() + port = tok.get_uint16() + target = tok.get_name(origin, relativize, relativize_to) + return cls(rdclass, rdtype, priority, weight, port, target) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + three_ints = struct.pack("!HHH", self.priority, self.weight, self.port) + file.write(three_ints) + self.target.to_wire(file, compress, origin, canonicalize) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + (priority, weight, port) = parser.get_struct("!HHH") + target = parser.get_name(origin) + return cls(rdclass, rdtype, priority, weight, port, target) + + def _processing_priority(self): + return self.priority + + def _processing_weight(self): + return self.weight + + @classmethod + def _processing_order(cls, iterable): + return dns.rdtypes.util.weighted_processing_order(iterable) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/SVCB.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/SVCB.py new file mode 100644 index 0000000..ff3e932 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/SVCB.py @@ -0,0 +1,9 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import dns.immutable +import dns.rdtypes.svcbbase + + +@dns.immutable.immutable +class SVCB(dns.rdtypes.svcbbase.SVCBBase): + """SVCB record""" diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/WKS.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/WKS.py new file mode 100644 index 0000000..cc6c373 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/WKS.py @@ -0,0 +1,100 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import socket +import struct + +import dns.immutable +import dns.ipv4 +import dns.rdata + +try: + _proto_tcp = socket.getprotobyname("tcp") + _proto_udp = socket.getprotobyname("udp") +except OSError: + # Fall back to defaults in case /etc/protocols is unavailable. + _proto_tcp = 6 + _proto_udp = 17 + + +@dns.immutable.immutable +class WKS(dns.rdata.Rdata): + """WKS record""" + + # see: RFC 1035 + + __slots__ = ["address", "protocol", "bitmap"] + + def __init__(self, rdclass, rdtype, address, protocol, bitmap): + super().__init__(rdclass, rdtype) + self.address = self._as_ipv4_address(address) + self.protocol = self._as_uint8(protocol) + self.bitmap = self._as_bytes(bitmap) + + def to_text(self, origin=None, relativize=True, **kw): + bits = [] + for i, byte in enumerate(self.bitmap): + for j in range(0, 8): + if byte & (0x80 >> j): + bits.append(str(i * 8 + j)) + text = " ".join(bits) + return f"{self.address} {self.protocol} {text}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + address = tok.get_string() + protocol = tok.get_string() + if protocol.isdigit(): + protocol = int(protocol) + else: + protocol = socket.getprotobyname(protocol) + bitmap = bytearray() + for token in tok.get_remaining(): + value = token.unescape().value + if value.isdigit(): + serv = int(value) + else: + if protocol != _proto_udp and protocol != _proto_tcp: + raise NotImplementedError("protocol must be TCP or UDP") + if protocol == _proto_udp: + protocol_text = "udp" + else: + protocol_text = "tcp" + serv = socket.getservbyname(value, protocol_text) + i = serv // 8 + l = len(bitmap) + if l < i + 1: + for _ in range(l, i + 1): + bitmap.append(0) + bitmap[i] = bitmap[i] | (0x80 >> (serv % 8)) + bitmap = dns.rdata._truncate_bitmap(bitmap) + return cls(rdclass, rdtype, address, protocol, bitmap) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(dns.ipv4.inet_aton(self.address)) + protocol = struct.pack("!B", self.protocol) + file.write(protocol) + file.write(self.bitmap) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + address = parser.get_bytes(4) + protocol = parser.get_uint8() + bitmap = parser.get_remaining() + return cls(rdclass, rdtype, address, protocol, bitmap) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__init__.py b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__init__.py new file mode 100644 index 0000000..dcec4dd --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__init__.py @@ -0,0 +1,35 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""Class IN rdata type classes.""" + +__all__ = [ + "A", + "AAAA", + "APL", + "DHCID", + "HTTPS", + "IPSECKEY", + "KX", + "NAPTR", + "NSAP", + "NSAP_PTR", + "PX", + "SRV", + "SVCB", + "WKS", +] diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/A.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/A.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1a14072f12300893e95ff892110e139a31fa8ed GIT binary patch literal 2099 zcma)7&2Jk;6rb4-d+peDYSSidC=`)KwUpRE+EOHhh>%)(F;ZIz$p=Q;ok_Ckde_Wu z(l|0wkRmlXR4EcDJ#YvosPbQMgiE6orEC!@;>0b~JybpM-hMbys1Q%```(%N-u!0# zbFr8wKtKKX`|R;7A%CKgQYKM)--5D1IN{VGO&Uohk!m8fMCwf{=v*OPVqd<@r;y8p zD=UPn@Xe9^+*F$zOtpQvrN;VJ;%eqPw5t7uR>Mkskx24L?=vuOL;WJEy{T~8RJk%k zo7yUwrd(YynmRYQwnCd3kr_%WBPuW zkv!;*0b?*B0cxk_WrBR=%wL3BgYcA!dYH5+LgYAz3d%jG5Gm2~3ZDaV} z(B{}fqxxuQWOrzMduaTJPj`mSuWEM+J;)oCP&kDPiCCthO9t*@`07qO))HlKJBg~h{qf%wP zyViYRj7Y?*h1;Zr(~Hjh0w_?LI+vVXnt(Ee+80>`ibo7|vnkB`Wrs&*M8Iq=+`yi; zg{&H(YB|2d8e|bzs0Q9mSw^#hW^unI7I;zDz5xMj3n^|bH!ZzZ+}yxat#6D$qW=c4 zWwK|G%JJRu=yrK@r#!Zr{k2rS`|iCIU#d6 z>q9hmaE3_?jup#4MCn~fC54+1AG99owK{7T2M#^s4QbaIL^a-df8QLSGu%2Jbe?7Tz@vDow zl{4FwGn@Q-=R0SoGWm;f?mve1zks6mfujE#C`AN{6fF~80C%REk;72e0i0yoru2o3 zp@N6y<3k@|BI#~0!U49&;@qWjf{g>dxA056xy$59d3d)xwp||EDW82gK&ndPdR1*) zZvbmtiVvuOuas-H1iVst##$bqcTg`d_W8W&B#|s*+_S(9Z<6nLfzMd$$budTFiHBVaN)JDnd&UR%i>tK6shTy-6mSomuZp zJ~pfnA<*QZq=J$@_z<78K({mM7h@#PHQSx zrfE%EBU6;Ct42-d2G>?;EhDly!gE|-rJel^p640x=cTYb$;ZO1$+;B1Z)r#JLK#zQ-vssz-6TQBxvsUyU7e5?Z7JJIY^J0|X2Eux zTIx29Z7ssmQc06Mpw+S#>i&E~$k1TScI|+%u$P%Wvl|N&EXhgYo@G7MeBn%mS;;NO z^!+d+dC-JU(HrI%i>!?KsGkQ~dGT>&#;c2pfL9?F&U#HSKPp4ROTkln)9vr3-%T)4(>dd9WU*TSNlZ zNtwo7&?J5lW;|(6+isYb!Z8DT!Cn@jGPmgCjbZJw2cxoq#{#hw#CJWGXm5baN7S#7 z{hq#!)_UuqaYiD1Ej%eDY-4o!6Cgl#>TGg}X#lP%^1jF_kUxT|Ms>Q>qSl~s4`vy3$GNib=+?4d{aedvP%-JsV(F{B{c@Z%d zv?<#Wp<#LT2EO2^k72rhG@7zplkIU@%luOn#5 zG)(CW83P9I%g5;-AtNcUH-Z9I$l}=L{zHTuF~5l)<11bvPfCM(rQw~@@NVhi%VAPh zs<+E(^>!6l^;&#Q1)N*1Sr;(3@{H9zKJTDhVC?gG(@8v8#<*vJ8K#u)c!AGYG)`nc z936H=gR3rJ9C3LDEOH3hMf4Suv1SR!VGM=<{`2_w5I=|>fkgO4>=Zh#1AD$o{?JZ7 zIdS@4*UF7&nxap%4XU3#$dJs5C!gGUs%rXZTLtAnL8(CXOOfxXq9l%IC`C6M?Oe`> zMF8QLSh7STumOFq!ZESUuGi-Sa}x1~Au&u*lH3f;I0oLfZ~=u90Ne@J;{g38#zn_G zAn}nnxn3CW#jSi_UI7*B^*;i3pi)ZPLxlD}Ca*juJ&#E*2#~&_-#Pc?xjj0#LkFKJ dr|9U~_{Np>D;rnWueJ%OP_T09Z*)Y5`~!qt&N7)qQJ8lWkZw1E^#v$mkp1v=~EyChB=JKSB! zpPtvOs#J%9;*?Esv`z`rB!ZZ<^i!F(4^y=d)Al8>?UmCfWnpl3^ z9;Mqj_>*H$jMz$C%mu@MsEEGP60` zV~h>zvN0(%ZvkP|g$GUzD;MfD_NcwlkG;3e8)t`m`hV|@DZjVJIK8@ZtLLr1vl*%m zr}y97PN2;Abji(^+)Rk=yo&@~M7nY}h%!t~AnI+%LZs>#{w z@|?`6%xn1Lh;UM5IF^u`TSTOdDvK(*lgYS<#9OUG#n{lL0})-1MWk`64Ob^*5$)CN z)M9i}?RbSkiov6Pwq_eXtq80Hw$2@%Jv{f^>~qV3)?A?Vc1J$Yo^HQ# zdc|Kq7n%*tg=fRd{^p#&d2!e6hTE^@{m0VnS6?c)s;ACRp3k;@>Z<>tW1PwR+t3(a zm(G147dWtZ?ysJuz=3?=<%brqKj+`Scp~p_MU3mPbt0-GC87y*;TVno1jG!|d{nz3 zi6NeZmNNMKD=-|;Vj^yl5n3o{G4Zmr<=56TXr}+&c+43hM0Y+Q8y=9)JRrj%T9Y&+ zUqx{Xks>mm654)HL`nxD9g$=aB*k5n6kO4aI=%|o1c5I7$mJu28ec}9d29Nu%;0?U z{Mj29u3wn%Tx?iu|M;bkURpe|H?Lp_Y9Dn4x?$NPo1Eniy{u_0Yp4u(Nm&waz*WGSHYVPJ z;l6Z66Nrj^A8W>Kvdb8uztW(JUs#U`r^VXhig_wU$<;=SY#O(sq2|ePSSwHSv|$9= zi8p~GMJpDCMtIIs0|${TTycUSB*&p9MUg~;qzMJgqC)wJl%?FrDMn#-fj!cUixu>1I4Y|&LUGNMFe4(sddP;=Q>A-i zaX}w9&PlRL##Az{TKfc!7eon}B5X5)h7QZg$Vf~SiaM-U!2QA{fwpTpYyvj`f8TW9 zvTt|Jw|l|+`z<%OY_odqkZeQl&nBWG5NJMo+B7;eOIF4v%Byw?>i|Z`_ihME( z)pkI~cv6-k5pfHgrMMksPeXQ-zyo3o6_G&VY%35F654l(f6{?}Rkco>T%#+PXV?5R z(?+g4ease(4RJ)_&rG@#O054Bs@~fWoA8!ih)mK%4++r;3(*M+@d?>w6>N5bw?mXd znvoC7J9sO!*##S4!P`n&9BE>d?5Y4W;Z!NW`tb_Lf}#*jihQHWpioAN>Z*m8WkxBN zq4-O(G)|dh6yhqhDDUw&bvfb#xn{mrK06nr9 zeSF6|Hpc*UwRDbq&}xH;q#WdMGBv^K7H*%n!f7LOr;?lFevs`&>5|tg$%-cvcJ@qK|LVJCWma^D8Q+C zidrv}8+T$rCWwaxyoewKwRC?nmQbldZb+p7v9!~owbQaD#aJ)2Iz%B30q>>QxS(1F zMkF*FIv(A(02q>mQ5lO`qh5$W7YQ93V3K>&)ac~sht!8J%zJP6ulw`0Pv+hGme_sT zzDuKAM5A4;cfo#Vh+ZOPQ>b!#H&g*+@hZq7tFZm%X#l^Tj9%qjh?HHi^CUKrnghUs zxphDtJeq(}v?nY)gUaO$KW_ztt82ss_@*jS&Z4X<6~;NIDimi~e%l6qSE{mvztZHd zEYXw-#i6*AO4Bgf6po2s4n5d6Q~msUM*Im;bBECI<*pdHb6pG zk~I0V_lU_sO#)p=Ma^P2a7cfIY=QveQ|(DJg__#snq9e?UHO{bYlPFjEq%P;shv4G zeKhacvFr)uJfU=Z!R^VkO^#%DT>e=hur0$DHV3k8+2=E()AXG{5X$upb6vAt3)I4^ z`TFo=XZrQb*_E2wtnb?S^oc@n=MBeo$HM+aG9P>@=d4ek%k-G7D_{Xz<_|5LTkOeu zj=)sT>Z#L{r?cMAom*GyU_JM?5LKSc!R(3o<9YX6?qYeM|shp z7T_%+AFiBQ6d#d5L~3a8j&72jAq&0@Z1;4y6q{fQm5n9`;dx7v@HMK=D3?ekVo@#* zaDXqZ2$wn{uyD1StD<41UK)YXBqU&f3EtY7Q`4u`2oml9j-kpthI=^KK7Y2_&d0{}IkmY9nL)n}n(9S5H_WdkU%L7D0=y$9ex z3DgQ!gi^{bn^0C%LVysf4l^u{GpZE_Zyx~A5L*FkfL0vHhaZQr$3dL|c)}(JWpY$t zj_sHOfM5fMuLvIf$jRN1fXivX6|B+(4PzdKRqQ~s=oCzNsQ}q2_f%(s8*&A{51REG zAg_F4LXi1X1rX%YA?OL1RCCHvgme}=sB-9@6lj!*Y4cFOJGY)7I=UWkH+XD`B& z1EH1fZrc&vjUH#jCxKnH;{IUpg#@BBFYX+)gq$KW;w4aJ9pXd@JPV9g$ZHS};1ReU z-tg)*Jlc=i5L`(pm?!}xUH+>u1R-BNq76CjHG!$5_AN953;UZO?o@38bgJ|&S8mN! zZp~K)=bJyR+*zmzXmB-uDPPkF;8FufH2o7G+%;M1vZBFLl`p$v+BI#}z{$5|*|#g_ z+XXDX#&pLDz)8n7CvweeGTh`?whd6t$H90;>}&TZOu7v^K{33ClP3v+c&#!!L}I4`;TO78mu+U9-i-8 zwB7donfyat-t$5UynP1n7AD*^CELqa?!4Dj4(z})IUAxn&vruAc|t^v1YA`Md_)xJ zh=)*i7-i3(3_I+v0Hs!7?`=?kH#A;+6SW}&ASO|pOZV-fLOdRczynDK)Zzz{W15F| z1a(Cj8cI3|*|*5MuK~lcsV-xmIC-CDnCHvSFMEx`XjB+N|4$IHl;gVC;NY;#b;kvj zMO|<-YEUhz9o1=1owzqPpa_m3Vl@~wNk^{-;wF^g9kwE>9%blR=mGnz78)HD$DoKb zO}YfxDn*jy`c8tZ`jT+L?<>OlCHlGX4=TNI1{UVM`k&V?leIar_8Uthxn-4r49>$+ lw~CA1#d;{Rh$ATy_8e literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/DHCID.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/DHCID.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..986b7589d0556bf81c008df96c31cfc2660a750f GIT binary patch literal 2243 zcmaJ?&1)M+6rb4-X(dbA)J;+c^4TOM8``L?os<+40x@X?MS(cnL-p>2jw==)@ zd(XcVi+Kd==b!$Z9?v55R4Uz-NjIHuV6%=WqS!;VikAYuYlJ#8h1}YC1KjwuEaLmdzoWqxurw%}?_*1HK%~(gB+7^W>M&JgyClBdZYU zW1n8Bj4c%6$8pY_fYV5$c4#|V->KwY9;|a6#J6}Ig+1qr)I#1>?t88X3TGE0v%!Mw>R%cmTV>CkC^wqnbl?x4 z`{2W}a6|T9!**`kQ%sbpFUm=3QLa?WF*xTMO`e0i3#CNup7I3P#M>ZVETYFoaV6U} zhVH+$@y@S?^=SXldi7p)V`OXpnPu&6t^-lX>D?O?dC&p)bv_3HsHW%ug;FHJ+*>pK z6-ljtbC>45I0W=?QA_u*8#_GAbJeBxLj9LNP%iRZoLY90vh7n1id$mFBj2vTdH+ z82h2}U8QY~{5S~Wp)tbuL69Sl)m;*Xm|iW|ti#b~dO&nT5b82}rXRr6-SyqhhB8;Y z_5!Ar?Go^|p#VuGX8hs=i`;jC^0BmF7jQs#&5q{}#PkNHkOS(Hhj zjB*nfMkw?TWmYay9ZzsM8F&#yq=vywen6@RrCQvDmxMv2!kcj!gyfgRIbI3axk(<> z6S&O;?w;WcM2n9>ETSC)nS@Lr_=*6qR1F0>drI$@m>>2n-9D{bF| zWZI-BF+I0H~3b1rAS-b?lO;f!Js#3FiR6jt*q z`L&Vti}x=6W*m9_W<8Q!NN)DOB)hHm=%K$#4;&SzSh!0IE5naM%D#GM8a5Yk#a z&+lYV=8Y%U#-FLWezv26vZKgd3F}8=-!nx?9na-1E^?gP%JIXHIV!@GJICf7)(G9e z=aL(V&8^pGLwf?4my;pM9L)8s{7o6DUzg)CPWS*N$>qd*X^bYYUE;DU7}A|3$`R@?=m#hGxo^qD-0k?ku-4^oIAo?|a|7_jp(3@&J>7O z{sb^Z3^D8?6U(fanB)G+vFd>QKTSbDN}WEe+XfAGHk z_8kd(5q~qZoG;cf4+$HDBw$fI1iarSa*_Hl4s!6N3`GE89>N6u%54YYLQ*%hqzujV zp687s%XWF90!=oBmVHgHQcaHA> literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/IPSECKEY.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/IPSECKEY.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd14171132c783bdf074154116c69300ac1c823b GIT binary patch literal 4231 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 BPnQ4y literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/KX.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/KX.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15472df9b81481f083f4d562eee322f1d32eae55 GIT binary patch literal 636 zcmZutJ!>055S`tVC7okIh~gk43@Os6h?Y1mO^RR$Mp#`yT(g{R7fZ^0<@V&{+yxAY ztCaR%F#Z=QlS-B03@MVzoz2;etIV!WB!)~e^WK|J=0~Hk3~1lK{T$s|0KYX^ShWPj z34&t~AczkRF_Ah%lF6cC(Sh-Q8ih9?=phITd+PrbmvCZkxUWxE?sLt~@In*k0i0S! zI+aV!`d+D-KuS_PL~sn!TWYPiX|e=V#GUr)*E!=EUDl;aSEZTg#~8g+&l}j;9S6ZQ zb%(ypEu{G)t(DWd!N;MiWN!bhsg!+O;k)JN^KHd67H- literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/NAPTR.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/NAPTR.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6becdec3acc5750664d88a6f00537bef83f3533d GIT binary patch literal 5045 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 E0jPEH+W-In literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/NSAP.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/NSAP.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bed87893a259b792a14e23582a51c5e418f50b8b GIT binary patch literal 2689 zcmbVO-ER{|5Z^ufeCIDdjsO9KFepS=5Sy=p(gtW%l@x*8mLOHK5U0!SCb@9F3wsw5 zM+T)R5{{~hAhk-Wwo-Vai2tIG?MsYXgCdv<1Lc6WC6 zH?#LkHk(4we*WptF-Jz|85g3K2$|ORz$_wy2sTkU=y6%_4R2a`hOLY46 z#soBoh%kpp4CZX;*hbv=riknG4`TuUFdQl;t2iHj&ZUwbj`>|^hgw%aybb zpST<&!YD4s>u3a%*qmGziA>^ixSXI;5|Jbk=Wu&{nWRVpbV(|aG?CV5Qgt+q%jqGM z&v^0D&GEY`j!!le|^^&UQsKZs4I*c!t7rnSc z%@I#xL^n0Z@e+)Omp38@jA7x7foZh zI8k-SZ0lIz$kC!>xU_FV)30iy)F~3nDF#bB#dD=1k56Hu%960`OxBkoANRqK!^!Je z^hD0iOAWbuY5Q{TuX6tJ_FaoN@7`P!4V=HraJlw9?*m>I^qqfCJ{`8osgGTPa?$jxa|Al^ifnIe{M(uq$E#K z@G*oS075P?9;D}-Fa^lWZpUSYHJT$fb#j*N<{VdJZf?qO$8v|K-{69WX1W10q1l+` z{&E^g;ywu4O7wS5rt2f;^Y2cgLkW>*4? zZt%0WE$6}3`4-45dLg4N+ZGFV3r%HTU3#2W7LF`@Rv%b8{wTfYiL!OE?{43!axuBB^_(z&;$ zpKU6GE1AJ3%Fb1#r=j#Le}4bUy(`WA?=+PYb?Ntw%EHN|v&)0cjfYm`LqS^bREQeC zK0htq1qRXrua_23E86gnCL$EU0YlAYJk|1oCr&Y_ePMwO@;*LdnADSXyE4IF0zckf zM%OIcGIY&^GSIHDVGWQW`s$RyXjC3U@dWJQa9}|dkTZ8u^Qnd7ivxEDev@+sJ8j+m&bZSpa~ zkE8~+Gg~{*Su@1`?p*1t!nz?br|(xnoR(%Hb-41e$}twQ0! zOQ~wbCX*)TGpc%RQZqx1q^iW${Wno{Oxtx-)qjrI4%pgD`ww4*!gEF#$22Q)a*XS; z;j^ey(^S+-v$Yx>{>|Ao_iG+#9~Xv;^`L& zl-T_AlcATfl-Spbf$&1$TsIc?`ns2b5Gr1A&c9=S?R+Z8@hsGYLc1g|9yre*Gb)uy zR~rW0CAkSBnyUpG$arc2=9ri47>R*_c#+%rJ3K(aZv0JpBM$^|Jk34?kssFK%!?St zxYdL3re~<@5B}Q(6g(Hi+q=Kmy^6aVxcil`7x&eV-RYn2zjJ2(ObdYsXBBq*%@zJ8 F{{UL;ORE3? literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/NSAP_PTR.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/NSAP_PTR.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a38d373a65e430df6e87a3ca00c877917596328 GIT binary patch literal 644 zcmZutJ!>055S_h~rSpe;#=*e~7;JE(!dk~}O(1DZnv02(=9=YnyI7L%E4L@Z*;T-h zq)O@hg^+)dGO1JvE|4Or(m9;L;L5YB6OJKU%)IwzX5TR1o6R}E_wDP?-n9wvTaoFj zSD^fa-~u>s5`ar|He4deVA?UA!2CbWfPLV`IdBtatbdv-IM6%X*9Qytxnf^%L!BoH z+(u5?wLgkEex>O|DpD>XxB#KIb`4HkGdJ31VL69S57@!0m#4NuTZqCZSTV;~81rGE@B(9RhF(xr8jSJSXY4bS%IoF@?Zu&>ejIdj>i4`X%jt=TPH52Y z(j?D%akSfcyhCL_6I+SrAA3haQXWZ~@+?mfKX>SKt4@-qHT+eFhX~{hhI9B;Yo+Q` zT5nrL6X!eqFdSxHHxPx1v{+VJ^=BuNT~CT)_O7Q*TYMe)ahRk+O2M7OiSMj3 z(N6P-Zmhk@6`rIIkgDU#XBaL`LP)s@WaS66F6&^-yjy#_HYTehvU+6@^60(%c=q9J ZY;TP0jj_EwvbRe>xirCO{Z$Rx;}(aulluSw literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/PX.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/PX.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cafe1f7c92b441064ab5563c0a975d112c40bc77 GIT binary patch literal 3439 zcma)8O>7&-6`o!0E|)(tMOy!oLnn0&v$aXfQEdmP6DJXzDhJhw4aDFf3wn1YElqOS z*_CWERHFhyLjnyH9SW)qin2w3$kjzhAA0Jsy@;R-sp}$0kzRUJOdkTD`riJKEG^d= zkZ<05^WMzPd-J`SzYPoo3ADfd<;$f%1NSuwUK3oV{W&moq7a42k+jn@Y1Wo~ZqKny8l#V6u5QSYQiVu7SyS5&ejd@(R-|;#8j;l%sOH6|M4@&d0tFt@T zj( z#saEe8BqK^BSA%6C+QIA1M8$?AFh&nOnTr3NgT7->$l3m>$gr)Ro1DJO!%x|k*W(S zRSU9e3HeO%@`VeQANqSU=g-St=y3-e{BaZQKLPnQQzs_$POAwO@(E8!g{iPRtfON{ zg=9h3-zvTx#?e)X>We2;DCyG!IgGoSEVys=zVsU=F1R?tjI zvVu~^ke0QAIen4h%rFFsLME?Lbf$hF;KvziZo%?XMb2dm!xE@smS7_E=-OEUNwUW! z$w0r8c3XQoms-;EYD(5~w@WE`DPx+Y)QVbIN#(TLsba}o(hIL9r!S-o%~a17GxD9x zqH3g+f{}8TF;a8$sq43r#S#s{wB-=+-Uhx03gayx->#8=iUT!&QykkGZJd56CJrWE z`hDs*sqG)MCVo`qeibmB!1BYmLj>)7$TF&+SDn?ui%aahOiV!{asNF~aR%0)ZU( zvMy{q1MEGFuliy9sC;l0z9M)vYE}GBRsj|=j+w0ZD`F+E;{xvnp$O6YtQA~TO&2?s zh&;k35wv`A&Y%&np;$aEpQ+!WqbQD{IM9u`Wa@noGN9P|Q$6r4Ew*_u?_|%phK{IhYx=QG$7r z9KZ5$4>~={E8u6m0c4GQH9WaLe7ZS&x;1_s<&lpy$#_3ip@qduh zK?h8Typ5a*V4in%xTPe)IK`Q{ddH`EI1kn z0jS53;~O8#w?sr*=5|gs@T0waMxU`S5A}t?o(0RHaRSI1`FA9S$k~s? zo00f_h+mQvT?REgc#NEG8j@tcOeiMX3fXU5a!e zfq#=}`#?HmZFZ33-#83|{FvpsL&!c3Gkp9|fT3?#hM#r3p4hA%8++kIh@OIFtpUhm zQoSduMN`uY^wo||%jcI(OqC^~2}Z{XAV-yqnXv{P)=q!Y&VHcl!8}xXg2!D}BWbhFgg}t!+$0vSqVxJjnGGkBJ dbIdGLJ-#ts8{df6;%$O5#D*RJ#vZbl`Y)S<&*lIC literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/SRV.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/SRV.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02f5672883200c31031d579fc155757d12f0c57e GIT binary patch literal 3692 zcmbtXU2Gf25#BxCkw;P#O_`Qy%d+gsDa^VO;l^;|!ZnguRqUdW0NV%) z9Zw8HHxFG%vokxhKQ}Yq?D8+6P=G-D^Pm1X_YrWPq0nw#hiUx=m^x93N^>M>_cSSR z+2i!0(@Re8O$wTj?Qbu2*!LO}$ZJFumWb+sIY$m{+gx^_%^mg|9-H5AhLV8=8W;bA z(p1_RQ zA5+u0lwnw2rkV?|+LxzsW6Q57+(=OjoP5S^i_dUYL~+5K(+e+-U;IwO$e7w#F_pfTn$?ViS}+p!W=7)XR06_0UR+>(uyCmlh4$Bs z!iaGT$QO&`A985fw)*QOg2&1f8CKkDKJ9{yIZz%hNJAv zq>2dh+~^<9eiuptXxfG1P(d6GdbO)@jrcD4r4Jw>Og*D#;*uSWNP14!4NYNME@fu! z117qH28Ll+&GG_ZW-|pV(6%gTmT>O@U&X*U^Ek4jXzMd|#nc{}r67jD5xxZ);}sx_ zWG`~8K2#gpjg0R^#@Fv}mYR`QtKPkV!TMNjY;~kT8|Qv|;nx=$!|SO{@%R4U`PUz9 zo!y$;dTBd6u`N%qD6H}&km4t!J6rDo0f&3C4%k3+Te~_`d|#uNzeeu|eXrn&WeFhW zXet2#0*?VL`^s|JUk+?IA-hc&*8jL*1)$hC2(x755mND>XK&oN0oBlJm~1|cw+fW0t^irm1GstY!pVX1 zAj7n{*9Mt5lgVk8oYwP2rWpngy49CX74$+ToyuhZa{+UXX&TgM(=gDTc9?D4$&e2U zuIJP~M|ug&j7cC+`i>3N->kja7+jCOKm2%jOZ*`Chv0{IK6>MiZ#0ihR|9);-%4OP zu=>4Eb{dELYy`Rv{CJBgW@yZYP>(Cg@PobWN1(PVk8pIL zFBN-sDe7XTWAytl)3^#`k$gHD-yOZQGkR(B_2%f+9r;W(wt9K((q80veWW(h7;Hw) z|4*E_Sb)`#=QueD%=0dgb{(NS!yZ$fLIzHCz9Fgb;3TXrgnZ7 zJT)~I7kDySGD1FeH>VkR=ysAtzG7cG*6jtp%uSeN3l!&i2~ zSGLY~)LXdT@5DV*ccy?$P1-k27+-aGxA0e;lGoMw9P)jN@{{>g&e8Z3Mb*pf3)Q_c!#O%8!*DgkX%G^3CU$7hzBd=yztUmE~hAQ%J8QI5C6*? zTuek$B)GDX2lB-d`Ii_ykV)jq;?1X`Bz?QpFGxS2a36UipHAOC@c5(&+VX(3FK~9i zBfU!Z12AwPcn)>Q9t5Z~eBhc1fU zFC2C1R5-ywCpRTdPQFW1e}Ff<_kG`c@4k2UP$*0wIQMr?bpiQBlF`X!Ah`i>jTmCs zMJ7(OVq%V)qmI!8-^D!=IuaecA zOiL5aNHPWB8u4^%Q(@RtW2K}DZU11qdSb}99B{`ESgoW9ozj-&aZ1GmrJm0^uEdj+ z9(OD^tK=zVzC-CPN~Fi<9;y2tCywvhF>&fv6vd>=TV3KdY|@USy5Fjl*Vakci1=#T za*nJ+9un3HNx-7G4S2Uky_oRAB7X_FtH$1NsS+>iC z3RKKUExWHAb{*TcLN3OCy7F03=-*XcnpUB2=sGSfQXd1*r@lFS|5h*!RDNezh47|g zjFTL~vyW)uEr*n`^TpG}0WS7&@k7CQ?xJ$Fak()tmiorhz*y-UD+vNQQIRtLE*nyh EFHkIve*gdg literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/WKS.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/WKS.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff1d6aa67de5acb0cbd33293e4083bb223d0c61f GIT binary patch literal 4593 zcma)9-ESMm5#Qt8@s1)XiXvs|BZ_6~qoF@UE3#t+N#(HhM;ymYQoBja*0`Q{XPGp= zymyi<;@GNzfl#gji*Q>hNQ+W!fhhHh|A4+EFGbrIGjt&KA`~vt7riNx9|Avh_Krs~ z3KMjJ+uPll*^k+o-^~8g?QTO*{_)AbufEuh&=;g*6??6*!%zq6 zElL}IruJu}v;*0Yj1n$un#NzU-GiA(iE`h{L)UqDnNIjk{^q*Xm<^pllKmEvIFQ-X z%x$(4-QOI$N1J2!YNV)pjv7R=V+di6LJ||@BsR)lLW?JFpqun1bb|`=n}j!_(`qcM z(=$?bljvyE3ssCztWuj9?feWz-UZ#si0CV7lc*>?jU?M2sVGxGS15_T#Yb6*mzY~r z)Gl)lBsnDZ7FFL9FSWtBsHj8cB&WnRQQ8XVCKYvFKtY#Izjf|Ht_`XIEXOif3eq|! zN)nb8Md!0Pqh?~6q;8*zt0^%Xv+kAbmVuA#cIPip{f1gbD*Vl|VAs%lEJ7Nk+3wlQ zF$!rY0b{$1R=+h33D)TeuVXhS%*R+a47~NZfY;rEmu=$ZEczV!h>8SxomFPCGS+!P zh^OPKAm|-}s0i`w>`|e1dQ8^W9b|YWo>nI?v39dRHK)o7c0xbM>5L*Lujm|>Vo4En zX2+5`2U8PiL&V?%Il-U{fhRS6s`FpI8g_fvmpf4{wSv(kM8`tItU-sN;L zU3#;!=TL!p&$$EhpjiEapu@vQl(5rjIQcZnGiuFA;i^!gcJaVbFV>ZuM3+w>^i$x- zvw6E(zez-G$ePsh`G8kE>h-qq2;{ch0Q!@gg<($M)YMmoMQEo#u zkE$MTf!PQQl>8s|t@c#{hqn>!II?i25H6@2UA-$~E8^XsY$Kcd$P!ic1y+uh#@>Iv z;v25|yO+-u&;6M$h1aOnQ)}VXvwv#;%pdxa1=cMZ=Gfv*sN$!y2!4TJv2y4xr_Sk$UVjMw}`!9ivE;CHoHb zHOXf{l9p_FciJY=d3TeRTEtJysktpT&$~32#N-GRw5xsAI-0{;C-2cbTD!y&SH1>Z zcv$VPbJZ!usToBbn34xB{_ypI z3?BIL$=5N7ub7x9?uBZT7`uTdm?$ae)7z$HRRFMvr>Au;u1J6ks!l_w((Otnc1>3G zcHl5pn3_wADOqurJ|C_ti8Z|uum{E zilA4gV@U-QgTq}0yPCO%iJtKw>AD+GC#abQB-XGKm|?MFgcAZWp4Hu!N)435BZNXF zbxKf3s#OO=t6_qo;|k8COjvXiSgd!S2N~rosD2&+eEIe)j}}KOo%;)1721=<$%=2V zz*n8Vb!T7M+4s=dzY*wNnYf!@c=^A9{#t;2yApV=@bZSY_ikUoUhV2$?;0t0ja0gx z*+yQ+^MzAYf6sEH7^(P&*8K;{{sS=L@!!cWi+jDf$$Pj?eMQqMMojL z(cxP-B&S`Xy}x*Il4zz;&t>uB+hrW=Rmg*i~Ak0}!BF z;Irlb)}KNRlx`L3C8~9xx9}#^7H<-xIHs?Lxkv5|KM?PlV@T!|-TosVTwS(4cs{-wKO zVmgzK$HXM$5r&LH@_W-YOvm8C=Y$(^EY}jtTJI9DDQ`dp`2z2{)3(s|>+$7@;>5ad zxa=D)NgpOxlNI0iL;mQ7uV?v%;tMNk>Eef%S1-dL_=_pBH1Uvs%4#1aT%YlKziA%z zu&=>--xl^8VF@mT{;oinw*$S|{9sm6tl*l5=VcABS=1Ix$ zj*;+b7{RZT>PTOm>ICBybGQI1iOV4$3V>Z@DmKa78yN8V@W z&pu`t_VA92V<)yck=^^~wF}#HKYL<_hR&9av?dvLV9N#FZ5vY~JSHu}#*CSR?UPpVen3HA$TXW5ZXbc58AQyd})xr r+VUXY1KB<0f8^~XGy2|nWs8IE6C+gqb|^fKJ|1_3PtYGvQc(X7seRP` literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/IN/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c97d4b4927020c7c2849ba0d19f2a4cdef0f093 GIT binary patch literal 332 zcmXwzze~eF9L4XFloqA_K-?Sxx^%FIIx0e%78)usG^N%q$2E5}!88fEvk*7`3U@dE z4M+VCZgBA*P`h>V4)g=x$9r%1^4^=x4WM{?{fN&k06um2$M`d=9c7*X1ye8w6h6Z# ztc;fVaNwqdbKCRnk`h5|Q5DRNbjNtRwI!h=Ek_|uC%BTeZr}B~veqAsf>1WRAndt= z-mNqS6It_}U^JALA3A|-XvI1k!9<$ja4fBG>~^GiGYI{bA?p~^Ji=HTlBO8HfR&!Q zzD9ArU?|GdS%spQ2vMOW%a$ljW~it{oM&h4lT*YK!Hx?O-H|!th-Ms>RFCJ#^AW~L smI#d7MYV2REOL6EvdeN`{fFxKu97bkLil|I;Ng$8y=gqw%RSZp0&!7dj{pDw literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/__init__.py b/venv/lib/python3.12/site-packages/dns/rdtypes/__init__.py new file mode 100644 index 0000000..3997f84 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/__init__.py @@ -0,0 +1,33 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS rdata type classes""" + +__all__ = [ + "ANY", + "IN", + "CH", + "dnskeybase", + "dsbase", + "euibase", + "mxbase", + "nsbase", + "svcbbase", + "tlsabase", + "txtbase", + "util", +] diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fc63a5a4a6464a191821e1cd99f97f2fd1a0c43 GIT binary patch literal 331 zcmXv}Jxjzu5Z&Db6HWt)U?-%oN#QN0tq7upf~2wXnq{-QT*7TW$Zo=|oxj4~&c9)) z|6mIn|A2bkRpvtnX5O1O!|>)k2qvJ5x7Uw!<^u59H~)eD$a=RUPk@6tSO5;6;T#$i zIS&U{@m*MRs%fZAB|;{tQbI*L*13r9ExL&$nPG=`rWm+6plr8B(DBKyuHSd2=n2@ZP^?r98cB%Hd V^5sAXzmEVs{BeVA^yt;S`U~TkXvF{k literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/dnskeybase.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/dnskeybase.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec986b66117592236acaa47c12b887927c594c0a GIT binary patch literal 3869 zcma)9Z%iA>6`%F~@!G}<29n?;1Yg3Xbxs#w5=v-GXfGFN$w^KxM5ms=Y!~kUYiuu_ zU6MF5CnH6Qk$O!~Rhj5ig;bS}h)PXAHT{X*xo0)y@_uh>E9u5Z)w7>oNUo&zDp|9w`Yf_!rImsbZK|0d8EK0dOm*Uvi zm-4ZmPYJ9S>%CO({V8D@>AqicDcOg9fOP&g(gnuQgASH%+ogeR6EFH&9TR&IzWEh8#Vh>q`<}fFQ_0H z=N*1x^hb_#b@an4?~gh{>Pm7n-JFXMxRkXWdg=;IjpKqP*roLKA)w0o2veFUdy@~LLEv3N<<)!&<;@aDw&uu zb2yPUvy+8HdPcMDLgG5kUr%I>$;4d2o-y<1PMqDlIVI|{F5Ko)67~m>9?-?xTqA_C9@Hh!1+ZTa>HZc?u#9eUsn7(9w>iN} z$%*$zKe`A9zjcPvSURg&R&2^7F`{d>7PG+}F~f>wjaeMiV($DH!D*A|alsL%AQ+Y- z&k@r$!GTUd%TAlbuxD}(4=zblH+YTuoPOa>4}|QOTm{*l1}*uE=o8QvxuS2~=ZZOG zQw!FLDAT%5%p#?`=Zn`H|)s^=n!>Jvt8KI z%#KY4W0g%(RZ9lrnA=K(I*!ota;vKetJB^Wyafu2g8l0fdM<~T{F`#m%AwVhpUd&* z2cj$e4-)qiYj0K$zf(PMrY!s{SnE0ZK)J81-mLb#1LN@X=+Os#_xrY@XE&o~x1#4a zqvxLls?m#}Q)*p@DkIAyt0$^mC(FVoGRv;CmrSrHqTrzc+4(IH0A3w72xaPE1978e z8W3uT5CpLSmZo%yGfYBFKrF6tJ!l&q0en?VZ z@)Z}0`$;b-9FZj@;DDBz!2x5cz(TSoOmUIWYV1gmh|@;i2@;&uY~#9d3p>8q8_dL_ zsl_74;^z2mQ?>C;`&MU5?CP?!Fx46d0+CZ9ca0@uOF6!&9A6#yZ0O<8rgC>5Qn$T zS#pXF-=f2C6C0{+?g5g+!$K}x=$E>Q?ot;ufhl6}=+TC9c)Rf4Ua&A*OS5>~Xr3`N>!&POZ%s*GU zx0GX>%CQxzs+=hE|7`EPcYbATb#!g$@kdYjYWt-P*}YQJtx>1mW|;abF#jv*S(^Ue ziNAlcuh;Dt#3Hxv-P5=qIKaieGthpR@({(L0xJl@8$Y~w5zr$6MCQ}BBhW|5-q?vZ z;2Op(dU44u2*Q++#g3ddb8`e+7K4G)me%rS-bia%S|6kttm%L$>burR%YY8`hC%S_ z#l0?km&SPn2vms3p~{ivBP$=SUi&QdF!i|m552$deJcD#`IAzOTq#R6x$SOnDR^)2 zbNR5x4^)PhhyEcSb<09)2peT#j)idlvY>OGgTV3sy)a0PloWLzy_Jii&ewZ^^-?oQ z<6@Ew!yH2I2ttqYf^usOX!Qpp{>aA&B*c9!D{3bc$j7jt$YFRc>*PCBe2J3FR2Qy48PYhLRn<60-UO*dEByD} zX9NB9LnvViYV-AN^l#z2Uv_-EG`=f{;x~2_pE$-r6_FxeT$y;u%i<`v!^3FX$JlOO z?A;E+_@$2*FT1K;#Foq@lrye{*r%J; literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/dsbase.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/dsbase.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f62f11f3c30bb1ca94fbc42a95b103e577fea27e GIT binary patch literal 4180 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 KPYjc>)&BsMLWLs$ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/euibase.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/euibase.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4dc7bb9d7d281da8ac45e155d85955b35a49eb0 GIT binary patch literal 3590 zcmbVOUu+Y}8K2$t+PjVuJ9Y?x1I`EG&jN`#?ket2kHV3opc1zMTB&ZU)v~)uHnF|F z*$uHR2c-z<5^>jn)G9%Dh@9@E1MyHEuTMqCJ)ri*zO+iZMHSUO@s`6pAo|kp+x0qu zT79^YJ^Rh~&3yCC%=i1v{I$J3OrZSbtACAJAbpDszXTgX{r4cu6OCw0mSnsylVRO9 z*Z2l9tj>*i9FvQTVoyntGms4y4wv?Mkp~-;1RacYD4FK z8vRIp7@8kI+&alH8i6>O45zUpOeUal86N(@88XahflERrq=_1TiOC4MC=xBK1urqp zdBR$o76MzFE@}}?SZ9gMkRp?5KSQK;C-mOWPrj>IdN~3@v6!T~YEsSN^a+Eq5QrLB z7h?!y46`0%2>AmuPwb{~Hc%lS@q|>E3VRy|LSaaSjDhaOQsHjHcq|nn`^zS_=;=^9 z<{zQ2g74axR!1{_h!FaY-8__5M={p9&Rs*)7?`ca@oQ`uBL5hUy0slFf$O{<(?zC4 zBtGqMu!)ZZa9vI$sixRUa#XRBwh5o}da@i$o-5h9C5aAiO%!zM2(oPC4O^BSQC2M3 zwNl(Mhs)}DM?i&)5~t-YN7Rd|Ua$=_?}*1untP8@lS;f3u=MP(6QY`$RV>R1Qq3-b zch`?7%dS?IEo>*DOh5ZcYShf>Db>uLE2Y#?#kNbS^LqY#Dr=le6-xG~nSZVS)q^R^ zu=T!zqK+#gx|Mo`N7#5$rnI*}i@jI!>CmR?Y(V#ApQC^Ch^yE)>7(rjrtnyf{Wi(Oyz zeA-iu4&E8M+gpnc-ro*YRT!k*&{su(`=tr?6R@CpQsKs$yXJQRP&UM=klozxO2~%= z>{^SxbfMa3Hz7WTfO(;e;c2~4T&TA22T)|MH!0;JW4HSQUGTQxWG#T;*7!2yt`_ha zdVu_9KOs{>B~%eMfeS5&ky^}$k?#>J`1qJyXZ@YH$OK3w)RKULn&1=WR7fOt!jFEe z%a_)^;A7)j%a{C9@MoJ6Ss+oQ;)>|6?hM&M>>huFz1f$B*gk?9#u`LNqL>1N6sYNl+N0?EM zFzKE#edWyqiZznIt5HB`4OFB4gsa=g{-3LK*UB~u~frw#ECqB zH4A8Uf_fh2)1B}yl9b;(Lx{~-W$b(y*NzGoCo|)ANWnx0T^M$Vc(|7sz73ixeK!wmZC4u z1YDxM{N@68BXTYBOHtCzb|weD2);HmpV z_m5Q%{A{Uv2$*`6$NsB966>0)%vKhTEykCky)%JTmJ`#=gTzbAiJi5?&PDBu@lVG; z%Pl2dpBeZLdMBoH+)oPS&3LaP1me*M~AdhgI( zsk;BvQhcx)8T`*Ghy9*em{(u@V8^lT|E91HIPFTuV1 zj=9N31SS?bt7!~G)~cT?xsQglyM z*yC=8#HP5Mps`PO$><)a3<&~TcZ?Ui}=S*!Pi*iJk-=9#j4NFLtHB)t;YqFI! zZA+G2rl5Nu0QV(CUjfN!bKhJ!9bQrzeG_d*uo^&Hd*el>>e;L;OAJMtw~*$JxQnL& z?1~j8t92Htru}tB75^M~m3h z(k(ceKaG^-I=n?Z=lDW-Cj8Lb%fs|EG;x{M5vTx=3{&4unAo?Z>!0|GL6j{2!Pehy jU1oY}OwSYc03*$)bH=POXU>{+0!;{?-SizA+!_80zIO~1 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/mxbase.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/mxbase.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d9dcf3214624a1b50904279f43e9fe3ba578546 GIT binary patch literal 4383 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/nsbase.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/nsbase.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05d2bdba329139bcb32b1a56130228c25036a242 GIT binary patch literal 2816 zcmb_e&2JM|5P$pWwVjZd5)u+xRux6KNo*BVXe)#$Lg|Mpx5Bg%K6JI&eMvT1@1}1z z33iQ?N|8uZA;_Uhj=5AK%7wq7$M!;$BItsuRGfMXI0wY3GyCDVfgX_hWY3#7Gw);O zH*enl*4ddM(0={p&xtqEggn7P(&U(FZUeJIOj02xXYw{z;lo~tdok{%3QzgTj%4~? z|0)?zJ|U*CKui(l9N4o>xXJE>+v_)kh~J1smGo=wC&7L)O~15GrpD(}^`26B*tVvq zI;wkAHEiAUs8{$KH!B(OmSGnkgEYT|;SC54B^3_B17WEMCO^(q#6>d3fqP0tG8I!? z;3_grrHPp~r3J2ae8tR|GU(DYWoFG(nhY@ww)b$Jbv>ske|2};uCyN^L5AtG#O{epm7j5BINBm#b?dTZac1#hckCSP`S;H4HGSS@<`<0}4Y46LKv8@a520uu;ZImdP*Wf*6Fmk>H8BD{1 z89ILfnAc5%h5(>rB0=NeDuuAG^AYTb)ttrXdW^F;X4y1Q47WPXsON=I2s#YiaUILh zZL3cAqpd(p!X@@HKNb{WL$4nQ;H`Ar$}VLeCxVUS&&Ap!WVb*FuEa-F_PUOZWt9UvaR>F1KOrI zh0~7#nJ3%1-j!p^$JP#SO6x+YG!k9D4yrOIS)frOnvl1E)sqoSO#KX~f&q14?95W;tG=sVf>#dl#dol(`$MYk zn9(ZX0;UDU-`7X&$RJtJbmtrc-T<>uGXAG)YeI!Hx%O)d=`+Gq2pYr3WZx|eA+PuT zJ7$ICW}e3hufB-K@5pzh`^uRJt#`o?D*hnuGK6FpNdXDw)H;dGu*$R33Bil%Vewob zPa$3My9y_rjSSHypA8oXI^oSM&}#;r_AS>51QB9f9Mxr^g9*^a(li2!CCVNH?S| zFWq?ahc_Q^eVbh0F5k}$EtYRxUAlTpThf{YhLF?zksVa*e6-J4=FZ;!rb(a+jh^zr Hg(vw3*_3Y~ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/svcbbase.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/svcbbase.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55b894d1c9eb10a23ad05486cbe4a9f0ffc4c192 GIT binary patch literal 29869 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/tlsabase.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/tlsabase.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..866664feed7d5ac83e4c16257d39ccc8e56110cf GIT binary patch literal 3376 zcmai0U2NOd6~6qFDB21wM~R&#?&P$~N^iB2)k|BfX=ktT;3U=1+FLO_l%q-7rb3b0 zC7ncujCz29%Gf%`Cr+IJLUN&qmG1Sc(8w}n0ls7lZLy@PQOH#Dr zD#!(N?z!jOAKi1m^Bw*pnG_JTzyIYwb0W7AnnOfl@;n(kK(FC&etIkZ2wr4@Qw+J3*q1o~EJ$|uT{#NH*NoEqBv zE}Vy&KZfBu@c(s0{&Swzs5~& zEe5`X9@o0Gc$-J4qH9#XYZ6J_9zS(ya&$~_^o3JIW>iygoXm`kGsKZmEG;wn+Qe(; zM>AMgZLB#G>v8jrGOK&Mqnom;tg2&IjkjHg1)bce`7aQ^q-w~GoGbAP zdY40}LRIK3IxthHf(l?e*ec8x71%0BZbj%(bJ@1J%m@ErC(`8q8Fb`u`%t9b% zyg1hU;CnHwk#HVZ=TC;kvOh_d9kMtRw511^vU7G(&#JaLUCyd=itCoMS9I%2)-aUSqF#mvbh4V|WP`(;tZO>TG$3HORK_F`7E&!_47VHb=7Ew?dJPd`&--%T`OSENQy3j-3XF8DR?gQ%ejQwSjd!>F~uzQ};TeRViPqqGQI zi>wYOxq0idV=U-*m*77O!3`Ww~69kF+{yi(p3Gdp6Y(cQB; zu`=;FL-z=c!J$3BA3p7mtL$D39qy|Qtqom2vHtqH`{CmIi|h90>znTH7j7(Un%jfh zuWp~cEsovh$8Z|XeTZqu0p9NoC4u7u6-FlNrqn`!{k0@WFY;A zSuFq$$=3ipz6=nQn89{2z))|5p~!XymN*KCCA7z*RJxX2OTH&oxyIq6wR3Cd-g~9W zH+l|7Rf+g5@!XDht}eb(W$%c6yW-%EIJoh2UHn>={kPbA{rUCrjq{r? z-2B1zt95boHa{9*CZYg1l0?(T%-=wP+7uB?A*@GWjvAyqDn#`pGLtIQflR>Kflju< z^*|X^fduP7t3>q(Dq-gt9jT^;7z&kie+1ds+fc=VspBp0hSK(1P_>yuNqiyuN$H1CAg;Z zG6*OeslM9rwd3nQ+?e`Z{-gZO^dATQFmQ|gtMF%`p1N3#HTdpTVMVxp_GA8umOfH@ zaqY!_@&jLfz$ocUu=%5sUZYy!fv|V15>aZ#qJ-l#`WWn4;WQ@fS-+3%r=Y~;JO<*7 z30UqD?A#@qM0)~B$}sdvqDR>%#ql#Rr!C21B?UV=4)W|d;v<)G2t|aM6l774pD`rB zaf(jl`~^ScfT$^n$xY=ztMknL=d!i$?!Wz)lE~D zB?|X~)geLtjvo(9GRYTD5XU7DUo4~lvZwC>i9EkFai8Tlsd*^IeV5wnL9x`Q-=Dn4 z@Z1>HWMH&M`)Zoup4<~)e2-?hi-GIDulo1)x0eKbiX8PU2rmiccGxHu=Urvm)bVj* zOAuR7fql2H`k%vz{h24oOzsU*r~O@Tpx^{jVju7?1>t)cUw|RGqcaX-kD(~4`81+> zK0(RPQ2G-DN*anJ&Al`5(}7*8e~0S7PY+QSsj5S2?CRSqZ#NMc!zJlspZhNVoc{w- CXvWb1 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/txtbase.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/txtbase.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f5bdfec998a9ae8aea743d4779fc189df37743f GIT binary patch literal 5179 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/util.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/rdtypes/__pycache__/util.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56733dd26ff427239a41ad91a362566a4ffced1a GIT binary patch literal 13864 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 Pm(%{0t#;Z=*6@D;2%qz6 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/dnskeybase.py b/venv/lib/python3.12/site-packages/dns/rdtypes/dnskeybase.py new file mode 100644 index 0000000..fb49f92 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/dnskeybase.py @@ -0,0 +1,83 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2004-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import base64 +import enum +import struct + +import dns.dnssectypes +import dns.exception +import dns.immutable +import dns.rdata + +# wildcard import +__all__ = ["SEP", "REVOKE", "ZONE"] # noqa: F822 + + +class Flag(enum.IntFlag): + SEP = 0x0001 + REVOKE = 0x0080 + ZONE = 0x0100 + + +@dns.immutable.immutable +class DNSKEYBase(dns.rdata.Rdata): + """Base class for rdata that is like a DNSKEY record""" + + __slots__ = ["flags", "protocol", "algorithm", "key"] + + def __init__(self, rdclass, rdtype, flags, protocol, algorithm, key): + super().__init__(rdclass, rdtype) + self.flags = Flag(self._as_uint16(flags)) + self.protocol = self._as_uint8(protocol) + self.algorithm = dns.dnssectypes.Algorithm.make(algorithm) + self.key = self._as_bytes(key) + + def to_text(self, origin=None, relativize=True, **kw): + key = dns.rdata._base64ify(self.key, **kw) # pyright: ignore + return f"{self.flags} {self.protocol} {self.algorithm} {key}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + flags = tok.get_uint16() + protocol = tok.get_uint8() + algorithm = tok.get_string() + b64 = tok.concatenate_remaining_identifiers().encode() + key = base64.b64decode(b64) + return cls(rdclass, rdtype, flags, protocol, algorithm, key) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + header = struct.pack("!HBB", self.flags, self.protocol, self.algorithm) + file.write(header) + file.write(self.key) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + header = parser.get_struct("!HBB") + key = parser.get_remaining() + return cls(rdclass, rdtype, header[0], header[1], header[2], key) + + +### BEGIN generated Flag constants + +SEP = Flag.SEP +REVOKE = Flag.REVOKE +ZONE = Flag.ZONE + +### END generated Flag constants diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/dsbase.py b/venv/lib/python3.12/site-packages/dns/rdtypes/dsbase.py new file mode 100644 index 0000000..8e05c2a --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/dsbase.py @@ -0,0 +1,83 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2010, 2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import binascii +import struct + +import dns.dnssectypes +import dns.immutable +import dns.rdata +import dns.rdatatype + + +@dns.immutable.immutable +class DSBase(dns.rdata.Rdata): + """Base class for rdata that is like a DS record""" + + __slots__ = ["key_tag", "algorithm", "digest_type", "digest"] + + # Digest types registry: + # https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml + _digest_length_by_type = { + 1: 20, # SHA-1, RFC 3658 Sec. 2.4 + 2: 32, # SHA-256, RFC 4509 Sec. 2.2 + 3: 32, # GOST R 34.11-94, RFC 5933 Sec. 4 in conjunction with RFC 4490 Sec. 2.1 + 4: 48, # SHA-384, RFC 6605 Sec. 2 + } + + def __init__(self, rdclass, rdtype, key_tag, algorithm, digest_type, digest): + super().__init__(rdclass, rdtype) + self.key_tag = self._as_uint16(key_tag) + self.algorithm = dns.dnssectypes.Algorithm.make(algorithm) + self.digest_type = dns.dnssectypes.DSDigest.make(self._as_uint8(digest_type)) + self.digest = self._as_bytes(digest) + try: + if len(self.digest) != self._digest_length_by_type[self.digest_type]: + raise ValueError("digest length inconsistent with digest type") + except KeyError: + if self.digest_type == 0: # reserved, RFC 3658 Sec. 2.4 + raise ValueError("digest type 0 is reserved") + + def to_text(self, origin=None, relativize=True, **kw): + kw = kw.copy() + chunksize = kw.pop("chunksize", 128) + digest = dns.rdata._hexify( + self.digest, chunksize=chunksize, **kw # pyright: ignore + ) + return f"{self.key_tag} {self.algorithm} {self.digest_type} {digest}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + key_tag = tok.get_uint16() + algorithm = tok.get_string() + digest_type = tok.get_uint8() + digest = tok.concatenate_remaining_identifiers().encode() + digest = binascii.unhexlify(digest) + return cls(rdclass, rdtype, key_tag, algorithm, digest_type, digest) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + header = struct.pack("!HBB", self.key_tag, self.algorithm, self.digest_type) + file.write(header) + file.write(self.digest) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + header = parser.get_struct("!HBB") + digest = parser.get_remaining() + return cls(rdclass, rdtype, header[0], header[1], header[2], digest) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/euibase.py b/venv/lib/python3.12/site-packages/dns/rdtypes/euibase.py new file mode 100644 index 0000000..4eb82eb --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/euibase.py @@ -0,0 +1,73 @@ +# Copyright (C) 2015 Red Hat, Inc. +# Author: Petr Spacek +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 RED HAT DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import binascii + +import dns.exception +import dns.immutable +import dns.rdata + + +@dns.immutable.immutable +class EUIBase(dns.rdata.Rdata): + """EUIxx record""" + + # see: rfc7043.txt + + __slots__ = ["eui"] + # redefine these in subclasses + byte_len = 0 + text_len = 0 + # byte_len = 6 # 0123456789ab (in hex) + # text_len = byte_len * 3 - 1 # 01-23-45-67-89-ab + + def __init__(self, rdclass, rdtype, eui): + super().__init__(rdclass, rdtype) + self.eui = self._as_bytes(eui) + if len(self.eui) != self.byte_len: + raise dns.exception.FormError( + f"EUI{self.byte_len * 8} rdata has to have {self.byte_len} bytes" + ) + + def to_text(self, origin=None, relativize=True, **kw): + return dns.rdata._hexify(self.eui, chunksize=2, separator=b"-", **kw) + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + text = tok.get_string() + if len(text) != cls.text_len: + raise dns.exception.SyntaxError( + f"Input text must have {cls.text_len} characters" + ) + for i in range(2, cls.byte_len * 3 - 1, 3): + if text[i] != "-": + raise dns.exception.SyntaxError(f"Dash expected at position {i}") + text = text.replace("-", "") + try: + data = binascii.unhexlify(text.encode()) + except (ValueError, TypeError) as ex: + raise dns.exception.SyntaxError(f"Hex decoding error: {str(ex)}") + return cls(rdclass, rdtype, data) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(self.eui) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + eui = parser.get_bytes(cls.byte_len) + return cls(rdclass, rdtype, eui) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/mxbase.py b/venv/lib/python3.12/site-packages/dns/rdtypes/mxbase.py new file mode 100644 index 0000000..5d33e61 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/mxbase.py @@ -0,0 +1,87 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""MX-like base classes.""" + +import struct + +import dns.exception +import dns.immutable +import dns.name +import dns.rdata +import dns.rdtypes.util + + +@dns.immutable.immutable +class MXBase(dns.rdata.Rdata): + """Base class for rdata that is like an MX record.""" + + __slots__ = ["preference", "exchange"] + + def __init__(self, rdclass, rdtype, preference, exchange): + super().__init__(rdclass, rdtype) + self.preference = self._as_uint16(preference) + self.exchange = self._as_name(exchange) + + def to_text(self, origin=None, relativize=True, **kw): + exchange = self.exchange.choose_relativity(origin, relativize) + return f"{self.preference} {exchange}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + preference = tok.get_uint16() + exchange = tok.get_name(origin, relativize, relativize_to) + return cls(rdclass, rdtype, preference, exchange) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + pref = struct.pack("!H", self.preference) + file.write(pref) + self.exchange.to_wire(file, compress, origin, canonicalize) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + preference = parser.get_uint16() + exchange = parser.get_name(origin) + return cls(rdclass, rdtype, preference, exchange) + + def _processing_priority(self): + return self.preference + + @classmethod + def _processing_order(cls, iterable): + return dns.rdtypes.util.priority_processing_order(iterable) + + +@dns.immutable.immutable +class UncompressedMX(MXBase): + """Base class for rdata that is like an MX record, but whose name + is not compressed when converted to DNS wire format, and whose + digestable form is not downcased.""" + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + super()._to_wire(file, None, origin, False) + + +@dns.immutable.immutable +class UncompressedDowncasingMX(MXBase): + """Base class for rdata that is like an MX record, but whose name + is not compressed when convert to DNS wire format.""" + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + super()._to_wire(file, None, origin, canonicalize) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/nsbase.py b/venv/lib/python3.12/site-packages/dns/rdtypes/nsbase.py new file mode 100644 index 0000000..904224f --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/nsbase.py @@ -0,0 +1,63 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""NS-like base classes.""" + +import dns.exception +import dns.immutable +import dns.name +import dns.rdata + + +@dns.immutable.immutable +class NSBase(dns.rdata.Rdata): + """Base class for rdata that is like an NS record.""" + + __slots__ = ["target"] + + def __init__(self, rdclass, rdtype, target): + super().__init__(rdclass, rdtype) + self.target = self._as_name(target) + + def to_text(self, origin=None, relativize=True, **kw): + target = self.target.choose_relativity(origin, relativize) + return str(target) + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + target = tok.get_name(origin, relativize, relativize_to) + return cls(rdclass, rdtype, target) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + self.target.to_wire(file, compress, origin, canonicalize) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + target = parser.get_name(origin) + return cls(rdclass, rdtype, target) + + +@dns.immutable.immutable +class UncompressedNS(NSBase): + """Base class for rdata that is like an NS record, but whose name + is not compressed when convert to DNS wire format, and whose + digestable form is not downcased.""" + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + self.target.to_wire(file, None, origin, False) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/svcbbase.py b/venv/lib/python3.12/site-packages/dns/rdtypes/svcbbase.py new file mode 100644 index 0000000..7338b66 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/svcbbase.py @@ -0,0 +1,587 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import base64 +import enum +import struct +from typing import Any, Dict + +import dns.enum +import dns.exception +import dns.immutable +import dns.ipv4 +import dns.ipv6 +import dns.name +import dns.rdata +import dns.rdtypes.util +import dns.renderer +import dns.tokenizer +import dns.wire + +# Until there is an RFC, this module is experimental and may be changed in +# incompatible ways. + + +class UnknownParamKey(dns.exception.DNSException): + """Unknown SVCB ParamKey""" + + +class ParamKey(dns.enum.IntEnum): + """SVCB ParamKey""" + + MANDATORY = 0 + ALPN = 1 + NO_DEFAULT_ALPN = 2 + PORT = 3 + IPV4HINT = 4 + ECH = 5 + IPV6HINT = 6 + DOHPATH = 7 + OHTTP = 8 + + @classmethod + def _maximum(cls): + return 65535 + + @classmethod + def _short_name(cls): + return "SVCBParamKey" + + @classmethod + def _prefix(cls): + return "KEY" + + @classmethod + def _unknown_exception_class(cls): + return UnknownParamKey + + +class Emptiness(enum.IntEnum): + NEVER = 0 + ALWAYS = 1 + ALLOWED = 2 + + +def _validate_key(key): + force_generic = False + if isinstance(key, bytes): + # We decode to latin-1 so we get 0-255 as valid and do NOT interpret + # UTF-8 sequences + key = key.decode("latin-1") + if isinstance(key, str): + if key.lower().startswith("key"): + force_generic = True + if key[3:].startswith("0") and len(key) != 4: + # key has leading zeros + raise ValueError("leading zeros in key") + key = key.replace("-", "_") + return (ParamKey.make(key), force_generic) + + +def key_to_text(key): + return ParamKey.to_text(key).replace("_", "-").lower() + + +# Like rdata escapify, but escapes ',' too. + +_escaped = b'",\\' + + +def _escapify(qstring): + text = "" + for c in qstring: + if c in _escaped: + text += "\\" + chr(c) + elif c >= 0x20 and c < 0x7F: + text += chr(c) + else: + text += f"\\{c:03d}" + return text + + +def _unescape(value: str) -> bytes: + if value == "": + return b"" + unescaped = b"" + l = len(value) + i = 0 + while i < l: + c = value[i] + i += 1 + if c == "\\": + if i >= l: # pragma: no cover (can't happen via tokenizer get()) + raise dns.exception.UnexpectedEnd + c = value[i] + i += 1 + if c.isdigit(): + if i >= l: + raise dns.exception.UnexpectedEnd + c2 = value[i] + i += 1 + if i >= l: + raise dns.exception.UnexpectedEnd + c3 = value[i] + i += 1 + if not (c2.isdigit() and c3.isdigit()): + raise dns.exception.SyntaxError + codepoint = int(c) * 100 + int(c2) * 10 + int(c3) + if codepoint > 255: + raise dns.exception.SyntaxError + unescaped += b"%c" % (codepoint) + continue + unescaped += c.encode() + return unescaped + + +def _split(value): + l = len(value) + i = 0 + items = [] + unescaped = b"" + while i < l: + c = value[i] + i += 1 + if c == ord("\\"): + if i >= l: # pragma: no cover (can't happen via tokenizer get()) + raise dns.exception.UnexpectedEnd + c = value[i] + i += 1 + unescaped += b"%c" % (c) + elif c == ord(","): + items.append(unescaped) + unescaped = b"" + else: + unescaped += b"%c" % (c) + items.append(unescaped) + return items + + +@dns.immutable.immutable +class Param: + """Abstract base class for SVCB parameters""" + + @classmethod + def emptiness(cls) -> Emptiness: + return Emptiness.NEVER + + +@dns.immutable.immutable +class GenericParam(Param): + """Generic SVCB parameter""" + + def __init__(self, value): + self.value = dns.rdata.Rdata._as_bytes(value, True) + + @classmethod + def emptiness(cls): + return Emptiness.ALLOWED + + @classmethod + def from_value(cls, value): + if value is None or len(value) == 0: + return None + else: + return cls(_unescape(value)) + + def to_text(self): + return '"' + dns.rdata._escapify(self.value) + '"' + + @classmethod + def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 + value = parser.get_bytes(parser.remaining()) + if len(value) == 0: + return None + else: + return cls(value) + + def to_wire(self, file, origin=None): # pylint: disable=W0613 + file.write(self.value) + + +@dns.immutable.immutable +class MandatoryParam(Param): + def __init__(self, keys): + # check for duplicates + keys = sorted([_validate_key(key)[0] for key in keys]) + prior_k = None + for k in keys: + if k == prior_k: + raise ValueError(f"duplicate key {k:d}") + prior_k = k + if k == ParamKey.MANDATORY: + raise ValueError("listed the mandatory key as mandatory") + self.keys = tuple(keys) + + @classmethod + def from_value(cls, value): + keys = [k.encode() for k in value.split(",")] + return cls(keys) + + def to_text(self): + return '"' + ",".join([key_to_text(key) for key in self.keys]) + '"' + + @classmethod + def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 + keys = [] + last_key = -1 + while parser.remaining() > 0: + key = parser.get_uint16() + if key < last_key: + raise dns.exception.FormError("manadatory keys not ascending") + last_key = key + keys.append(key) + return cls(keys) + + def to_wire(self, file, origin=None): # pylint: disable=W0613 + for key in self.keys: + file.write(struct.pack("!H", key)) + + +@dns.immutable.immutable +class ALPNParam(Param): + def __init__(self, ids): + self.ids = dns.rdata.Rdata._as_tuple( + ids, lambda x: dns.rdata.Rdata._as_bytes(x, True, 255, False) + ) + + @classmethod + def from_value(cls, value): + return cls(_split(_unescape(value))) + + def to_text(self): + value = ",".join([_escapify(id) for id in self.ids]) + return '"' + dns.rdata._escapify(value.encode()) + '"' + + @classmethod + def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 + ids = [] + while parser.remaining() > 0: + id = parser.get_counted_bytes() + ids.append(id) + return cls(ids) + + def to_wire(self, file, origin=None): # pylint: disable=W0613 + for id in self.ids: + file.write(struct.pack("!B", len(id))) + file.write(id) + + +@dns.immutable.immutable +class NoDefaultALPNParam(Param): + # We don't ever expect to instantiate this class, but we need + # a from_value() and a from_wire_parser(), so we just return None + # from the class methods when things are OK. + + @classmethod + def emptiness(cls): + return Emptiness.ALWAYS + + @classmethod + def from_value(cls, value): + if value is None or value == "": + return None + else: + raise ValueError("no-default-alpn with non-empty value") + + def to_text(self): + raise NotImplementedError # pragma: no cover + + @classmethod + def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 + if parser.remaining() != 0: + raise dns.exception.FormError + return None + + def to_wire(self, file, origin=None): # pylint: disable=W0613 + raise NotImplementedError # pragma: no cover + + +@dns.immutable.immutable +class PortParam(Param): + def __init__(self, port): + self.port = dns.rdata.Rdata._as_uint16(port) + + @classmethod + def from_value(cls, value): + value = int(value) + return cls(value) + + def to_text(self): + return f'"{self.port}"' + + @classmethod + def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 + port = parser.get_uint16() + return cls(port) + + def to_wire(self, file, origin=None): # pylint: disable=W0613 + file.write(struct.pack("!H", self.port)) + + +@dns.immutable.immutable +class IPv4HintParam(Param): + def __init__(self, addresses): + self.addresses = dns.rdata.Rdata._as_tuple( + addresses, dns.rdata.Rdata._as_ipv4_address + ) + + @classmethod + def from_value(cls, value): + addresses = value.split(",") + return cls(addresses) + + def to_text(self): + return '"' + ",".join(self.addresses) + '"' + + @classmethod + def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 + addresses = [] + while parser.remaining() > 0: + ip = parser.get_bytes(4) + addresses.append(dns.ipv4.inet_ntoa(ip)) + return cls(addresses) + + def to_wire(self, file, origin=None): # pylint: disable=W0613 + for address in self.addresses: + file.write(dns.ipv4.inet_aton(address)) + + +@dns.immutable.immutable +class IPv6HintParam(Param): + def __init__(self, addresses): + self.addresses = dns.rdata.Rdata._as_tuple( + addresses, dns.rdata.Rdata._as_ipv6_address + ) + + @classmethod + def from_value(cls, value): + addresses = value.split(",") + return cls(addresses) + + def to_text(self): + return '"' + ",".join(self.addresses) + '"' + + @classmethod + def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 + addresses = [] + while parser.remaining() > 0: + ip = parser.get_bytes(16) + addresses.append(dns.ipv6.inet_ntoa(ip)) + return cls(addresses) + + def to_wire(self, file, origin=None): # pylint: disable=W0613 + for address in self.addresses: + file.write(dns.ipv6.inet_aton(address)) + + +@dns.immutable.immutable +class ECHParam(Param): + def __init__(self, ech): + self.ech = dns.rdata.Rdata._as_bytes(ech, True) + + @classmethod + def from_value(cls, value): + if "\\" in value: + raise ValueError("escape in ECH value") + value = base64.b64decode(value.encode()) + return cls(value) + + def to_text(self): + b64 = base64.b64encode(self.ech).decode("ascii") + return f'"{b64}"' + + @classmethod + def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 + value = parser.get_bytes(parser.remaining()) + return cls(value) + + def to_wire(self, file, origin=None): # pylint: disable=W0613 + file.write(self.ech) + + +@dns.immutable.immutable +class OHTTPParam(Param): + # We don't ever expect to instantiate this class, but we need + # a from_value() and a from_wire_parser(), so we just return None + # from the class methods when things are OK. + + @classmethod + def emptiness(cls): + return Emptiness.ALWAYS + + @classmethod + def from_value(cls, value): + if value is None or value == "": + return None + else: + raise ValueError("ohttp with non-empty value") + + def to_text(self): + raise NotImplementedError # pragma: no cover + + @classmethod + def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613 + if parser.remaining() != 0: + raise dns.exception.FormError + return None + + def to_wire(self, file, origin=None): # pylint: disable=W0613 + raise NotImplementedError # pragma: no cover + + +_class_for_key: Dict[ParamKey, Any] = { + ParamKey.MANDATORY: MandatoryParam, + ParamKey.ALPN: ALPNParam, + ParamKey.NO_DEFAULT_ALPN: NoDefaultALPNParam, + ParamKey.PORT: PortParam, + ParamKey.IPV4HINT: IPv4HintParam, + ParamKey.ECH: ECHParam, + ParamKey.IPV6HINT: IPv6HintParam, + ParamKey.OHTTP: OHTTPParam, +} + + +def _validate_and_define(params, key, value): + (key, force_generic) = _validate_key(_unescape(key)) + if key in params: + raise SyntaxError(f'duplicate key "{key:d}"') + cls = _class_for_key.get(key, GenericParam) + emptiness = cls.emptiness() + if value is None: + if emptiness == Emptiness.NEVER: + raise SyntaxError("value cannot be empty") + value = cls.from_value(value) + else: + if force_generic: + value = cls.from_wire_parser(dns.wire.Parser(_unescape(value))) + else: + value = cls.from_value(value) + params[key] = value + + +@dns.immutable.immutable +class SVCBBase(dns.rdata.Rdata): + """Base class for SVCB-like records""" + + # see: draft-ietf-dnsop-svcb-https-11 + + __slots__ = ["priority", "target", "params"] + + def __init__(self, rdclass, rdtype, priority, target, params): + super().__init__(rdclass, rdtype) + self.priority = self._as_uint16(priority) + self.target = self._as_name(target) + for k, v in params.items(): + k = ParamKey.make(k) + if not isinstance(v, Param) and v is not None: + raise ValueError(f"{k:d} not a Param") + self.params = dns.immutable.Dict(params) + # Make sure any parameter listed as mandatory is present in the + # record. + mandatory = params.get(ParamKey.MANDATORY) + if mandatory: + for key in mandatory.keys: + # Note we have to say "not in" as we have None as a value + # so a get() and a not None test would be wrong. + if key not in params: + raise ValueError(f"key {key:d} declared mandatory but not present") + # The no-default-alpn parameter requires the alpn parameter. + if ParamKey.NO_DEFAULT_ALPN in params: + if ParamKey.ALPN not in params: + raise ValueError("no-default-alpn present, but alpn missing") + + def to_text(self, origin=None, relativize=True, **kw): + target = self.target.choose_relativity(origin, relativize) + params = [] + for key in sorted(self.params.keys()): + value = self.params[key] + if value is None: + params.append(key_to_text(key)) + else: + kv = key_to_text(key) + "=" + value.to_text() + params.append(kv) + if len(params) > 0: + space = " " + else: + space = "" + return f"{self.priority} {target}{space}{' '.join(params)}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + priority = tok.get_uint16() + target = tok.get_name(origin, relativize, relativize_to) + if priority == 0: + token = tok.get() + if not token.is_eol_or_eof(): + raise SyntaxError("parameters in AliasMode") + tok.unget(token) + params = {} + while True: + token = tok.get() + if token.is_eol_or_eof(): + tok.unget(token) + break + if token.ttype != dns.tokenizer.IDENTIFIER: + raise SyntaxError("parameter is not an identifier") + equals = token.value.find("=") + if equals == len(token.value) - 1: + # 'key=', so next token should be a quoted string without + # any intervening whitespace. + key = token.value[:-1] + token = tok.get(want_leading=True) + if token.ttype != dns.tokenizer.QUOTED_STRING: + raise SyntaxError("whitespace after =") + value = token.value + elif equals > 0: + # key=value + key = token.value[:equals] + value = token.value[equals + 1 :] + elif equals == 0: + # =key + raise SyntaxError('parameter cannot start with "="') + else: + # key + key = token.value + value = None + _validate_and_define(params, key, value) + return cls(rdclass, rdtype, priority, target, params) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + file.write(struct.pack("!H", self.priority)) + self.target.to_wire(file, None, origin, False) + for key in sorted(self.params): + file.write(struct.pack("!H", key)) + value = self.params[key] + with dns.renderer.prefixed_length(file, 2): + # Note that we're still writing a length of zero if the value is None + if value is not None: + value.to_wire(file, origin) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + priority = parser.get_uint16() + target = parser.get_name(origin) + if priority == 0 and parser.remaining() != 0: + raise dns.exception.FormError("parameters in AliasMode") + params = {} + prior_key = -1 + while parser.remaining() > 0: + key = parser.get_uint16() + if key < prior_key: + raise dns.exception.FormError("keys not in order") + prior_key = key + vlen = parser.get_uint16() + pkey = ParamKey.make(key) + pcls = _class_for_key.get(pkey, GenericParam) + with parser.restrict_to(vlen): + value = pcls.from_wire_parser(parser, origin) + params[pkey] = value + return cls(rdclass, rdtype, priority, target, params) + + def _processing_priority(self): + return self.priority + + @classmethod + def _processing_order(cls, iterable): + return dns.rdtypes.util.priority_processing_order(iterable) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/tlsabase.py b/venv/lib/python3.12/site-packages/dns/rdtypes/tlsabase.py new file mode 100644 index 0000000..ddc196f --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/tlsabase.py @@ -0,0 +1,69 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2005-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import binascii +import struct + +import dns.immutable +import dns.rdata +import dns.rdatatype + + +@dns.immutable.immutable +class TLSABase(dns.rdata.Rdata): + """Base class for TLSA and SMIMEA records""" + + # see: RFC 6698 + + __slots__ = ["usage", "selector", "mtype", "cert"] + + def __init__(self, rdclass, rdtype, usage, selector, mtype, cert): + super().__init__(rdclass, rdtype) + self.usage = self._as_uint8(usage) + self.selector = self._as_uint8(selector) + self.mtype = self._as_uint8(mtype) + self.cert = self._as_bytes(cert) + + def to_text(self, origin=None, relativize=True, **kw): + kw = kw.copy() + chunksize = kw.pop("chunksize", 128) + cert = dns.rdata._hexify( + self.cert, chunksize=chunksize, **kw # pyright: ignore + ) + return f"{self.usage} {self.selector} {self.mtype} {cert}" + + @classmethod + def from_text( + cls, rdclass, rdtype, tok, origin=None, relativize=True, relativize_to=None + ): + usage = tok.get_uint8() + selector = tok.get_uint8() + mtype = tok.get_uint8() + cert = tok.concatenate_remaining_identifiers().encode() + cert = binascii.unhexlify(cert) + return cls(rdclass, rdtype, usage, selector, mtype, cert) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + header = struct.pack("!BBB", self.usage, self.selector, self.mtype) + file.write(header) + file.write(self.cert) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + header = parser.get_struct("BBB") + cert = parser.get_remaining() + return cls(rdclass, rdtype, header[0], header[1], header[2], cert) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/txtbase.py b/venv/lib/python3.12/site-packages/dns/rdtypes/txtbase.py new file mode 100644 index 0000000..5e5b24f --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/txtbase.py @@ -0,0 +1,109 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2006-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""TXT-like base class.""" + +from typing import Any, Dict, Iterable, Tuple + +import dns.exception +import dns.immutable +import dns.name +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.renderer +import dns.tokenizer + + +@dns.immutable.immutable +class TXTBase(dns.rdata.Rdata): + """Base class for rdata that is like a TXT record (see RFC 1035).""" + + __slots__ = ["strings"] + + def __init__( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + strings: Iterable[bytes | str], + ): + """Initialize a TXT-like rdata. + + *rdclass*, an ``int`` is the rdataclass of the Rdata. + + *rdtype*, an ``int`` is the rdatatype of the Rdata. + + *strings*, a tuple of ``bytes`` + """ + super().__init__(rdclass, rdtype) + self.strings: Tuple[bytes] = self._as_tuple( + strings, lambda x: self._as_bytes(x, True, 255) + ) + if len(self.strings) == 0: + raise ValueError("the list of strings must not be empty") + + def to_text( + self, + origin: dns.name.Name | None = None, + relativize: bool = True, + **kw: Dict[str, Any], + ) -> str: + txt = "" + prefix = "" + for s in self.strings: + txt += f'{prefix}"{dns.rdata._escapify(s)}"' + prefix = " " + return txt + + @classmethod + def from_text( + cls, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + tok: dns.tokenizer.Tokenizer, + origin: dns.name.Name | None = None, + relativize: bool = True, + relativize_to: dns.name.Name | None = None, + ) -> dns.rdata.Rdata: + strings = [] + for token in tok.get_remaining(): + token = token.unescape_to_bytes() + # The 'if' below is always true in the current code, but we + # are leaving this check in in case things change some day. + if not ( + token.is_quoted_string() or token.is_identifier() + ): # pragma: no cover + raise dns.exception.SyntaxError("expected a string") + if len(token.value) > 255: + raise dns.exception.SyntaxError("string too long") + strings.append(token.value) + if len(strings) == 0: + raise dns.exception.UnexpectedEnd + return cls(rdclass, rdtype, strings) + + def _to_wire(self, file, compress=None, origin=None, canonicalize=False): + for s in self.strings: + with dns.renderer.prefixed_length(file, 1): + file.write(s) + + @classmethod + def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): + strings = [] + while parser.remaining() > 0: + s = parser.get_counted_bytes() + strings.append(s) + return cls(rdclass, rdtype, strings) diff --git a/venv/lib/python3.12/site-packages/dns/rdtypes/util.py b/venv/lib/python3.12/site-packages/dns/rdtypes/util.py new file mode 100644 index 0000000..c17b154 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rdtypes/util.py @@ -0,0 +1,269 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2006, 2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import collections +import random +import struct +from typing import Any, Iterable, List, Tuple + +import dns.exception +import dns.ipv4 +import dns.ipv6 +import dns.name +import dns.rdata +import dns.rdatatype +import dns.tokenizer +import dns.wire + + +class Gateway: + """A helper class for the IPSECKEY gateway and AMTRELAY relay fields""" + + name = "" + + def __init__(self, type: Any, gateway: str | dns.name.Name | None = None): + self.type = dns.rdata.Rdata._as_uint8(type) + self.gateway = gateway + self._check() + + @classmethod + def _invalid_type(cls, gateway_type): + return f"invalid {cls.name} type: {gateway_type}" + + def _check(self): + if self.type == 0: + if self.gateway not in (".", None): + raise SyntaxError(f"invalid {self.name} for type 0") + self.gateway = None + elif self.type == 1: + # check that it's OK + assert isinstance(self.gateway, str) + dns.ipv4.inet_aton(self.gateway) + elif self.type == 2: + # check that it's OK + assert isinstance(self.gateway, str) + dns.ipv6.inet_aton(self.gateway) + elif self.type == 3: + if not isinstance(self.gateway, dns.name.Name): + raise SyntaxError(f"invalid {self.name}; not a name") + else: + raise SyntaxError(self._invalid_type(self.type)) + + def to_text(self, origin=None, relativize=True): + if self.type == 0: + return "." + elif self.type in (1, 2): + return self.gateway + elif self.type == 3: + assert isinstance(self.gateway, dns.name.Name) + return str(self.gateway.choose_relativity(origin, relativize)) + else: + raise ValueError(self._invalid_type(self.type)) # pragma: no cover + + @classmethod + def from_text( + cls, gateway_type, tok, origin=None, relativize=True, relativize_to=None + ): + if gateway_type in (0, 1, 2): + gateway = tok.get_string() + elif gateway_type == 3: + gateway = tok.get_name(origin, relativize, relativize_to) + else: + raise dns.exception.SyntaxError( + cls._invalid_type(gateway_type) + ) # pragma: no cover + return cls(gateway_type, gateway) + + # pylint: disable=unused-argument + def to_wire(self, file, compress=None, origin=None, canonicalize=False): + if self.type == 0: + pass + elif self.type == 1: + assert isinstance(self.gateway, str) + file.write(dns.ipv4.inet_aton(self.gateway)) + elif self.type == 2: + assert isinstance(self.gateway, str) + file.write(dns.ipv6.inet_aton(self.gateway)) + elif self.type == 3: + assert isinstance(self.gateway, dns.name.Name) + self.gateway.to_wire(file, None, origin, False) + else: + raise ValueError(self._invalid_type(self.type)) # pragma: no cover + + # pylint: enable=unused-argument + + @classmethod + def from_wire_parser(cls, gateway_type, parser, origin=None): + if gateway_type == 0: + gateway = None + elif gateway_type == 1: + gateway = dns.ipv4.inet_ntoa(parser.get_bytes(4)) + elif gateway_type == 2: + gateway = dns.ipv6.inet_ntoa(parser.get_bytes(16)) + elif gateway_type == 3: + gateway = parser.get_name(origin) + else: + raise dns.exception.FormError(cls._invalid_type(gateway_type)) + return cls(gateway_type, gateway) + + +class Bitmap: + """A helper class for the NSEC/NSEC3/CSYNC type bitmaps""" + + type_name = "" + + def __init__(self, windows: Iterable[Tuple[int, bytes]] | None = None): + last_window = -1 + if windows is None: + windows = [] + self.windows = windows + for window, bitmap in self.windows: + if not isinstance(window, int): + raise ValueError(f"bad {self.type_name} window type") + if window <= last_window: + raise ValueError(f"bad {self.type_name} window order") + if window > 256: + raise ValueError(f"bad {self.type_name} window number") + last_window = window + if not isinstance(bitmap, bytes): + raise ValueError(f"bad {self.type_name} octets type") + if len(bitmap) == 0 or len(bitmap) > 32: + raise ValueError(f"bad {self.type_name} octets") + + def to_text(self) -> str: + text = "" + for window, bitmap in self.windows: + bits = [] + for i, byte in enumerate(bitmap): + for j in range(0, 8): + if byte & (0x80 >> j): + rdtype = dns.rdatatype.RdataType.make(window * 256 + i * 8 + j) + bits.append(dns.rdatatype.to_text(rdtype)) + text += " " + " ".join(bits) + return text + + @classmethod + def from_text(cls, tok: "dns.tokenizer.Tokenizer") -> "Bitmap": + rdtypes = [] + for token in tok.get_remaining(): + rdtype = dns.rdatatype.from_text(token.unescape().value) + if rdtype == 0: + raise dns.exception.SyntaxError(f"{cls.type_name} with bit 0") + rdtypes.append(rdtype) + return cls.from_rdtypes(rdtypes) + + @classmethod + def from_rdtypes(cls, rdtypes: List[dns.rdatatype.RdataType]) -> "Bitmap": + rdtypes = sorted(rdtypes) + window = 0 + octets = 0 + prior_rdtype = 0 + bitmap = bytearray(b"\0" * 32) + windows = [] + for rdtype in rdtypes: + if rdtype == prior_rdtype: + continue + prior_rdtype = rdtype + new_window = rdtype // 256 + if new_window != window: + if octets != 0: + windows.append((window, bytes(bitmap[0:octets]))) + bitmap = bytearray(b"\0" * 32) + window = new_window + offset = rdtype % 256 + byte = offset // 8 + bit = offset % 8 + octets = byte + 1 + bitmap[byte] = bitmap[byte] | (0x80 >> bit) + if octets != 0: + windows.append((window, bytes(bitmap[0:octets]))) + return cls(windows) + + def to_wire(self, file: Any) -> None: + for window, bitmap in self.windows: + file.write(struct.pack("!BB", window, len(bitmap))) + file.write(bitmap) + + @classmethod + def from_wire_parser(cls, parser: "dns.wire.Parser") -> "Bitmap": + windows = [] + while parser.remaining() > 0: + window = parser.get_uint8() + bitmap = parser.get_counted_bytes() + windows.append((window, bitmap)) + return cls(windows) + + +def _priority_table(items): + by_priority = collections.defaultdict(list) + for rdata in items: + by_priority[rdata._processing_priority()].append(rdata) + return by_priority + + +def priority_processing_order(iterable): + items = list(iterable) + if len(items) == 1: + return items + by_priority = _priority_table(items) + ordered = [] + for k in sorted(by_priority.keys()): + rdatas = by_priority[k] + random.shuffle(rdatas) + ordered.extend(rdatas) + return ordered + + +_no_weight = 0.1 + + +def weighted_processing_order(iterable): + items = list(iterable) + if len(items) == 1: + return items + by_priority = _priority_table(items) + ordered = [] + for k in sorted(by_priority.keys()): + rdatas = by_priority[k] + total = sum(rdata._processing_weight() or _no_weight for rdata in rdatas) + while len(rdatas) > 1: + r = random.uniform(0, total) + for n, rdata in enumerate(rdatas): # noqa: B007 + weight = rdata._processing_weight() or _no_weight + if weight > r: + break + r -= weight + total -= weight # pyright: ignore[reportPossiblyUnboundVariable] + # pylint: disable=undefined-loop-variable + ordered.append(rdata) # pyright: ignore[reportPossiblyUnboundVariable] + del rdatas[n] # pyright: ignore[reportPossiblyUnboundVariable] + ordered.append(rdatas[0]) + return ordered + + +def parse_formatted_hex(formatted, num_chunks, chunk_size, separator): + if len(formatted) != num_chunks * (chunk_size + 1) - 1: + raise ValueError("invalid formatted hex string") + value = b"" + for _ in range(num_chunks): + chunk = formatted[0:chunk_size] + value += int(chunk, 16).to_bytes(chunk_size // 2, "big") + formatted = formatted[chunk_size:] + if len(formatted) > 0 and formatted[0] != separator: + raise ValueError("invalid formatted hex string") + formatted = formatted[1:] + return value diff --git a/venv/lib/python3.12/site-packages/dns/renderer.py b/venv/lib/python3.12/site-packages/dns/renderer.py new file mode 100644 index 0000000..cc912b2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/renderer.py @@ -0,0 +1,355 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""Help for building DNS wire format messages""" + +import contextlib +import io +import random +import struct +import time + +import dns.edns +import dns.exception +import dns.rdataclass +import dns.rdatatype +import dns.tsig + +# Note we can't import dns.message for cicularity reasons + +QUESTION = 0 +ANSWER = 1 +AUTHORITY = 2 +ADDITIONAL = 3 + + +@contextlib.contextmanager +def prefixed_length(output, length_length): + output.write(b"\00" * length_length) + start = output.tell() + yield + end = output.tell() + length = end - start + if length > 0: + try: + output.seek(start - length_length) + try: + output.write(length.to_bytes(length_length, "big")) + except OverflowError: + raise dns.exception.FormError + finally: + output.seek(end) + + +class Renderer: + """Helper class for building DNS wire-format messages. + + Most applications can use the higher-level L{dns.message.Message} + class and its to_wire() method to generate wire-format messages. + This class is for those applications which need finer control + over the generation of messages. + + Typical use:: + + r = dns.renderer.Renderer(id=1, flags=0x80, max_size=512) + r.add_question(qname, qtype, qclass) + r.add_rrset(dns.renderer.ANSWER, rrset_1) + r.add_rrset(dns.renderer.ANSWER, rrset_2) + r.add_rrset(dns.renderer.AUTHORITY, ns_rrset) + r.add_rrset(dns.renderer.ADDITIONAL, ad_rrset_1) + r.add_rrset(dns.renderer.ADDITIONAL, ad_rrset_2) + r.add_edns(0, 0, 4096) + r.write_header() + r.add_tsig(keyname, secret, 300, 1, 0, '', request_mac) + wire = r.get_wire() + + If padding is going to be used, then the OPT record MUST be + written after everything else in the additional section except for + the TSIG (if any). + + output, an io.BytesIO, where rendering is written + + id: the message id + + flags: the message flags + + max_size: the maximum size of the message + + origin: the origin to use when rendering relative names + + compress: the compression table + + section: an int, the section currently being rendered + + counts: list of the number of RRs in each section + + mac: the MAC of the rendered message (if TSIG was used) + """ + + def __init__(self, id=None, flags=0, max_size=65535, origin=None): + """Initialize a new renderer.""" + + self.output = io.BytesIO() + if id is None: + self.id = random.randint(0, 65535) + else: + self.id = id + self.flags = flags + self.max_size = max_size + self.origin = origin + self.compress = {} + self.section = QUESTION + self.counts = [0, 0, 0, 0] + self.output.write(b"\x00" * 12) + self.mac = "" + self.reserved = 0 + self.was_padded = False + + def _rollback(self, where): + """Truncate the output buffer at offset *where*, and remove any + compression table entries that pointed beyond the truncation + point. + """ + + self.output.seek(where) + self.output.truncate() + keys_to_delete = [] + for k, v in self.compress.items(): + if v >= where: + keys_to_delete.append(k) + for k in keys_to_delete: + del self.compress[k] + + def _set_section(self, section): + """Set the renderer's current section. + + Sections must be rendered order: QUESTION, ANSWER, AUTHORITY, + ADDITIONAL. Sections may be empty. + + Raises dns.exception.FormError if an attempt was made to set + a section value less than the current section. + """ + + if self.section != section: + if self.section > section: + raise dns.exception.FormError + self.section = section + + @contextlib.contextmanager + def _track_size(self): + start = self.output.tell() + yield start + if self.output.tell() > self.max_size: + self._rollback(start) + raise dns.exception.TooBig + + @contextlib.contextmanager + def _temporarily_seek_to(self, where): + current = self.output.tell() + try: + self.output.seek(where) + yield + finally: + self.output.seek(current) + + def add_question(self, qname, rdtype, rdclass=dns.rdataclass.IN): + """Add a question to the message.""" + + self._set_section(QUESTION) + with self._track_size(): + qname.to_wire(self.output, self.compress, self.origin) + self.output.write(struct.pack("!HH", rdtype, rdclass)) + self.counts[QUESTION] += 1 + + def add_rrset(self, section, rrset, **kw): + """Add the rrset to the specified section. + + Any keyword arguments are passed on to the rdataset's to_wire() + routine. + """ + + self._set_section(section) + with self._track_size(): + n = rrset.to_wire(self.output, self.compress, self.origin, **kw) + self.counts[section] += n + + def add_rdataset(self, section, name, rdataset, **kw): + """Add the rdataset to the specified section, using the specified + name as the owner name. + + Any keyword arguments are passed on to the rdataset's to_wire() + routine. + """ + + self._set_section(section) + with self._track_size(): + n = rdataset.to_wire(name, self.output, self.compress, self.origin, **kw) + self.counts[section] += n + + def add_opt(self, opt, pad=0, opt_size=0, tsig_size=0): + """Add *opt* to the additional section, applying padding if desired. The + padding will take the specified precomputed OPT size and TSIG size into + account. + + Note that we don't have reliable way of knowing how big a GSS-TSIG digest + might be, so we we might not get an even multiple of the pad in that case.""" + if pad: + ttl = opt.ttl + assert opt_size >= 11 + opt_rdata = opt[0] + size_without_padding = self.output.tell() + opt_size + tsig_size + remainder = size_without_padding % pad + if remainder: + pad = b"\x00" * (pad - remainder) + else: + pad = b"" + options = list(opt_rdata.options) + options.append(dns.edns.GenericOption(dns.edns.OptionType.PADDING, pad)) + opt = dns.message.Message._make_opt( # pyright: ignore + ttl, opt_rdata.rdclass, options + ) + self.was_padded = True + self.add_rrset(ADDITIONAL, opt) + + def add_edns(self, edns, ednsflags, payload, options=None): + """Add an EDNS OPT record to the message.""" + + # make sure the EDNS version in ednsflags agrees with edns + ednsflags &= 0xFF00FFFF + ednsflags |= edns << 16 + opt = dns.message.Message._make_opt( # pyright: ignore + ednsflags, payload, options + ) + self.add_opt(opt) + + def add_tsig( + self, + keyname, + secret, + fudge, + id, + tsig_error, + other_data, + request_mac, + algorithm=dns.tsig.default_algorithm, + ): + """Add a TSIG signature to the message.""" + + s = self.output.getvalue() + + if isinstance(secret, dns.tsig.Key): + key = secret + else: + key = dns.tsig.Key(keyname, secret, algorithm) + tsig = dns.message.Message._make_tsig( # pyright: ignore + keyname, algorithm, 0, fudge, b"", id, tsig_error, other_data + ) + (tsig, _) = dns.tsig.sign(s, key, tsig[0], int(time.time()), request_mac) + self._write_tsig(tsig, keyname) + + def add_multi_tsig( + self, + ctx, + keyname, + secret, + fudge, + id, + tsig_error, + other_data, + request_mac, + algorithm=dns.tsig.default_algorithm, + ): + """Add a TSIG signature to the message. Unlike add_tsig(), this can be + used for a series of consecutive DNS envelopes, e.g. for a zone + transfer over TCP [RFC2845, 4.4]. + + For the first message in the sequence, give ctx=None. For each + subsequent message, give the ctx that was returned from the + add_multi_tsig() call for the previous message.""" + + s = self.output.getvalue() + + if isinstance(secret, dns.tsig.Key): + key = secret + else: + key = dns.tsig.Key(keyname, secret, algorithm) + tsig = dns.message.Message._make_tsig( # pyright: ignore + keyname, algorithm, 0, fudge, b"", id, tsig_error, other_data + ) + (tsig, ctx) = dns.tsig.sign( + s, key, tsig[0], int(time.time()), request_mac, ctx, True + ) + self._write_tsig(tsig, keyname) + return ctx + + def _write_tsig(self, tsig, keyname): + if self.was_padded: + compress = None + else: + compress = self.compress + self._set_section(ADDITIONAL) + with self._track_size(): + keyname.to_wire(self.output, compress, self.origin) + self.output.write( + struct.pack("!HHI", dns.rdatatype.TSIG, dns.rdataclass.ANY, 0) + ) + with prefixed_length(self.output, 2): + tsig.to_wire(self.output) + + self.counts[ADDITIONAL] += 1 + with self._temporarily_seek_to(10): + self.output.write(struct.pack("!H", self.counts[ADDITIONAL])) + + def write_header(self): + """Write the DNS message header. + + Writing the DNS message header is done after all sections + have been rendered, but before the optional TSIG signature + is added. + """ + + with self._temporarily_seek_to(0): + self.output.write( + struct.pack( + "!HHHHHH", + self.id, + self.flags, + self.counts[0], + self.counts[1], + self.counts[2], + self.counts[3], + ) + ) + + def get_wire(self): + """Return the wire format message.""" + + return self.output.getvalue() + + def reserve(self, size: int) -> None: + """Reserve *size* bytes.""" + if size < 0: + raise ValueError("reserved amount must be non-negative") + if size > self.max_size: + raise ValueError("cannot reserve more than the maximum size") + self.reserved += size + self.max_size -= size + + def release_reserved(self) -> None: + """Release the reserved bytes.""" + self.max_size += self.reserved + self.reserved = 0 diff --git a/venv/lib/python3.12/site-packages/dns/resolver.py b/venv/lib/python3.12/site-packages/dns/resolver.py new file mode 100644 index 0000000..923bb4b --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/resolver.py @@ -0,0 +1,2068 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS stub resolver.""" + +import contextlib +import random +import socket +import sys +import threading +import time +import warnings +from typing import Any, Dict, Iterator, List, Sequence, Tuple, cast +from urllib.parse import urlparse + +import dns._ddr +import dns.edns +import dns.exception +import dns.flags +import dns.inet +import dns.ipv4 +import dns.ipv6 +import dns.message +import dns.name +import dns.nameserver +import dns.query +import dns.rcode +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.rdtypes.ANY.PTR +import dns.rdtypes.svcbbase +import dns.reversename +import dns.tsig + +if sys.platform == "win32": # pragma: no cover + import dns.win32util + + +class NXDOMAIN(dns.exception.DNSException): + """The DNS query name does not exist.""" + + supp_kwargs = {"qnames", "responses"} + fmt = None # we have our own __str__ implementation + + # pylint: disable=arguments-differ + + # We do this as otherwise mypy complains about unexpected keyword argument + # idna_exception + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def _check_kwargs(self, qnames, responses=None): # pyright: ignore + if not isinstance(qnames, list | tuple | set): + raise AttributeError("qnames must be a list, tuple or set") + if len(qnames) == 0: + raise AttributeError("qnames must contain at least one element") + if responses is None: + responses = {} + elif not isinstance(responses, dict): + raise AttributeError("responses must be a dict(qname=response)") + kwargs = dict(qnames=qnames, responses=responses) + return kwargs + + def __str__(self) -> str: + if "qnames" not in self.kwargs: + return super().__str__() + qnames = self.kwargs["qnames"] + if len(qnames) > 1: + msg = "None of DNS query names exist" + else: + msg = "The DNS query name does not exist" + qnames = ", ".join(map(str, qnames)) + return f"{msg}: {qnames}" + + @property + def canonical_name(self): + """Return the unresolved canonical name.""" + if "qnames" not in self.kwargs: + raise TypeError("parametrized exception required") + for qname in self.kwargs["qnames"]: + response = self.kwargs["responses"][qname] + try: + cname = response.canonical_name() + if cname != qname: + return cname + except Exception: # pragma: no cover + # We can just eat this exception as it means there was + # something wrong with the response. + pass + return self.kwargs["qnames"][0] + + def __add__(self, e_nx): + """Augment by results from another NXDOMAIN exception.""" + qnames0 = list(self.kwargs.get("qnames", [])) + responses0 = dict(self.kwargs.get("responses", {})) + responses1 = e_nx.kwargs.get("responses", {}) + for qname1 in e_nx.kwargs.get("qnames", []): + if qname1 not in qnames0: + qnames0.append(qname1) + if qname1 in responses1: + responses0[qname1] = responses1[qname1] + return NXDOMAIN(qnames=qnames0, responses=responses0) + + def qnames(self): + """All of the names that were tried. + + Returns a list of ``dns.name.Name``. + """ + return self.kwargs["qnames"] + + def responses(self): + """A map from queried names to their NXDOMAIN responses. + + Returns a dict mapping a ``dns.name.Name`` to a + ``dns.message.Message``. + """ + return self.kwargs["responses"] + + def response(self, qname): + """The response for query *qname*. + + Returns a ``dns.message.Message``. + """ + return self.kwargs["responses"][qname] + + +class YXDOMAIN(dns.exception.DNSException): + """The DNS query name is too long after DNAME substitution.""" + + +ErrorTuple = Tuple[ + str | None, + bool, + int, + Exception | str, + dns.message.Message | None, +] + + +def _errors_to_text(errors: List[ErrorTuple]) -> List[str]: + """Turn a resolution errors trace into a list of text.""" + texts = [] + for err in errors: + texts.append(f"Server {err[0]} answered {err[3]}") + return texts + + +class LifetimeTimeout(dns.exception.Timeout): + """The resolution lifetime expired.""" + + msg = "The resolution lifetime expired." + fmt = f"{msg[:-1]} after {{timeout:.3f}} seconds: {{errors}}" + supp_kwargs = {"timeout", "errors"} + + # We do this as otherwise mypy complains about unexpected keyword argument + # idna_exception + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def _fmt_kwargs(self, **kwargs): + srv_msgs = _errors_to_text(kwargs["errors"]) + return super()._fmt_kwargs( + timeout=kwargs["timeout"], errors="; ".join(srv_msgs) + ) + + +# We added more detail to resolution timeouts, but they are still +# subclasses of dns.exception.Timeout for backwards compatibility. We also +# keep dns.resolver.Timeout defined for backwards compatibility. +Timeout = LifetimeTimeout + + +class NoAnswer(dns.exception.DNSException): + """The DNS response does not contain an answer to the question.""" + + fmt = "The DNS response does not contain an answer to the question: {query}" + supp_kwargs = {"response"} + + # We do this as otherwise mypy complains about unexpected keyword argument + # idna_exception + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def _fmt_kwargs(self, **kwargs): + return super()._fmt_kwargs(query=kwargs["response"].question) + + def response(self): + return self.kwargs["response"] + + +class NoNameservers(dns.exception.DNSException): + """All nameservers failed to answer the query. + + errors: list of servers and respective errors + The type of errors is + [(server IP address, any object convertible to string)]. + Non-empty errors list will add explanatory message () + """ + + msg = "All nameservers failed to answer the query." + fmt = f"{msg[:-1]} {{query}}: {{errors}}" + supp_kwargs = {"request", "errors"} + + # We do this as otherwise mypy complains about unexpected keyword argument + # idna_exception + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def _fmt_kwargs(self, **kwargs): + srv_msgs = _errors_to_text(kwargs["errors"]) + return super()._fmt_kwargs( + query=kwargs["request"].question, errors="; ".join(srv_msgs) + ) + + +class NotAbsolute(dns.exception.DNSException): + """An absolute domain name is required but a relative name was provided.""" + + +class NoRootSOA(dns.exception.DNSException): + """There is no SOA RR at the DNS root name. This should never happen!""" + + +class NoMetaqueries(dns.exception.DNSException): + """DNS metaqueries are not allowed.""" + + +class NoResolverConfiguration(dns.exception.DNSException): + """Resolver configuration could not be read or specified no nameservers.""" + + +class Answer: + """DNS stub resolver answer. + + Instances of this class bundle up the result of a successful DNS + resolution. + + For convenience, the answer object implements much of the sequence + protocol, forwarding to its ``rrset`` attribute. E.g. + ``for a in answer`` is equivalent to ``for a in answer.rrset``. + ``answer[i]`` is equivalent to ``answer.rrset[i]``, and + ``answer[i:j]`` is equivalent to ``answer.rrset[i:j]``. + + Note that CNAMEs or DNAMEs in the response may mean that answer + RRset's name might not be the query name. + """ + + def __init__( + self, + qname: dns.name.Name, + rdtype: dns.rdatatype.RdataType, + rdclass: dns.rdataclass.RdataClass, + response: dns.message.QueryMessage, + nameserver: str | None = None, + port: int | None = None, + ) -> None: + self.qname = qname + self.rdtype = rdtype + self.rdclass = rdclass + self.response = response + self.nameserver = nameserver + self.port = port + self.chaining_result = response.resolve_chaining() + # Copy some attributes out of chaining_result for backwards + # compatibility and convenience. + self.canonical_name = self.chaining_result.canonical_name + self.rrset = self.chaining_result.answer + self.expiration = time.time() + self.chaining_result.minimum_ttl + + def __getattr__(self, attr): # pragma: no cover + if self.rrset is not None: + if attr == "name": + return self.rrset.name + elif attr == "ttl": + return self.rrset.ttl + elif attr == "covers": + return self.rrset.covers + elif attr == "rdclass": + return self.rrset.rdclass + elif attr == "rdtype": + return self.rrset.rdtype + else: + raise AttributeError(attr) + + def __len__(self) -> int: + return self.rrset is not None and len(self.rrset) or 0 + + def __iter__(self) -> Iterator[Any]: + return self.rrset is not None and iter(self.rrset) or iter(tuple()) + + def __getitem__(self, i): + if self.rrset is None: + raise IndexError + return self.rrset[i] + + def __delitem__(self, i): + if self.rrset is None: + raise IndexError + del self.rrset[i] + + +class Answers(dict): + """A dict of DNS stub resolver answers, indexed by type.""" + + +class EmptyHostAnswers(dns.exception.DNSException): + """The HostAnswers has no addresses""" + + +class HostAnswers(Answers): + """A dict of DNS stub resolver answers to a host name lookup, indexed by + type. + """ + + @classmethod + def make( + cls, + v6: Answer | None = None, + v4: Answer | None = None, + add_empty: bool = True, + ) -> "HostAnswers": + answers = HostAnswers() + if v6 is not None and (add_empty or v6.rrset): + answers[dns.rdatatype.AAAA] = v6 + if v4 is not None and (add_empty or v4.rrset): + answers[dns.rdatatype.A] = v4 + return answers + + # Returns pairs of (address, family) from this result, potentially + # filtering by address family. + def addresses_and_families( + self, family: int = socket.AF_UNSPEC + ) -> Iterator[Tuple[str, int]]: + if family == socket.AF_UNSPEC: + yield from self.addresses_and_families(socket.AF_INET6) + yield from self.addresses_and_families(socket.AF_INET) + return + elif family == socket.AF_INET6: + answer = self.get(dns.rdatatype.AAAA) + elif family == socket.AF_INET: + answer = self.get(dns.rdatatype.A) + else: # pragma: no cover + raise NotImplementedError(f"unknown address family {family}") + if answer: + for rdata in answer: + yield (rdata.address, family) + + # Returns addresses from this result, potentially filtering by + # address family. + def addresses(self, family: int = socket.AF_UNSPEC) -> Iterator[str]: + return (pair[0] for pair in self.addresses_and_families(family)) + + # Returns the canonical name from this result. + def canonical_name(self) -> dns.name.Name: + answer = self.get(dns.rdatatype.AAAA, self.get(dns.rdatatype.A)) + if answer is None: + raise EmptyHostAnswers + return answer.canonical_name + + +class CacheStatistics: + """Cache Statistics""" + + def __init__(self, hits: int = 0, misses: int = 0) -> None: + self.hits = hits + self.misses = misses + + def reset(self) -> None: + self.hits = 0 + self.misses = 0 + + def clone(self) -> "CacheStatistics": + return CacheStatistics(self.hits, self.misses) + + +class CacheBase: + def __init__(self) -> None: + self.lock = threading.Lock() + self.statistics = CacheStatistics() + + def reset_statistics(self) -> None: + """Reset all statistics to zero.""" + with self.lock: + self.statistics.reset() + + def hits(self) -> int: + """How many hits has the cache had?""" + with self.lock: + return self.statistics.hits + + def misses(self) -> int: + """How many misses has the cache had?""" + with self.lock: + return self.statistics.misses + + def get_statistics_snapshot(self) -> CacheStatistics: + """Return a consistent snapshot of all the statistics. + + If running with multiple threads, it's better to take a + snapshot than to call statistics methods such as hits() and + misses() individually. + """ + with self.lock: + return self.statistics.clone() + + +CacheKey = Tuple[dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass] + + +class Cache(CacheBase): + """Simple thread-safe DNS answer cache.""" + + def __init__(self, cleaning_interval: float = 300.0) -> None: + """*cleaning_interval*, a ``float`` is the number of seconds between + periodic cleanings. + """ + + super().__init__() + self.data: Dict[CacheKey, Answer] = {} + self.cleaning_interval = cleaning_interval + self.next_cleaning: float = time.time() + self.cleaning_interval + + def _maybe_clean(self) -> None: + """Clean the cache if it's time to do so.""" + + now = time.time() + if self.next_cleaning <= now: + keys_to_delete = [] + for k, v in self.data.items(): + if v.expiration <= now: + keys_to_delete.append(k) + for k in keys_to_delete: + del self.data[k] + now = time.time() + self.next_cleaning = now + self.cleaning_interval + + def get(self, key: CacheKey) -> Answer | None: + """Get the answer associated with *key*. + + Returns None if no answer is cached for the key. + + *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)`` + tuple whose values are the query name, rdtype, and rdclass respectively. + + Returns a ``dns.resolver.Answer`` or ``None``. + """ + + with self.lock: + self._maybe_clean() + v = self.data.get(key) + if v is None or v.expiration <= time.time(): + self.statistics.misses += 1 + return None + self.statistics.hits += 1 + return v + + def put(self, key: CacheKey, value: Answer) -> None: + """Associate key and value in the cache. + + *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)`` + tuple whose values are the query name, rdtype, and rdclass respectively. + + *value*, a ``dns.resolver.Answer``, the answer. + """ + + with self.lock: + self._maybe_clean() + self.data[key] = value + + def flush(self, key: CacheKey | None = None) -> None: + """Flush the cache. + + If *key* is not ``None``, only that item is flushed. Otherwise the entire cache + is flushed. + + *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)`` + tuple whose values are the query name, rdtype, and rdclass respectively. + """ + + with self.lock: + if key is not None: + if key in self.data: + del self.data[key] + else: + self.data = {} + self.next_cleaning = time.time() + self.cleaning_interval + + +class LRUCacheNode: + """LRUCache node.""" + + def __init__(self, key, value): + self.key = key + self.value = value + self.hits = 0 + self.prev = self + self.next = self + + def link_after(self, node: "LRUCacheNode") -> None: + self.prev = node + self.next = node.next + node.next.prev = self + node.next = self + + def unlink(self) -> None: + self.next.prev = self.prev + self.prev.next = self.next + + +class LRUCache(CacheBase): + """Thread-safe, bounded, least-recently-used DNS answer cache. + + This cache is better than the simple cache (above) if you're + running a web crawler or other process that does a lot of + resolutions. The LRUCache has a maximum number of nodes, and when + it is full, the least-recently used node is removed to make space + for a new one. + """ + + def __init__(self, max_size: int = 100000) -> None: + """*max_size*, an ``int``, is the maximum number of nodes to cache; + it must be greater than 0. + """ + + super().__init__() + self.data: Dict[CacheKey, LRUCacheNode] = {} + self.set_max_size(max_size) + self.sentinel: LRUCacheNode = LRUCacheNode(None, None) + self.sentinel.prev = self.sentinel + self.sentinel.next = self.sentinel + + def set_max_size(self, max_size: int) -> None: + if max_size < 1: + max_size = 1 + self.max_size = max_size + + def get(self, key: CacheKey) -> Answer | None: + """Get the answer associated with *key*. + + Returns None if no answer is cached for the key. + + *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)`` + tuple whose values are the query name, rdtype, and rdclass respectively. + + Returns a ``dns.resolver.Answer`` or ``None``. + """ + + with self.lock: + node = self.data.get(key) + if node is None: + self.statistics.misses += 1 + return None + # Unlink because we're either going to move the node to the front + # of the LRU list or we're going to free it. + node.unlink() + if node.value.expiration <= time.time(): + del self.data[node.key] + self.statistics.misses += 1 + return None + node.link_after(self.sentinel) + self.statistics.hits += 1 + node.hits += 1 + return node.value + + def get_hits_for_key(self, key: CacheKey) -> int: + """Return the number of cache hits associated with the specified key.""" + with self.lock: + node = self.data.get(key) + if node is None or node.value.expiration <= time.time(): + return 0 + else: + return node.hits + + def put(self, key: CacheKey, value: Answer) -> None: + """Associate key and value in the cache. + + *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)`` + tuple whose values are the query name, rdtype, and rdclass respectively. + + *value*, a ``dns.resolver.Answer``, the answer. + """ + + with self.lock: + node = self.data.get(key) + if node is not None: + node.unlink() + del self.data[node.key] + while len(self.data) >= self.max_size: + gnode = self.sentinel.prev + gnode.unlink() + del self.data[gnode.key] + node = LRUCacheNode(key, value) + node.link_after(self.sentinel) + self.data[key] = node + + def flush(self, key: CacheKey | None = None) -> None: + """Flush the cache. + + If *key* is not ``None``, only that item is flushed. Otherwise the entire cache + is flushed. + + *key*, a ``(dns.name.Name, dns.rdatatype.RdataType, dns.rdataclass.RdataClass)`` + tuple whose values are the query name, rdtype, and rdclass respectively. + """ + + with self.lock: + if key is not None: + node = self.data.get(key) + if node is not None: + node.unlink() + del self.data[node.key] + else: + gnode = self.sentinel.next + while gnode != self.sentinel: + next = gnode.next + gnode.unlink() + gnode = next + self.data = {} + + +class _Resolution: + """Helper class for dns.resolver.Resolver.resolve(). + + All of the "business logic" of resolution is encapsulated in this + class, allowing us to have multiple resolve() implementations + using different I/O schemes without copying all of the + complicated logic. + + This class is a "friend" to dns.resolver.Resolver and manipulates + resolver data structures directly. + """ + + def __init__( + self, + resolver: "BaseResolver", + qname: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str, + rdclass: dns.rdataclass.RdataClass | str, + tcp: bool, + raise_on_no_answer: bool, + search: bool | None, + ) -> None: + if isinstance(qname, str): + qname = dns.name.from_text(qname, None) + rdtype = dns.rdatatype.RdataType.make(rdtype) + if dns.rdatatype.is_metatype(rdtype): + raise NoMetaqueries + rdclass = dns.rdataclass.RdataClass.make(rdclass) + if dns.rdataclass.is_metaclass(rdclass): + raise NoMetaqueries + self.resolver = resolver + self.qnames_to_try = resolver._get_qnames_to_try(qname, search) + self.qnames = self.qnames_to_try[:] + self.rdtype = rdtype + self.rdclass = rdclass + self.tcp = tcp + self.raise_on_no_answer = raise_on_no_answer + self.nxdomain_responses: Dict[dns.name.Name, dns.message.QueryMessage] = {} + # Initialize other things to help analysis tools + self.qname = dns.name.empty + self.nameservers: List[dns.nameserver.Nameserver] = [] + self.current_nameservers: List[dns.nameserver.Nameserver] = [] + self.errors: List[ErrorTuple] = [] + self.nameserver: dns.nameserver.Nameserver | None = None + self.tcp_attempt = False + self.retry_with_tcp = False + self.request: dns.message.QueryMessage | None = None + self.backoff = 0.0 + + def next_request( + self, + ) -> Tuple[dns.message.QueryMessage | None, Answer | None]: + """Get the next request to send, and check the cache. + + Returns a (request, answer) tuple. At most one of request or + answer will not be None. + """ + + # We return a tuple instead of Union[Message,Answer] as it lets + # the caller avoid isinstance(). + + while len(self.qnames) > 0: + self.qname = self.qnames.pop(0) + + # Do we know the answer? + if self.resolver.cache: + answer = self.resolver.cache.get( + (self.qname, self.rdtype, self.rdclass) + ) + if answer is not None: + if answer.rrset is None and self.raise_on_no_answer: + raise NoAnswer(response=answer.response) + else: + return (None, answer) + answer = self.resolver.cache.get( + (self.qname, dns.rdatatype.ANY, self.rdclass) + ) + if answer is not None and answer.response.rcode() == dns.rcode.NXDOMAIN: + # cached NXDOMAIN; record it and continue to next + # name. + self.nxdomain_responses[self.qname] = answer.response + continue + + # Build the request + request = dns.message.make_query(self.qname, self.rdtype, self.rdclass) + if self.resolver.keyname is not None: + request.use_tsig( + self.resolver.keyring, + self.resolver.keyname, + algorithm=self.resolver.keyalgorithm, + ) + request.use_edns( + self.resolver.edns, + self.resolver.ednsflags, + self.resolver.payload, + options=self.resolver.ednsoptions, + ) + if self.resolver.flags is not None: + request.flags = self.resolver.flags + + self.nameservers = self.resolver._enrich_nameservers( + self.resolver._nameservers, + self.resolver.nameserver_ports, + self.resolver.port, + ) + if self.resolver.rotate: + random.shuffle(self.nameservers) + self.current_nameservers = self.nameservers[:] + self.errors = [] + self.nameserver = None + self.tcp_attempt = False + self.retry_with_tcp = False + self.request = request + self.backoff = 0.10 + + return (request, None) + + # + # We've tried everything and only gotten NXDOMAINs. (We know + # it's only NXDOMAINs as anything else would have returned + # before now.) + # + raise NXDOMAIN(qnames=self.qnames_to_try, responses=self.nxdomain_responses) + + def next_nameserver(self) -> Tuple[dns.nameserver.Nameserver, bool, float]: + if self.retry_with_tcp: + assert self.nameserver is not None + assert not self.nameserver.is_always_max_size() + self.tcp_attempt = True + self.retry_with_tcp = False + return (self.nameserver, True, 0) + + backoff = 0.0 + if not self.current_nameservers: + if len(self.nameservers) == 0: + # Out of things to try! + raise NoNameservers(request=self.request, errors=self.errors) + self.current_nameservers = self.nameservers[:] + backoff = self.backoff + self.backoff = min(self.backoff * 2, 2) + + self.nameserver = self.current_nameservers.pop(0) + self.tcp_attempt = self.tcp or self.nameserver.is_always_max_size() + return (self.nameserver, self.tcp_attempt, backoff) + + def query_result( + self, response: dns.message.Message | None, ex: Exception | None + ) -> Tuple[Answer | None, bool]: + # + # returns an (answer: Answer, end_loop: bool) tuple. + # + assert self.nameserver is not None + if ex: + # Exception during I/O or from_wire() + assert response is None + self.errors.append( + ( + str(self.nameserver), + self.tcp_attempt, + self.nameserver.answer_port(), + ex, + response, + ) + ) + if ( + isinstance(ex, dns.exception.FormError) + or isinstance(ex, EOFError) + or isinstance(ex, OSError) + or isinstance(ex, NotImplementedError) + ): + # This nameserver is no good, take it out of the mix. + self.nameservers.remove(self.nameserver) + elif isinstance(ex, dns.message.Truncated): + if self.tcp_attempt: + # Truncation with TCP is no good! + self.nameservers.remove(self.nameserver) + else: + self.retry_with_tcp = True + return (None, False) + # We got an answer! + assert response is not None + assert isinstance(response, dns.message.QueryMessage) + rcode = response.rcode() + if rcode == dns.rcode.NOERROR: + try: + answer = Answer( + self.qname, + self.rdtype, + self.rdclass, + response, + self.nameserver.answer_nameserver(), + self.nameserver.answer_port(), + ) + except Exception as e: + self.errors.append( + ( + str(self.nameserver), + self.tcp_attempt, + self.nameserver.answer_port(), + e, + response, + ) + ) + # The nameserver is no good, take it out of the mix. + self.nameservers.remove(self.nameserver) + return (None, False) + if self.resolver.cache: + self.resolver.cache.put((self.qname, self.rdtype, self.rdclass), answer) + if answer.rrset is None and self.raise_on_no_answer: + raise NoAnswer(response=answer.response) + return (answer, True) + elif rcode == dns.rcode.NXDOMAIN: + # Further validate the response by making an Answer, even + # if we aren't going to cache it. + try: + answer = Answer( + self.qname, dns.rdatatype.ANY, dns.rdataclass.IN, response + ) + except Exception as e: + self.errors.append( + ( + str(self.nameserver), + self.tcp_attempt, + self.nameserver.answer_port(), + e, + response, + ) + ) + # The nameserver is no good, take it out of the mix. + self.nameservers.remove(self.nameserver) + return (None, False) + self.nxdomain_responses[self.qname] = response + if self.resolver.cache: + self.resolver.cache.put( + (self.qname, dns.rdatatype.ANY, self.rdclass), answer + ) + # Make next_nameserver() return None, so caller breaks its + # inner loop and calls next_request(). + return (None, True) + elif rcode == dns.rcode.YXDOMAIN: + yex = YXDOMAIN() + self.errors.append( + ( + str(self.nameserver), + self.tcp_attempt, + self.nameserver.answer_port(), + yex, + response, + ) + ) + raise yex + else: + # + # We got a response, but we're not happy with the + # rcode in it. + # + if rcode != dns.rcode.SERVFAIL or not self.resolver.retry_servfail: + self.nameservers.remove(self.nameserver) + self.errors.append( + ( + str(self.nameserver), + self.tcp_attempt, + self.nameserver.answer_port(), + dns.rcode.to_text(rcode), + response, + ) + ) + return (None, False) + + +class BaseResolver: + """DNS stub resolver.""" + + # We initialize in reset() + # + # pylint: disable=attribute-defined-outside-init + + domain: dns.name.Name + nameserver_ports: Dict[str, int] + port: int + search: List[dns.name.Name] + use_search_by_default: bool + timeout: float + lifetime: float + keyring: Any | None + keyname: dns.name.Name | str | None + keyalgorithm: dns.name.Name | str + edns: int + ednsflags: int + ednsoptions: List[dns.edns.Option] | None + payload: int + cache: Any + flags: int | None + retry_servfail: bool + rotate: bool + ndots: int | None + _nameservers: Sequence[str | dns.nameserver.Nameserver] + + def __init__( + self, filename: str = "/etc/resolv.conf", configure: bool = True + ) -> None: + """*filename*, a ``str`` or file object, specifying a file + in standard /etc/resolv.conf format. This parameter is meaningful + only when *configure* is true and the platform is POSIX. + + *configure*, a ``bool``. If True (the default), the resolver + instance is configured in the normal fashion for the operating + system the resolver is running on. (I.e. by reading a + /etc/resolv.conf file on POSIX systems and from the registry + on Windows systems.) + """ + + self.reset() + if configure: + if sys.platform == "win32": # pragma: no cover + self.read_registry() + elif filename: + self.read_resolv_conf(filename) + + def reset(self) -> None: + """Reset all resolver configuration to the defaults.""" + + self.domain = dns.name.Name(dns.name.from_text(socket.gethostname())[1:]) + if len(self.domain) == 0: # pragma: no cover + self.domain = dns.name.root + self._nameservers = [] + self.nameserver_ports = {} + self.port = 53 + self.search = [] + self.use_search_by_default = False + self.timeout = 2.0 + self.lifetime = 5.0 + self.keyring = None + self.keyname = None + self.keyalgorithm = dns.tsig.default_algorithm + self.edns = -1 + self.ednsflags = 0 + self.ednsoptions = None + self.payload = 0 + self.cache = None + self.flags = None + self.retry_servfail = False + self.rotate = False + self.ndots = None + + def read_resolv_conf(self, f: Any) -> None: + """Process *f* as a file in the /etc/resolv.conf format. If f is + a ``str``, it is used as the name of the file to open; otherwise it + is treated as the file itself. + + Interprets the following items: + + - nameserver - name server IP address + + - domain - local domain name + + - search - search list for host-name lookup + + - options - supported options are rotate, timeout, edns0, and ndots + + """ + + nameservers = [] + if isinstance(f, str): + try: + cm: contextlib.AbstractContextManager = open(f, encoding="utf-8") + except OSError: + # /etc/resolv.conf doesn't exist, can't be read, etc. + raise NoResolverConfiguration(f"cannot open {f}") + else: + cm = contextlib.nullcontext(f) + with cm as f: + for l in f: + if len(l) == 0 or l[0] == "#" or l[0] == ";": + continue + tokens = l.split() + + # Any line containing less than 2 tokens is malformed + if len(tokens) < 2: + continue + + if tokens[0] == "nameserver": + nameservers.append(tokens[1]) + elif tokens[0] == "domain": + self.domain = dns.name.from_text(tokens[1]) + # domain and search are exclusive + self.search = [] + elif tokens[0] == "search": + # the last search wins + self.search = [] + for suffix in tokens[1:]: + self.search.append(dns.name.from_text(suffix)) + # We don't set domain as it is not used if + # len(self.search) > 0 + elif tokens[0] == "options": + for opt in tokens[1:]: + if opt == "rotate": + self.rotate = True + elif opt == "edns0": + self.use_edns() + elif "timeout" in opt: + try: + self.timeout = int(opt.split(":")[1]) + except (ValueError, IndexError): + pass + elif "ndots" in opt: + try: + self.ndots = int(opt.split(":")[1]) + except (ValueError, IndexError): + pass + if len(nameservers) == 0: + raise NoResolverConfiguration("no nameservers") + # Assigning directly instead of appending means we invoke the + # setter logic, with additonal checking and enrichment. + self.nameservers = nameservers + + def read_registry(self) -> None: # pragma: no cover + """Extract resolver configuration from the Windows registry.""" + try: + info = dns.win32util.get_dns_info() # type: ignore + if info.domain is not None: + self.domain = info.domain + self.nameservers = info.nameservers + self.search = info.search + except AttributeError: + raise NotImplementedError + + def _compute_timeout( + self, + start: float, + lifetime: float | None = None, + errors: List[ErrorTuple] | None = None, + ) -> float: + lifetime = self.lifetime if lifetime is None else lifetime + now = time.time() + duration = now - start + if errors is None: + errors = [] + if duration < 0: + if duration < -1: + # Time going backwards is bad. Just give up. + raise LifetimeTimeout(timeout=duration, errors=errors) + else: + # Time went backwards, but only a little. This can + # happen, e.g. under vmware with older linux kernels. + # Pretend it didn't happen. + duration = 0 + if duration >= lifetime: + raise LifetimeTimeout(timeout=duration, errors=errors) + return min(lifetime - duration, self.timeout) + + def _get_qnames_to_try( + self, qname: dns.name.Name, search: bool | None + ) -> List[dns.name.Name]: + # This is a separate method so we can unit test the search + # rules without requiring the Internet. + if search is None: + search = self.use_search_by_default + qnames_to_try = [] + if qname.is_absolute(): + qnames_to_try.append(qname) + else: + abs_qname = qname.concatenate(dns.name.root) + if search: + if len(self.search) > 0: + # There is a search list, so use it exclusively + search_list = self.search[:] + elif self.domain != dns.name.root and self.domain is not None: + # We have some notion of a domain that isn't the root, so + # use it as the search list. + search_list = [self.domain] + else: + search_list = [] + # Figure out the effective ndots (default is 1) + if self.ndots is None: + ndots = 1 + else: + ndots = self.ndots + for suffix in search_list: + qnames_to_try.append(qname + suffix) + if len(qname) > ndots: + # The name has at least ndots dots, so we should try an + # absolute query first. + qnames_to_try.insert(0, abs_qname) + else: + # The name has less than ndots dots, so we should search + # first, then try the absolute name. + qnames_to_try.append(abs_qname) + else: + qnames_to_try.append(abs_qname) + return qnames_to_try + + def use_tsig( + self, + keyring: Any, + keyname: dns.name.Name | str | None = None, + algorithm: dns.name.Name | str = dns.tsig.default_algorithm, + ) -> None: + """Add a TSIG signature to each query. + + The parameters are passed to ``dns.message.Message.use_tsig()``; + see its documentation for details. + """ + + self.keyring = keyring + self.keyname = keyname + self.keyalgorithm = algorithm + + def use_edns( + self, + edns: int | bool | None = 0, + ednsflags: int = 0, + payload: int = dns.message.DEFAULT_EDNS_PAYLOAD, + options: List[dns.edns.Option] | None = None, + ) -> None: + """Configure EDNS behavior. + + *edns*, an ``int``, is the EDNS level to use. Specifying + ``None``, ``False``, or ``-1`` means "do not use EDNS", and in this case + the other parameters are ignored. Specifying ``True`` is + equivalent to specifying 0, i.e. "use EDNS0". + + *ednsflags*, an ``int``, the EDNS flag values. + + *payload*, an ``int``, is the EDNS sender's payload field, which is the + maximum size of UDP datagram the sender can handle. I.e. how big + a response to this message can be. + + *options*, a list of ``dns.edns.Option`` objects or ``None``, the EDNS + options. + """ + + if edns is None or edns is False: + edns = -1 + elif edns is True: + edns = 0 + self.edns = edns + self.ednsflags = ednsflags + self.payload = payload + self.ednsoptions = options + + def set_flags(self, flags: int) -> None: + """Overrides the default flags with your own. + + *flags*, an ``int``, the message flags to use. + """ + + self.flags = flags + + @classmethod + def _enrich_nameservers( + cls, + nameservers: Sequence[str | dns.nameserver.Nameserver], + nameserver_ports: Dict[str, int], + default_port: int, + ) -> List[dns.nameserver.Nameserver]: + enriched_nameservers = [] + if isinstance(nameservers, list | tuple): + for nameserver in nameservers: + enriched_nameserver: dns.nameserver.Nameserver + if isinstance(nameserver, dns.nameserver.Nameserver): + enriched_nameserver = nameserver + elif dns.inet.is_address(nameserver): + port = nameserver_ports.get(nameserver, default_port) + enriched_nameserver = dns.nameserver.Do53Nameserver( + nameserver, port + ) + else: + try: + if urlparse(nameserver).scheme != "https": + raise NotImplementedError + except Exception: + raise ValueError( + f"nameserver {nameserver} is not a " + "dns.nameserver.Nameserver instance or text form, " + "IP address, nor a valid https URL" + ) + enriched_nameserver = dns.nameserver.DoHNameserver(nameserver) + enriched_nameservers.append(enriched_nameserver) + else: + raise ValueError( + f"nameservers must be a list or tuple (not a {type(nameservers)})" + ) + return enriched_nameservers + + @property + def nameservers( + self, + ) -> Sequence[str | dns.nameserver.Nameserver]: + return self._nameservers + + @nameservers.setter + def nameservers( + self, nameservers: Sequence[str | dns.nameserver.Nameserver] + ) -> None: + """ + *nameservers*, a ``list`` or ``tuple`` of nameservers, where a nameserver is either + a string interpretable as a nameserver, or a ``dns.nameserver.Nameserver`` + instance. + + Raises ``ValueError`` if *nameservers* is not a list of nameservers. + """ + # We just call _enrich_nameservers() for checking + self._enrich_nameservers(nameservers, self.nameserver_ports, self.port) + self._nameservers = nameservers + + +class Resolver(BaseResolver): + """DNS stub resolver.""" + + def resolve( + self, + qname: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str = dns.rdatatype.A, + rdclass: dns.rdataclass.RdataClass | str = dns.rdataclass.IN, + tcp: bool = False, + source: str | None = None, + raise_on_no_answer: bool = True, + source_port: int = 0, + lifetime: float | None = None, + search: bool | None = None, + ) -> Answer: # pylint: disable=arguments-differ + """Query nameservers to find the answer to the question. + + The *qname*, *rdtype*, and *rdclass* parameters may be objects + of the appropriate type, or strings that can be converted into objects + of the appropriate type. + + *qname*, a ``dns.name.Name`` or ``str``, the query name. + + *rdtype*, an ``int`` or ``str``, the query type. + + *rdclass*, an ``int`` or ``str``, the query class. + + *tcp*, a ``bool``. If ``True``, use TCP to make the query. + + *source*, a ``str`` or ``None``. If not ``None``, bind to this IP + address when making queries. + + *raise_on_no_answer*, a ``bool``. If ``True``, raise + ``dns.resolver.NoAnswer`` if there's no answer to the question. + + *source_port*, an ``int``, the port from which to send the message. + + *lifetime*, a ``float``, how many seconds a query should run + before timing out. + + *search*, a ``bool`` or ``None``, determines whether the + search list configured in the system's resolver configuration + are used for relative names, and whether the resolver's domain + may be added to relative names. The default is ``None``, + which causes the value of the resolver's + ``use_search_by_default`` attribute to be used. + + Raises ``dns.resolver.LifetimeTimeout`` if no answers could be found + in the specified lifetime. + + Raises ``dns.resolver.NXDOMAIN`` if the query name does not exist. + + Raises ``dns.resolver.YXDOMAIN`` if the query name is too long after + DNAME substitution. + + Raises ``dns.resolver.NoAnswer`` if *raise_on_no_answer* is + ``True`` and the query name exists but has no RRset of the + desired type and class. + + Raises ``dns.resolver.NoNameservers`` if no non-broken + nameservers are available to answer the question. + + Returns a ``dns.resolver.Answer`` instance. + + """ + + resolution = _Resolution( + self, qname, rdtype, rdclass, tcp, raise_on_no_answer, search + ) + start = time.time() + while True: + (request, answer) = resolution.next_request() + # Note we need to say "if answer is not None" and not just + # "if answer" because answer implements __len__, and python + # will call that. We want to return if we have an answer + # object, including in cases where its length is 0. + if answer is not None: + # cache hit! + return answer + assert request is not None # needed for type checking + done = False + while not done: + (nameserver, tcp, backoff) = resolution.next_nameserver() + if backoff: + time.sleep(backoff) + timeout = self._compute_timeout(start, lifetime, resolution.errors) + try: + response = nameserver.query( + request, + timeout=timeout, + source=source, + source_port=source_port, + max_size=tcp, + ) + except Exception as ex: + (_, done) = resolution.query_result(None, ex) + continue + (answer, done) = resolution.query_result(response, None) + # Note we need to say "if answer is not None" and not just + # "if answer" because answer implements __len__, and python + # will call that. We want to return if we have an answer + # object, including in cases where its length is 0. + if answer is not None: + return answer + + def query( + self, + qname: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str = dns.rdatatype.A, + rdclass: dns.rdataclass.RdataClass | str = dns.rdataclass.IN, + tcp: bool = False, + source: str | None = None, + raise_on_no_answer: bool = True, + source_port: int = 0, + lifetime: float | None = None, + ) -> Answer: # pragma: no cover + """Query nameservers to find the answer to the question. + + This method calls resolve() with ``search=True``, and is + provided for backwards compatibility with prior versions of + dnspython. See the documentation for the resolve() method for + further details. + """ + warnings.warn( + "please use dns.resolver.Resolver.resolve() instead", + DeprecationWarning, + stacklevel=2, + ) + return self.resolve( + qname, + rdtype, + rdclass, + tcp, + source, + raise_on_no_answer, + source_port, + lifetime, + True, + ) + + def resolve_address(self, ipaddr: str, *args: Any, **kwargs: Any) -> Answer: + """Use a resolver to run a reverse query for PTR records. + + This utilizes the resolve() method to perform a PTR lookup on the + specified IP address. + + *ipaddr*, a ``str``, the IPv4 or IPv6 address you want to get + the PTR record for. + + All other arguments that can be passed to the resolve() function + except for rdtype and rdclass are also supported by this + function. + """ + # We make a modified kwargs for type checking happiness, as otherwise + # we get a legit warning about possibly having rdtype and rdclass + # in the kwargs more than once. + modified_kwargs: Dict[str, Any] = {} + modified_kwargs.update(kwargs) + modified_kwargs["rdtype"] = dns.rdatatype.PTR + modified_kwargs["rdclass"] = dns.rdataclass.IN + return self.resolve( + dns.reversename.from_address(ipaddr), *args, **modified_kwargs + ) + + def resolve_name( + self, + name: dns.name.Name | str, + family: int = socket.AF_UNSPEC, + **kwargs: Any, + ) -> HostAnswers: + """Use a resolver to query for address records. + + This utilizes the resolve() method to perform A and/or AAAA lookups on + the specified name. + + *qname*, a ``dns.name.Name`` or ``str``, the name to resolve. + + *family*, an ``int``, the address family. If socket.AF_UNSPEC + (the default), both A and AAAA records will be retrieved. + + All other arguments that can be passed to the resolve() function + except for rdtype and rdclass are also supported by this + function. + """ + # We make a modified kwargs for type checking happiness, as otherwise + # we get a legit warning about possibly having rdtype and rdclass + # in the kwargs more than once. + modified_kwargs: Dict[str, Any] = {} + modified_kwargs.update(kwargs) + modified_kwargs.pop("rdtype", None) + modified_kwargs["rdclass"] = dns.rdataclass.IN + + if family == socket.AF_INET: + v4 = self.resolve(name, dns.rdatatype.A, **modified_kwargs) + return HostAnswers.make(v4=v4) + elif family == socket.AF_INET6: + v6 = self.resolve(name, dns.rdatatype.AAAA, **modified_kwargs) + return HostAnswers.make(v6=v6) + elif family != socket.AF_UNSPEC: # pragma: no cover + raise NotImplementedError(f"unknown address family {family}") + + raise_on_no_answer = modified_kwargs.pop("raise_on_no_answer", True) + lifetime = modified_kwargs.pop("lifetime", None) + start = time.time() + v6 = self.resolve( + name, + dns.rdatatype.AAAA, + raise_on_no_answer=False, + lifetime=self._compute_timeout(start, lifetime), + **modified_kwargs, + ) + # Note that setting name ensures we query the same name + # for A as we did for AAAA. (This is just in case search lists + # are active by default in the resolver configuration and + # we might be talking to a server that says NXDOMAIN when it + # wants to say NOERROR no data. + name = v6.qname + v4 = self.resolve( + name, + dns.rdatatype.A, + raise_on_no_answer=False, + lifetime=self._compute_timeout(start, lifetime), + **modified_kwargs, + ) + answers = HostAnswers.make(v6=v6, v4=v4, add_empty=not raise_on_no_answer) + if not answers: + raise NoAnswer(response=v6.response) + return answers + + # pylint: disable=redefined-outer-name + + def canonical_name(self, name: dns.name.Name | str) -> dns.name.Name: + """Determine the canonical name of *name*. + + The canonical name is the name the resolver uses for queries + after all CNAME and DNAME renamings have been applied. + + *name*, a ``dns.name.Name`` or ``str``, the query name. + + This method can raise any exception that ``resolve()`` can + raise, other than ``dns.resolver.NoAnswer`` and + ``dns.resolver.NXDOMAIN``. + + Returns a ``dns.name.Name``. + """ + try: + answer = self.resolve(name, raise_on_no_answer=False) + canonical_name = answer.canonical_name + except NXDOMAIN as e: + canonical_name = e.canonical_name + return canonical_name + + # pylint: enable=redefined-outer-name + + def try_ddr(self, lifetime: float = 5.0) -> None: + """Try to update the resolver's nameservers using Discovery of Designated + Resolvers (DDR). If successful, the resolver will subsequently use + DNS-over-HTTPS or DNS-over-TLS for future queries. + + *lifetime*, a float, is the maximum time to spend attempting DDR. The default + is 5 seconds. + + If the SVCB query is successful and results in a non-empty list of nameservers, + then the resolver's nameservers are set to the returned servers in priority + order. + + The current implementation does not use any address hints from the SVCB record, + nor does it resolve addresses for the SCVB target name, rather it assumes that + the bootstrap nameserver will always be one of the addresses and uses it. + A future revision to the code may offer fuller support. The code verifies that + the bootstrap nameserver is in the Subject Alternative Name field of the + TLS certficate. + """ + try: + expiration = time.time() + lifetime + answer = self.resolve( + dns._ddr._local_resolver_name, "SVCB", lifetime=lifetime + ) + timeout = dns.query._remaining(expiration) + nameservers = dns._ddr._get_nameservers_sync(answer, timeout) + if len(nameservers) > 0: + self.nameservers = nameservers + except Exception: # pragma: no cover + pass + + +#: The default resolver. +default_resolver: Resolver | None = None + + +def get_default_resolver() -> Resolver: + """Get the default resolver, initializing it if necessary.""" + if default_resolver is None: + reset_default_resolver() + assert default_resolver is not None + return default_resolver + + +def reset_default_resolver() -> None: + """Re-initialize default resolver. + + Note that the resolver configuration (i.e. /etc/resolv.conf on UNIX + systems) will be re-read immediately. + """ + + global default_resolver + default_resolver = Resolver() + + +def resolve( + qname: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str = dns.rdatatype.A, + rdclass: dns.rdataclass.RdataClass | str = dns.rdataclass.IN, + tcp: bool = False, + source: str | None = None, + raise_on_no_answer: bool = True, + source_port: int = 0, + lifetime: float | None = None, + search: bool | None = None, +) -> Answer: # pragma: no cover + """Query nameservers to find the answer to the question. + + This is a convenience function that uses the default resolver + object to make the query. + + See ``dns.resolver.Resolver.resolve`` for more information on the + parameters. + """ + + return get_default_resolver().resolve( + qname, + rdtype, + rdclass, + tcp, + source, + raise_on_no_answer, + source_port, + lifetime, + search, + ) + + +def query( + qname: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str = dns.rdatatype.A, + rdclass: dns.rdataclass.RdataClass | str = dns.rdataclass.IN, + tcp: bool = False, + source: str | None = None, + raise_on_no_answer: bool = True, + source_port: int = 0, + lifetime: float | None = None, +) -> Answer: # pragma: no cover + """Query nameservers to find the answer to the question. + + This method calls resolve() with ``search=True``, and is + provided for backwards compatibility with prior versions of + dnspython. See the documentation for the resolve() method for + further details. + """ + warnings.warn( + "please use dns.resolver.resolve() instead", DeprecationWarning, stacklevel=2 + ) + return resolve( + qname, + rdtype, + rdclass, + tcp, + source, + raise_on_no_answer, + source_port, + lifetime, + True, + ) + + +def resolve_address(ipaddr: str, *args: Any, **kwargs: Any) -> Answer: + """Use a resolver to run a reverse query for PTR records. + + See ``dns.resolver.Resolver.resolve_address`` for more information on the + parameters. + """ + + return get_default_resolver().resolve_address(ipaddr, *args, **kwargs) + + +def resolve_name( + name: dns.name.Name | str, family: int = socket.AF_UNSPEC, **kwargs: Any +) -> HostAnswers: + """Use a resolver to query for address records. + + See ``dns.resolver.Resolver.resolve_name`` for more information on the + parameters. + """ + + return get_default_resolver().resolve_name(name, family, **kwargs) + + +def canonical_name(name: dns.name.Name | str) -> dns.name.Name: + """Determine the canonical name of *name*. + + See ``dns.resolver.Resolver.canonical_name`` for more information on the + parameters and possible exceptions. + """ + + return get_default_resolver().canonical_name(name) + + +def try_ddr(lifetime: float = 5.0) -> None: # pragma: no cover + """Try to update the default resolver's nameservers using Discovery of Designated + Resolvers (DDR). If successful, the resolver will subsequently use + DNS-over-HTTPS or DNS-over-TLS for future queries. + + See :py:func:`dns.resolver.Resolver.try_ddr` for more information. + """ + return get_default_resolver().try_ddr(lifetime) + + +def zone_for_name( + name: dns.name.Name | str, + rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, + tcp: bool = False, + resolver: Resolver | None = None, + lifetime: float | None = None, +) -> dns.name.Name: # pyright: ignore[reportReturnType] + """Find the name of the zone which contains the specified name. + + *name*, an absolute ``dns.name.Name`` or ``str``, the query name. + + *rdclass*, an ``int``, the query class. + + *tcp*, a ``bool``. If ``True``, use TCP to make the query. + + *resolver*, a ``dns.resolver.Resolver`` or ``None``, the resolver to use. + If ``None``, the default, then the default resolver is used. + + *lifetime*, a ``float``, the total time to allow for the queries needed + to determine the zone. If ``None``, the default, then only the individual + query limits of the resolver apply. + + Raises ``dns.resolver.NoRootSOA`` if there is no SOA RR at the DNS + root. (This is only likely to happen if you're using non-default + root servers in your network and they are misconfigured.) + + Raises ``dns.resolver.LifetimeTimeout`` if the answer could not be + found in the allotted lifetime. + + Returns a ``dns.name.Name``. + """ + + if isinstance(name, str): + name = dns.name.from_text(name, dns.name.root) + if resolver is None: + resolver = get_default_resolver() + if not name.is_absolute(): + raise NotAbsolute(name) + start = time.time() + expiration: float | None + if lifetime is not None: + expiration = start + lifetime + else: + expiration = None + while 1: + try: + rlifetime: float | None + if expiration is not None: + rlifetime = expiration - time.time() + if rlifetime <= 0: + rlifetime = 0 + else: + rlifetime = None + answer = resolver.resolve( + name, dns.rdatatype.SOA, rdclass, tcp, lifetime=rlifetime + ) + assert answer.rrset is not None + if answer.rrset.name == name: + return name + # otherwise we were CNAMEd or DNAMEd and need to look higher + except (NXDOMAIN, NoAnswer) as e: + if isinstance(e, NXDOMAIN): + response = e.responses().get(name) + else: + response = e.response() # pylint: disable=no-value-for-parameter + if response: + for rrs in response.authority: + if rrs.rdtype == dns.rdatatype.SOA and rrs.rdclass == rdclass: + (nr, _, _) = rrs.name.fullcompare(name) + if nr == dns.name.NAMERELN_SUPERDOMAIN: + # We're doing a proper superdomain check as + # if the name were equal we ought to have gotten + # it in the answer section! We are ignoring the + # possibility that the authority is insane and + # is including multiple SOA RRs for different + # authorities. + return rrs.name + # we couldn't extract anything useful from the response (e.g. it's + # a type 3 NXDOMAIN) + try: + name = name.parent() + except dns.name.NoParent: + raise NoRootSOA + + +def make_resolver_at( + where: dns.name.Name | str, + port: int = 53, + family: int = socket.AF_UNSPEC, + resolver: Resolver | None = None, +) -> Resolver: + """Make a stub resolver using the specified destination as the full resolver. + + *where*, a ``dns.name.Name`` or ``str`` the domain name or IP address of the + full resolver. + + *port*, an ``int``, the port to use. If not specified, the default is 53. + + *family*, an ``int``, the address family to use. This parameter is used if + *where* is not an address. The default is ``socket.AF_UNSPEC`` in which case + the first address returned by ``resolve_name()`` will be used, otherwise the + first address of the specified family will be used. + + *resolver*, a ``dns.resolver.Resolver`` or ``None``, the resolver to use for + resolution of hostnames. If not specified, the default resolver will be used. + + Returns a ``dns.resolver.Resolver`` or raises an exception. + """ + if resolver is None: + resolver = get_default_resolver() + nameservers: List[str | dns.nameserver.Nameserver] = [] + if isinstance(where, str) and dns.inet.is_address(where): + nameservers.append(dns.nameserver.Do53Nameserver(where, port)) + else: + for address in resolver.resolve_name(where, family).addresses(): + nameservers.append(dns.nameserver.Do53Nameserver(address, port)) + res = Resolver(configure=False) + res.nameservers = nameservers + return res + + +def resolve_at( + where: dns.name.Name | str, + qname: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str = dns.rdatatype.A, + rdclass: dns.rdataclass.RdataClass | str = dns.rdataclass.IN, + tcp: bool = False, + source: str | None = None, + raise_on_no_answer: bool = True, + source_port: int = 0, + lifetime: float | None = None, + search: bool | None = None, + port: int = 53, + family: int = socket.AF_UNSPEC, + resolver: Resolver | None = None, +) -> Answer: + """Query nameservers to find the answer to the question. + + This is a convenience function that calls ``dns.resolver.make_resolver_at()`` to + make a resolver, and then uses it to resolve the query. + + See ``dns.resolver.Resolver.resolve`` for more information on the resolution + parameters, and ``dns.resolver.make_resolver_at`` for information about the resolver + parameters *where*, *port*, *family*, and *resolver*. + + If making more than one query, it is more efficient to call + ``dns.resolver.make_resolver_at()`` and then use that resolver for the queries + instead of calling ``resolve_at()`` multiple times. + """ + return make_resolver_at(where, port, family, resolver).resolve( + qname, + rdtype, + rdclass, + tcp, + source, + raise_on_no_answer, + source_port, + lifetime, + search, + ) + + +# +# Support for overriding the system resolver for all python code in the +# running process. +# + +_protocols_for_socktype: Dict[Any, List[Any]] = { + socket.SOCK_DGRAM: [socket.SOL_UDP], + socket.SOCK_STREAM: [socket.SOL_TCP], +} + +_resolver: Resolver | None = None +_original_getaddrinfo = socket.getaddrinfo +_original_getnameinfo = socket.getnameinfo +_original_getfqdn = socket.getfqdn +_original_gethostbyname = socket.gethostbyname +_original_gethostbyname_ex = socket.gethostbyname_ex +_original_gethostbyaddr = socket.gethostbyaddr + + +def _getaddrinfo( + host=None, service=None, family=socket.AF_UNSPEC, socktype=0, proto=0, flags=0 +): + if flags & socket.AI_NUMERICHOST != 0: + # Short circuit directly into the system's getaddrinfo(). We're + # not adding any value in this case, and this avoids infinite loops + # because dns.query.* needs to call getaddrinfo() for IPv6 scoping + # reasons. We will also do this short circuit below if we + # discover that the host is an address literal. + return _original_getaddrinfo(host, service, family, socktype, proto, flags) + if flags & (socket.AI_ADDRCONFIG | socket.AI_V4MAPPED) != 0: + # Not implemented. We raise a gaierror as opposed to a + # NotImplementedError as it helps callers handle errors more + # appropriately. [Issue #316] + # + # We raise EAI_FAIL as opposed to EAI_SYSTEM because there is + # no EAI_SYSTEM on Windows [Issue #416]. We didn't go for + # EAI_BADFLAGS as the flags aren't bad, we just don't + # implement them. + raise socket.gaierror( + socket.EAI_FAIL, "Non-recoverable failure in name resolution" + ) + if host is None and service is None: + raise socket.gaierror(socket.EAI_NONAME, "Name or service not known") + addrs = [] + canonical_name = None # pylint: disable=redefined-outer-name + # Is host None or an address literal? If so, use the system's + # getaddrinfo(). + if host is None: + return _original_getaddrinfo(host, service, family, socktype, proto, flags) + try: + # We don't care about the result of af_for_address(), we're just + # calling it so it raises an exception if host is not an IPv4 or + # IPv6 address. + dns.inet.af_for_address(host) + return _original_getaddrinfo(host, service, family, socktype, proto, flags) + except Exception: + pass + # Something needs resolution! + try: + assert _resolver is not None + answers = _resolver.resolve_name(host, family) + addrs = answers.addresses_and_families() + canonical_name = answers.canonical_name().to_text(True) + except NXDOMAIN: + raise socket.gaierror(socket.EAI_NONAME, "Name or service not known") + except Exception: + # We raise EAI_AGAIN here as the failure may be temporary + # (e.g. a timeout) and EAI_SYSTEM isn't defined on Windows. + # [Issue #416] + raise socket.gaierror(socket.EAI_AGAIN, "Temporary failure in name resolution") + port = None + try: + # Is it a port literal? + if service is None: + port = 0 + else: + port = int(service) + except Exception: + if flags & socket.AI_NUMERICSERV == 0: + try: + port = socket.getservbyname(service) # pyright: ignore + except Exception: + pass + if port is None: + raise socket.gaierror(socket.EAI_NONAME, "Name or service not known") + tuples = [] + if socktype == 0: + socktypes = [socket.SOCK_DGRAM, socket.SOCK_STREAM] + else: + socktypes = [socktype] + if flags & socket.AI_CANONNAME != 0: + cname = canonical_name + else: + cname = "" + for addr, af in addrs: + for socktype in socktypes: + for sockproto in _protocols_for_socktype[socktype]: + proto = int(sockproto) + addr_tuple = dns.inet.low_level_address_tuple((addr, port), af) + tuples.append((af, socktype, proto, cname, addr_tuple)) + if len(tuples) == 0: + raise socket.gaierror(socket.EAI_NONAME, "Name or service not known") + return tuples + + +def _getnameinfo(sockaddr, flags=0): + host = sockaddr[0] + port = sockaddr[1] + if len(sockaddr) == 4: + scope = sockaddr[3] + family = socket.AF_INET6 + else: + scope = None + family = socket.AF_INET + tuples = _getaddrinfo(host, port, family, socket.SOCK_STREAM, socket.SOL_TCP, 0) + if len(tuples) > 1: + raise OSError("sockaddr resolved to multiple addresses") + addr = tuples[0][4][0] + if flags & socket.NI_DGRAM: + pname = "udp" + else: + pname = "tcp" + assert isinstance(addr, str) + qname = dns.reversename.from_address(addr) + if flags & socket.NI_NUMERICHOST == 0: + try: + assert _resolver is not None + answer = _resolver.resolve(qname, "PTR") + assert answer.rrset is not None + rdata = cast(dns.rdtypes.ANY.PTR.PTR, answer.rrset[0]) + hostname = rdata.target.to_text(True) + except (NXDOMAIN, NoAnswer): + if flags & socket.NI_NAMEREQD: + raise socket.gaierror(socket.EAI_NONAME, "Name or service not known") + hostname = addr + if scope is not None: + hostname += "%" + str(scope) + else: + hostname = addr + if scope is not None: + hostname += "%" + str(scope) + if flags & socket.NI_NUMERICSERV: + service = str(port) + else: + service = socket.getservbyport(port, pname) + return (hostname, service) + + +def _getfqdn(name=None): + if name is None: + name = socket.gethostname() + try: + (name, _, _) = _gethostbyaddr(name) + # Python's version checks aliases too, but our gethostbyname + # ignores them, so we do so here as well. + except Exception: # pragma: no cover + pass + return name + + +def _gethostbyname(name): + return _gethostbyname_ex(name)[2][0] + + +def _gethostbyname_ex(name): + aliases = [] + addresses = [] + tuples = _getaddrinfo( + name, 0, socket.AF_INET, socket.SOCK_STREAM, socket.SOL_TCP, socket.AI_CANONNAME + ) + canonical = tuples[0][3] + for item in tuples: + addresses.append(item[4][0]) + # XXX we just ignore aliases + return (canonical, aliases, addresses) + + +def _gethostbyaddr(ip): + try: + dns.ipv6.inet_aton(ip) + sockaddr = (ip, 80, 0, 0) + family = socket.AF_INET6 + except Exception: + try: + dns.ipv4.inet_aton(ip) + except Exception: + raise socket.gaierror(socket.EAI_NONAME, "Name or service not known") + sockaddr = (ip, 80) + family = socket.AF_INET + (name, _) = _getnameinfo(sockaddr, socket.NI_NAMEREQD) + aliases = [] + addresses = [] + tuples = _getaddrinfo( + name, 0, family, socket.SOCK_STREAM, socket.SOL_TCP, socket.AI_CANONNAME + ) + canonical = tuples[0][3] + # We only want to include an address from the tuples if it's the + # same as the one we asked about. We do this comparison in binary + # to avoid any differences in text representations. + bin_ip = dns.inet.inet_pton(family, ip) + for item in tuples: + addr = item[4][0] + assert isinstance(addr, str) + bin_addr = dns.inet.inet_pton(family, addr) + if bin_ip == bin_addr: + addresses.append(addr) + # XXX we just ignore aliases + return (canonical, aliases, addresses) + + +def override_system_resolver(resolver: Resolver | None = None) -> None: + """Override the system resolver routines in the socket module with + versions which use dnspython's resolver. + + This can be useful in testing situations where you want to control + the resolution behavior of python code without having to change + the system's resolver settings (e.g. /etc/resolv.conf). + + The resolver to use may be specified; if it's not, the default + resolver will be used. + + resolver, a ``dns.resolver.Resolver`` or ``None``, the resolver to use. + """ + + if resolver is None: + resolver = get_default_resolver() + global _resolver + _resolver = resolver + socket.getaddrinfo = _getaddrinfo + socket.getnameinfo = _getnameinfo + socket.getfqdn = _getfqdn + socket.gethostbyname = _gethostbyname + socket.gethostbyname_ex = _gethostbyname_ex + socket.gethostbyaddr = _gethostbyaddr + + +def restore_system_resolver() -> None: + """Undo the effects of prior override_system_resolver().""" + + global _resolver + _resolver = None + socket.getaddrinfo = _original_getaddrinfo + socket.getnameinfo = _original_getnameinfo + socket.getfqdn = _original_getfqdn + socket.gethostbyname = _original_gethostbyname + socket.gethostbyname_ex = _original_gethostbyname_ex + socket.gethostbyaddr = _original_gethostbyaddr diff --git a/venv/lib/python3.12/site-packages/dns/reversename.py b/venv/lib/python3.12/site-packages/dns/reversename.py new file mode 100644 index 0000000..60a4e83 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/reversename.py @@ -0,0 +1,106 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2006-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS Reverse Map Names.""" + +import binascii + +import dns.exception +import dns.ipv4 +import dns.ipv6 +import dns.name + +ipv4_reverse_domain = dns.name.from_text("in-addr.arpa.") +ipv6_reverse_domain = dns.name.from_text("ip6.arpa.") + + +def from_address( + text: str, + v4_origin: dns.name.Name = ipv4_reverse_domain, + v6_origin: dns.name.Name = ipv6_reverse_domain, +) -> dns.name.Name: + """Convert an IPv4 or IPv6 address in textual form into a Name object whose + value is the reverse-map domain name of the address. + + *text*, a ``str``, is an IPv4 or IPv6 address in textual form + (e.g. '127.0.0.1', '::1') + + *v4_origin*, a ``dns.name.Name`` to append to the labels corresponding to + the address if the address is an IPv4 address, instead of the default + (in-addr.arpa.) + + *v6_origin*, a ``dns.name.Name`` to append to the labels corresponding to + the address if the address is an IPv6 address, instead of the default + (ip6.arpa.) + + Raises ``dns.exception.SyntaxError`` if the address is badly formed. + + Returns a ``dns.name.Name``. + """ + + try: + v6 = dns.ipv6.inet_aton(text) + if dns.ipv6.is_mapped(v6): + parts = [str(byte) for byte in v6[12:]] + origin = v4_origin + else: + parts = [x for x in str(binascii.hexlify(v6).decode())] + origin = v6_origin + except Exception: + parts = [str(byte) for byte in dns.ipv4.inet_aton(text)] + origin = v4_origin + return dns.name.from_text(".".join(reversed(parts)), origin=origin) + + +def to_address( + name: dns.name.Name, + v4_origin: dns.name.Name = ipv4_reverse_domain, + v6_origin: dns.name.Name = ipv6_reverse_domain, +) -> str: + """Convert a reverse map domain name into textual address form. + + *name*, a ``dns.name.Name``, an IPv4 or IPv6 address in reverse-map name + form. + + *v4_origin*, a ``dns.name.Name`` representing the top-level domain for + IPv4 addresses, instead of the default (in-addr.arpa.) + + *v6_origin*, a ``dns.name.Name`` representing the top-level domain for + IPv4 addresses, instead of the default (ip6.arpa.) + + Raises ``dns.exception.SyntaxError`` if the name does not have a + reverse-map form. + + Returns a ``str``. + """ + + if name.is_subdomain(v4_origin): + name = name.relativize(v4_origin) + text = b".".join(reversed(name.labels)) + # run through inet_ntoa() to check syntax and make pretty. + return dns.ipv4.inet_ntoa(dns.ipv4.inet_aton(text)) + elif name.is_subdomain(v6_origin): + name = name.relativize(v6_origin) + labels = list(reversed(name.labels)) + parts = [] + for i in range(0, len(labels), 4): + parts.append(b"".join(labels[i : i + 4])) + text = b":".join(parts) + # run through inet_ntoa() to check syntax and make pretty. + return dns.ipv6.inet_ntoa(dns.ipv6.inet_aton(text)) + else: + raise dns.exception.SyntaxError("unknown reverse-map address family") diff --git a/venv/lib/python3.12/site-packages/dns/rrset.py b/venv/lib/python3.12/site-packages/dns/rrset.py new file mode 100644 index 0000000..271ddbe --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/rrset.py @@ -0,0 +1,287 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS RRsets (an RRset is a named rdataset)""" + +from typing import Any, Collection, Dict, cast + +import dns.name +import dns.rdata +import dns.rdataclass +import dns.rdataset +import dns.rdatatype +import dns.renderer + + +class RRset(dns.rdataset.Rdataset): + """A DNS RRset (named rdataset). + + RRset inherits from Rdataset, and RRsets can be treated as + Rdatasets in most cases. There are, however, a few notable + exceptions. RRsets have different to_wire() and to_text() method + arguments, reflecting the fact that RRsets always have an owner + name. + """ + + __slots__ = ["name", "deleting"] + + def __init__( + self, + name: dns.name.Name, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + deleting: dns.rdataclass.RdataClass | None = None, + ): + """Create a new RRset.""" + + super().__init__(rdclass, rdtype, covers) + self.name = name + self.deleting = deleting + + def _clone(self): + obj = cast(RRset, super()._clone()) + obj.name = self.name + obj.deleting = self.deleting + return obj + + def __repr__(self): + if self.covers == 0: + ctext = "" + else: + ctext = "(" + dns.rdatatype.to_text(self.covers) + ")" + if self.deleting is not None: + dtext = " delete=" + dns.rdataclass.to_text(self.deleting) + else: + dtext = "" + return ( + "" + ) + + def __str__(self): + return self.to_text() + + def __eq__(self, other): + if isinstance(other, RRset): + if self.name != other.name: + return False + elif not isinstance(other, dns.rdataset.Rdataset): + return False + return super().__eq__(other) + + def match(self, *args: Any, **kwargs: Any) -> bool: # type: ignore[override] + """Does this rrset match the specified attributes? + + Behaves as :py:func:`full_match()` if the first argument is a + ``dns.name.Name``, and as :py:func:`dns.rdataset.Rdataset.match()` + otherwise. + + (This behavior fixes a design mistake where the signature of this + method became incompatible with that of its superclass. The fix + makes RRsets matchable as Rdatasets while preserving backwards + compatibility.) + """ + if isinstance(args[0], dns.name.Name): + return self.full_match(*args, **kwargs) # type: ignore[arg-type] + else: + return super().match(*args, **kwargs) # type: ignore[arg-type] + + def full_match( + self, + name: dns.name.Name, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType, + deleting: dns.rdataclass.RdataClass | None = None, + ) -> bool: + """Returns ``True`` if this rrset matches the specified name, class, + type, covers, and deletion state. + """ + if not super().match(rdclass, rdtype, covers): + return False + if self.name != name or self.deleting != deleting: + return False + return True + + # pylint: disable=arguments-differ + + def to_text( # type: ignore[override] + self, + origin: dns.name.Name | None = None, + relativize: bool = True, + **kw: Dict[str, Any], + ) -> str: + """Convert the RRset into DNS zone file format. + + See ``dns.name.Name.choose_relativity`` for more information + on how *origin* and *relativize* determine the way names + are emitted. + + Any additional keyword arguments are passed on to the rdata + ``to_text()`` method. + + *origin*, a ``dns.name.Name`` or ``None``, the origin for relative + names. + + *relativize*, a ``bool``. If ``True``, names will be relativized + to *origin*. + """ + + return super().to_text( + self.name, origin, relativize, self.deleting, **kw # type: ignore + ) + + def to_wire( # type: ignore[override] + self, + file: Any, + compress: dns.name.CompressType | None = None, # type: ignore + origin: dns.name.Name | None = None, + **kw: Dict[str, Any], + ) -> int: + """Convert the RRset to wire format. + + All keyword arguments are passed to ``dns.rdataset.to_wire()``; see + that function for details. + + Returns an ``int``, the number of records emitted. + """ + + return super().to_wire( + self.name, file, compress, origin, self.deleting, **kw # type:ignore + ) + + # pylint: enable=arguments-differ + + def to_rdataset(self) -> dns.rdataset.Rdataset: + """Convert an RRset into an Rdataset. + + Returns a ``dns.rdataset.Rdataset``. + """ + return dns.rdataset.from_rdata_list(self.ttl, list(self)) + + +def from_text_list( + name: dns.name.Name | str, + ttl: int, + rdclass: dns.rdataclass.RdataClass | str, + rdtype: dns.rdatatype.RdataType | str, + text_rdatas: Collection[str], + idna_codec: dns.name.IDNACodec | None = None, + origin: dns.name.Name | None = None, + relativize: bool = True, + relativize_to: dns.name.Name | None = None, +) -> RRset: + """Create an RRset with the specified name, TTL, class, and type, and with + the specified list of rdatas in text format. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder to use; if ``None``, the default IDNA 2003 + encoder/decoder is used. + + *origin*, a ``dns.name.Name`` (or ``None``), the + origin to use for relative names. + + *relativize*, a ``bool``. If true, name will be relativized. + + *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use + when relativizing names. If not set, the *origin* value will be used. + + Returns a ``dns.rrset.RRset`` object. + """ + + if isinstance(name, str): + name = dns.name.from_text(name, None, idna_codec=idna_codec) + rdclass = dns.rdataclass.RdataClass.make(rdclass) + rdtype = dns.rdatatype.RdataType.make(rdtype) + r = RRset(name, rdclass, rdtype) + r.update_ttl(ttl) + for t in text_rdatas: + rd = dns.rdata.from_text( + r.rdclass, r.rdtype, t, origin, relativize, relativize_to, idna_codec + ) + r.add(rd) + return r + + +def from_text( + name: dns.name.Name | str, + ttl: int, + rdclass: dns.rdataclass.RdataClass | str, + rdtype: dns.rdatatype.RdataType | str, + *text_rdatas: Any, +) -> RRset: + """Create an RRset with the specified name, TTL, class, and type and with + the specified rdatas in text format. + + Returns a ``dns.rrset.RRset`` object. + """ + + return from_text_list( + name, ttl, rdclass, rdtype, cast(Collection[str], text_rdatas) + ) + + +def from_rdata_list( + name: dns.name.Name | str, + ttl: int, + rdatas: Collection[dns.rdata.Rdata], + idna_codec: dns.name.IDNACodec | None = None, +) -> RRset: + """Create an RRset with the specified name and TTL, and with + the specified list of rdata objects. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder to use; if ``None``, the default IDNA 2003 + encoder/decoder is used. + + Returns a ``dns.rrset.RRset`` object. + + """ + + if isinstance(name, str): + name = dns.name.from_text(name, None, idna_codec=idna_codec) + + if len(rdatas) == 0: + raise ValueError("rdata list must not be empty") + r = None + for rd in rdatas: + if r is None: + r = RRset(name, rd.rdclass, rd.rdtype) + r.update_ttl(ttl) + r.add(rd) + assert r is not None + return r + + +def from_rdata(name: dns.name.Name | str, ttl: int, *rdatas: Any) -> RRset: + """Create an RRset with the specified name and TTL, and with + the specified rdata objects. + + Returns a ``dns.rrset.RRset`` object. + """ + + return from_rdata_list(name, ttl, cast(Collection[dns.rdata.Rdata], rdatas)) diff --git a/venv/lib/python3.12/site-packages/dns/serial.py b/venv/lib/python3.12/site-packages/dns/serial.py new file mode 100644 index 0000000..3417299 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/serial.py @@ -0,0 +1,118 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +"""Serial Number Arthimetic from RFC 1982""" + + +class Serial: + def __init__(self, value: int, bits: int = 32): + self.value = value % 2**bits + self.bits = bits + + def __repr__(self): + return f"dns.serial.Serial({self.value}, {self.bits})" + + def __eq__(self, other): + if isinstance(other, int): + other = Serial(other, self.bits) + elif not isinstance(other, Serial) or other.bits != self.bits: + return NotImplemented + return self.value == other.value + + def __ne__(self, other): + if isinstance(other, int): + other = Serial(other, self.bits) + elif not isinstance(other, Serial) or other.bits != self.bits: + return NotImplemented + return self.value != other.value + + def __lt__(self, other): + if isinstance(other, int): + other = Serial(other, self.bits) + elif not isinstance(other, Serial) or other.bits != self.bits: + return NotImplemented + if self.value < other.value and other.value - self.value < 2 ** (self.bits - 1): + return True + elif self.value > other.value and self.value - other.value > 2 ** ( + self.bits - 1 + ): + return True + else: + return False + + def __le__(self, other): + return self == other or self < other + + def __gt__(self, other): + if isinstance(other, int): + other = Serial(other, self.bits) + elif not isinstance(other, Serial) or other.bits != self.bits: + return NotImplemented + if self.value < other.value and other.value - self.value > 2 ** (self.bits - 1): + return True + elif self.value > other.value and self.value - other.value < 2 ** ( + self.bits - 1 + ): + return True + else: + return False + + def __ge__(self, other): + return self == other or self > other + + def __add__(self, other): + v = self.value + if isinstance(other, Serial): + delta = other.value + elif isinstance(other, int): + delta = other + else: + raise ValueError + if abs(delta) > (2 ** (self.bits - 1) - 1): + raise ValueError + v += delta + v = v % 2**self.bits + return Serial(v, self.bits) + + def __iadd__(self, other): + v = self.value + if isinstance(other, Serial): + delta = other.value + elif isinstance(other, int): + delta = other + else: + raise ValueError + if abs(delta) > (2 ** (self.bits - 1) - 1): + raise ValueError + v += delta + v = v % 2**self.bits + self.value = v + return self + + def __sub__(self, other): + v = self.value + if isinstance(other, Serial): + delta = other.value + elif isinstance(other, int): + delta = other + else: + raise ValueError + if abs(delta) > (2 ** (self.bits - 1) - 1): + raise ValueError + v -= delta + v = v % 2**self.bits + return Serial(v, self.bits) + + def __isub__(self, other): + v = self.value + if isinstance(other, Serial): + delta = other.value + elif isinstance(other, int): + delta = other + else: + raise ValueError + if abs(delta) > (2 ** (self.bits - 1) - 1): + raise ValueError + v -= delta + v = v % 2**self.bits + self.value = v + return self diff --git a/venv/lib/python3.12/site-packages/dns/set.py b/venv/lib/python3.12/site-packages/dns/set.py new file mode 100644 index 0000000..ae8f0dd --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/set.py @@ -0,0 +1,308 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +import itertools + + +class Set: + """A simple set class. + + This class was originally used to deal with python not having a set class, and + originally the class used lists in its implementation. The ordered and indexable + nature of RRsets and Rdatasets is unfortunately widely used in dnspython + applications, so for backwards compatibility sets continue to be a custom class, now + based on an ordered dictionary. + """ + + __slots__ = ["items"] + + def __init__(self, items=None): + """Initialize the set. + + *items*, an iterable or ``None``, the initial set of items. + """ + + self.items = dict() + if items is not None: + for item in items: + # This is safe for how we use set, but if other code + # subclasses it could be a legitimate issue. + self.add(item) # lgtm[py/init-calls-subclass] + + def __repr__(self): + return f"dns.set.Set({repr(list(self.items.keys()))})" # pragma: no cover + + def add(self, item): + """Add an item to the set.""" + + if item not in self.items: + self.items[item] = None + + def remove(self, item): + """Remove an item from the set.""" + + try: + del self.items[item] + except KeyError: + raise ValueError + + def discard(self, item): + """Remove an item from the set if present.""" + + self.items.pop(item, None) + + def pop(self): + """Remove an arbitrary item from the set.""" + (k, _) = self.items.popitem() + return k + + def _clone(self) -> "Set": + """Make a (shallow) copy of the set. + + There is a 'clone protocol' that subclasses of this class + should use. To make a copy, first call your super's _clone() + method, and use the object returned as the new instance. Then + make shallow copies of the attributes defined in the subclass. + + This protocol allows us to write the set algorithms that + return new instances (e.g. union) once, and keep using them in + subclasses. + """ + + if hasattr(self, "_clone_class"): + cls = self._clone_class # type: ignore + else: + cls = self.__class__ + obj = cls.__new__(cls) + obj.items = dict() + obj.items.update(self.items) + return obj + + def __copy__(self): + """Make a (shallow) copy of the set.""" + + return self._clone() + + def copy(self): + """Make a (shallow) copy of the set.""" + + return self._clone() + + def union_update(self, other): + """Update the set, adding any elements from other which are not + already in the set. + """ + + if not isinstance(other, Set): + raise ValueError("other must be a Set instance") + if self is other: # lgtm[py/comparison-using-is] + return + for item in other.items: + self.add(item) + + def intersection_update(self, other): + """Update the set, removing any elements from other which are not + in both sets. + """ + + if not isinstance(other, Set): + raise ValueError("other must be a Set instance") + if self is other: # lgtm[py/comparison-using-is] + return + # we make a copy of the list so that we can remove items from + # the list without breaking the iterator. + for item in list(self.items): + if item not in other.items: + del self.items[item] + + def difference_update(self, other): + """Update the set, removing any elements from other which are in + the set. + """ + + if not isinstance(other, Set): + raise ValueError("other must be a Set instance") + if self is other: # lgtm[py/comparison-using-is] + self.items.clear() + else: + for item in other.items: + self.discard(item) + + def symmetric_difference_update(self, other): + """Update the set, retaining only elements unique to both sets.""" + + if not isinstance(other, Set): + raise ValueError("other must be a Set instance") + if self is other: # lgtm[py/comparison-using-is] + self.items.clear() + else: + overlap = self.intersection(other) + self.union_update(other) + self.difference_update(overlap) + + def union(self, other): + """Return a new set which is the union of ``self`` and ``other``. + + Returns the same Set type as this set. + """ + + obj = self._clone() + obj.union_update(other) + return obj + + def intersection(self, other): + """Return a new set which is the intersection of ``self`` and + ``other``. + + Returns the same Set type as this set. + """ + + obj = self._clone() + obj.intersection_update(other) + return obj + + def difference(self, other): + """Return a new set which ``self`` - ``other``, i.e. the items + in ``self`` which are not also in ``other``. + + Returns the same Set type as this set. + """ + + obj = self._clone() + obj.difference_update(other) + return obj + + def symmetric_difference(self, other): + """Return a new set which (``self`` - ``other``) | (``other`` + - ``self), ie: the items in either ``self`` or ``other`` which + are not contained in their intersection. + + Returns the same Set type as this set. + """ + + obj = self._clone() + obj.symmetric_difference_update(other) + return obj + + def __or__(self, other): + return self.union(other) + + def __and__(self, other): + return self.intersection(other) + + def __add__(self, other): + return self.union(other) + + def __sub__(self, other): + return self.difference(other) + + def __xor__(self, other): + return self.symmetric_difference(other) + + def __ior__(self, other): + self.union_update(other) + return self + + def __iand__(self, other): + self.intersection_update(other) + return self + + def __iadd__(self, other): + self.union_update(other) + return self + + def __isub__(self, other): + self.difference_update(other) + return self + + def __ixor__(self, other): + self.symmetric_difference_update(other) + return self + + def update(self, other): + """Update the set, adding any elements from other which are not + already in the set. + + *other*, the collection of items with which to update the set, which + may be any iterable type. + """ + + for item in other: + self.add(item) + + def clear(self): + """Make the set empty.""" + self.items.clear() + + def __eq__(self, other): + return self.items == other.items + + def __ne__(self, other): + return not self.__eq__(other) + + def __len__(self): + return len(self.items) + + def __iter__(self): + return iter(self.items) + + def __getitem__(self, i): + if isinstance(i, slice): + return list(itertools.islice(self.items, i.start, i.stop, i.step)) + else: + return next(itertools.islice(self.items, i, i + 1)) + + def __delitem__(self, i): + if isinstance(i, slice): + for elt in list(self[i]): + del self.items[elt] + else: + del self.items[self[i]] + + def issubset(self, other): + """Is this set a subset of *other*? + + Returns a ``bool``. + """ + + if not isinstance(other, Set): + raise ValueError("other must be a Set instance") + for item in self.items: + if item not in other.items: + return False + return True + + def issuperset(self, other): + """Is this set a superset of *other*? + + Returns a ``bool``. + """ + + if not isinstance(other, Set): + raise ValueError("other must be a Set instance") + for item in other.items: + if item not in self.items: + return False + return True + + def isdisjoint(self, other): + if not isinstance(other, Set): + raise ValueError("other must be a Set instance") + for item in other.items: + if item in self.items: + return False + return True diff --git a/venv/lib/python3.12/site-packages/dns/tokenizer.py b/venv/lib/python3.12/site-packages/dns/tokenizer.py new file mode 100644 index 0000000..86ae3e2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/tokenizer.py @@ -0,0 +1,706 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""Tokenize DNS zone file format""" + +import io +import sys +from typing import Any, List, Tuple + +import dns.exception +import dns.name +import dns.ttl + +_DELIMITERS = {" ", "\t", "\n", ";", "(", ")", '"'} +_QUOTING_DELIMITERS = {'"'} + +EOF = 0 +EOL = 1 +WHITESPACE = 2 +IDENTIFIER = 3 +QUOTED_STRING = 4 +COMMENT = 5 +DELIMITER = 6 + + +class UngetBufferFull(dns.exception.DNSException): + """An attempt was made to unget a token when the unget buffer was full.""" + + +class Token: + """A DNS zone file format token. + + ttype: The token type + value: The token value + has_escape: Does the token value contain escapes? + """ + + def __init__( + self, + ttype: int, + value: Any = "", + has_escape: bool = False, + comment: str | None = None, + ): + """Initialize a token instance.""" + + self.ttype = ttype + self.value = value + self.has_escape = has_escape + self.comment = comment + + def is_eof(self) -> bool: + return self.ttype == EOF + + def is_eol(self) -> bool: + return self.ttype == EOL + + def is_whitespace(self) -> bool: + return self.ttype == WHITESPACE + + def is_identifier(self) -> bool: + return self.ttype == IDENTIFIER + + def is_quoted_string(self) -> bool: + return self.ttype == QUOTED_STRING + + def is_comment(self) -> bool: + return self.ttype == COMMENT + + def is_delimiter(self) -> bool: # pragma: no cover (we don't return delimiters yet) + return self.ttype == DELIMITER + + def is_eol_or_eof(self) -> bool: + return self.ttype == EOL or self.ttype == EOF + + def __eq__(self, other): + if not isinstance(other, Token): + return False + return self.ttype == other.ttype and self.value == other.value + + def __ne__(self, other): + if not isinstance(other, Token): + return True + return self.ttype != other.ttype or self.value != other.value + + def __str__(self): + return f'{self.ttype} "{self.value}"' + + def unescape(self) -> "Token": + if not self.has_escape: + return self + unescaped = "" + l = len(self.value) + i = 0 + while i < l: + c = self.value[i] + i += 1 + if c == "\\": + if i >= l: # pragma: no cover (can't happen via get()) + raise dns.exception.UnexpectedEnd + c = self.value[i] + i += 1 + if c.isdigit(): + if i >= l: + raise dns.exception.UnexpectedEnd + c2 = self.value[i] + i += 1 + if i >= l: + raise dns.exception.UnexpectedEnd + c3 = self.value[i] + i += 1 + if not (c2.isdigit() and c3.isdigit()): + raise dns.exception.SyntaxError + codepoint = int(c) * 100 + int(c2) * 10 + int(c3) + if codepoint > 255: + raise dns.exception.SyntaxError + c = chr(codepoint) + unescaped += c + return Token(self.ttype, unescaped) + + def unescape_to_bytes(self) -> "Token": + # We used to use unescape() for TXT-like records, but this + # caused problems as we'd process DNS escapes into Unicode code + # points instead of byte values, and then a to_text() of the + # processed data would not equal the original input. For + # example, \226 in the TXT record would have a to_text() of + # \195\162 because we applied UTF-8 encoding to Unicode code + # point 226. + # + # We now apply escapes while converting directly to bytes, + # avoiding this double encoding. + # + # This code also handles cases where the unicode input has + # non-ASCII code-points in it by converting it to UTF-8. TXT + # records aren't defined for Unicode, but this is the best we + # can do to preserve meaning. For example, + # + # foo\u200bbar + # + # (where \u200b is Unicode code point 0x200b) will be treated + # as if the input had been the UTF-8 encoding of that string, + # namely: + # + # foo\226\128\139bar + # + unescaped = b"" + l = len(self.value) + i = 0 + while i < l: + c = self.value[i] + i += 1 + if c == "\\": + if i >= l: # pragma: no cover (can't happen via get()) + raise dns.exception.UnexpectedEnd + c = self.value[i] + i += 1 + if c.isdigit(): + if i >= l: + raise dns.exception.UnexpectedEnd + c2 = self.value[i] + i += 1 + if i >= l: + raise dns.exception.UnexpectedEnd + c3 = self.value[i] + i += 1 + if not (c2.isdigit() and c3.isdigit()): + raise dns.exception.SyntaxError + codepoint = int(c) * 100 + int(c2) * 10 + int(c3) + if codepoint > 255: + raise dns.exception.SyntaxError + unescaped += b"%c" % (codepoint) + else: + # Note that as mentioned above, if c is a Unicode + # code point outside of the ASCII range, then this + # += is converting that code point to its UTF-8 + # encoding and appending multiple bytes to + # unescaped. + unescaped += c.encode() + else: + unescaped += c.encode() + return Token(self.ttype, bytes(unescaped)) + + +class Tokenizer: + """A DNS zone file format tokenizer. + + A token object is basically a (type, value) tuple. The valid + types are EOF, EOL, WHITESPACE, IDENTIFIER, QUOTED_STRING, + COMMENT, and DELIMITER. + + file: The file to tokenize + + ungotten_char: The most recently ungotten character, or None. + + ungotten_token: The most recently ungotten token, or None. + + multiline: The current multiline level. This value is increased + by one every time a '(' delimiter is read, and decreased by one every time + a ')' delimiter is read. + + quoting: This variable is true if the tokenizer is currently + reading a quoted string. + + eof: This variable is true if the tokenizer has encountered EOF. + + delimiters: The current delimiter dictionary. + + line_number: The current line number + + filename: A filename that will be returned by the where() method. + + idna_codec: A dns.name.IDNACodec, specifies the IDNA + encoder/decoder. If None, the default IDNA 2003 + encoder/decoder is used. + """ + + def __init__( + self, + f: Any = sys.stdin, + filename: str | None = None, + idna_codec: dns.name.IDNACodec | None = None, + ): + """Initialize a tokenizer instance. + + f: The file to tokenize. The default is sys.stdin. + This parameter may also be a string, in which case the tokenizer + will take its input from the contents of the string. + + filename: the name of the filename that the where() method + will return. + + idna_codec: A dns.name.IDNACodec, specifies the IDNA + encoder/decoder. If None, the default IDNA 2003 + encoder/decoder is used. + """ + + if isinstance(f, str): + f = io.StringIO(f) + if filename is None: + filename = "" + elif isinstance(f, bytes): + f = io.StringIO(f.decode()) + if filename is None: + filename = "" + else: + if filename is None: + if f is sys.stdin: + filename = "" + else: + filename = "" + self.file = f + self.ungotten_char: str | None = None + self.ungotten_token: Token | None = None + self.multiline = 0 + self.quoting = False + self.eof = False + self.delimiters = _DELIMITERS + self.line_number = 1 + assert filename is not None + self.filename = filename + if idna_codec is None: + self.idna_codec: dns.name.IDNACodec = dns.name.IDNA_2003 + else: + self.idna_codec = idna_codec + + def _get_char(self) -> str: + """Read a character from input.""" + + if self.ungotten_char is None: + if self.eof: + c = "" + else: + c = self.file.read(1) + if c == "": + self.eof = True + elif c == "\n": + self.line_number += 1 + else: + c = self.ungotten_char + self.ungotten_char = None + return c + + def where(self) -> Tuple[str, int]: + """Return the current location in the input. + + Returns a (string, int) tuple. The first item is the filename of + the input, the second is the current line number. + """ + + return (self.filename, self.line_number) + + def _unget_char(self, c: str) -> None: + """Unget a character. + + The unget buffer for characters is only one character large; it is + an error to try to unget a character when the unget buffer is not + empty. + + c: the character to unget + raises UngetBufferFull: there is already an ungotten char + """ + + if self.ungotten_char is not None: + # this should never happen! + raise UngetBufferFull # pragma: no cover + self.ungotten_char = c + + def skip_whitespace(self) -> int: + """Consume input until a non-whitespace character is encountered. + + The non-whitespace character is then ungotten, and the number of + whitespace characters consumed is returned. + + If the tokenizer is in multiline mode, then newlines are whitespace. + + Returns the number of characters skipped. + """ + + skipped = 0 + while True: + c = self._get_char() + if c != " " and c != "\t": + if (c != "\n") or not self.multiline: + self._unget_char(c) + return skipped + skipped += 1 + + def get(self, want_leading: bool = False, want_comment: bool = False) -> Token: + """Get the next token. + + want_leading: If True, return a WHITESPACE token if the + first character read is whitespace. The default is False. + + want_comment: If True, return a COMMENT token if the + first token read is a comment. The default is False. + + Raises dns.exception.UnexpectedEnd: input ended prematurely + + Raises dns.exception.SyntaxError: input was badly formed + + Returns a Token. + """ + + if self.ungotten_token is not None: + utoken = self.ungotten_token + self.ungotten_token = None + if utoken.is_whitespace(): + if want_leading: + return utoken + elif utoken.is_comment(): + if want_comment: + return utoken + else: + return utoken + skipped = self.skip_whitespace() + if want_leading and skipped > 0: + return Token(WHITESPACE, " ") + token = "" + ttype = IDENTIFIER + has_escape = False + while True: + c = self._get_char() + if c == "" or c in self.delimiters: + if c == "" and self.quoting: + raise dns.exception.UnexpectedEnd + if token == "" and ttype != QUOTED_STRING: + if c == "(": + self.multiline += 1 + self.skip_whitespace() + continue + elif c == ")": + if self.multiline <= 0: + raise dns.exception.SyntaxError + self.multiline -= 1 + self.skip_whitespace() + continue + elif c == '"': + if not self.quoting: + self.quoting = True + self.delimiters = _QUOTING_DELIMITERS + ttype = QUOTED_STRING + continue + else: + self.quoting = False + self.delimiters = _DELIMITERS + self.skip_whitespace() + continue + elif c == "\n": + return Token(EOL, "\n") + elif c == ";": + while 1: + c = self._get_char() + if c == "\n" or c == "": + break + token += c + if want_comment: + self._unget_char(c) + return Token(COMMENT, token) + elif c == "": + if self.multiline: + raise dns.exception.SyntaxError( + "unbalanced parentheses" + ) + return Token(EOF, comment=token) + elif self.multiline: + self.skip_whitespace() + token = "" + continue + else: + return Token(EOL, "\n", comment=token) + else: + # This code exists in case we ever want a + # delimiter to be returned. It never produces + # a token currently. + token = c + ttype = DELIMITER + else: + self._unget_char(c) + break + elif self.quoting and c == "\n": + raise dns.exception.SyntaxError("newline in quoted string") + elif c == "\\": + # + # It's an escape. Put it and the next character into + # the token; it will be checked later for goodness. + # + token += c + has_escape = True + c = self._get_char() + if c == "" or (c == "\n" and not self.quoting): + raise dns.exception.UnexpectedEnd + token += c + if token == "" and ttype != QUOTED_STRING: + if self.multiline: + raise dns.exception.SyntaxError("unbalanced parentheses") + ttype = EOF + return Token(ttype, token, has_escape) + + def unget(self, token: Token) -> None: + """Unget a token. + + The unget buffer for tokens is only one token large; it is + an error to try to unget a token when the unget buffer is not + empty. + + token: the token to unget + + Raises UngetBufferFull: there is already an ungotten token + """ + + if self.ungotten_token is not None: + raise UngetBufferFull + self.ungotten_token = token + + def next(self): + """Return the next item in an iteration. + + Returns a Token. + """ + + token = self.get() + if token.is_eof(): + raise StopIteration + return token + + __next__ = next + + def __iter__(self): + return self + + # Helpers + + def get_int(self, base: int = 10) -> int: + """Read the next token and interpret it as an unsigned integer. + + Raises dns.exception.SyntaxError if not an unsigned integer. + + Returns an int. + """ + + token = self.get().unescape() + if not token.is_identifier(): + raise dns.exception.SyntaxError("expecting an identifier") + if not token.value.isdigit(): + raise dns.exception.SyntaxError("expecting an integer") + return int(token.value, base) + + def get_uint8(self) -> int: + """Read the next token and interpret it as an 8-bit unsigned + integer. + + Raises dns.exception.SyntaxError if not an 8-bit unsigned integer. + + Returns an int. + """ + + value = self.get_int() + if value < 0 or value > 255: + raise dns.exception.SyntaxError(f"{value} is not an unsigned 8-bit integer") + return value + + def get_uint16(self, base: int = 10) -> int: + """Read the next token and interpret it as a 16-bit unsigned + integer. + + Raises dns.exception.SyntaxError if not a 16-bit unsigned integer. + + Returns an int. + """ + + value = self.get_int(base=base) + if value < 0 or value > 65535: + if base == 8: + raise dns.exception.SyntaxError( + f"{value:o} is not an octal unsigned 16-bit integer" + ) + else: + raise dns.exception.SyntaxError( + f"{value} is not an unsigned 16-bit integer" + ) + return value + + def get_uint32(self, base: int = 10) -> int: + """Read the next token and interpret it as a 32-bit unsigned + integer. + + Raises dns.exception.SyntaxError if not a 32-bit unsigned integer. + + Returns an int. + """ + + value = self.get_int(base=base) + if value < 0 or value > 4294967295: + raise dns.exception.SyntaxError( + f"{value} is not an unsigned 32-bit integer" + ) + return value + + def get_uint48(self, base: int = 10) -> int: + """Read the next token and interpret it as a 48-bit unsigned + integer. + + Raises dns.exception.SyntaxError if not a 48-bit unsigned integer. + + Returns an int. + """ + + value = self.get_int(base=base) + if value < 0 or value > 281474976710655: + raise dns.exception.SyntaxError( + f"{value} is not an unsigned 48-bit integer" + ) + return value + + def get_string(self, max_length: int | None = None) -> str: + """Read the next token and interpret it as a string. + + Raises dns.exception.SyntaxError if not a string. + Raises dns.exception.SyntaxError if token value length + exceeds max_length (if specified). + + Returns a string. + """ + + token = self.get().unescape() + if not (token.is_identifier() or token.is_quoted_string()): + raise dns.exception.SyntaxError("expecting a string") + if max_length and len(token.value) > max_length: + raise dns.exception.SyntaxError("string too long") + return token.value + + def get_identifier(self) -> str: + """Read the next token, which should be an identifier. + + Raises dns.exception.SyntaxError if not an identifier. + + Returns a string. + """ + + token = self.get().unescape() + if not token.is_identifier(): + raise dns.exception.SyntaxError("expecting an identifier") + return token.value + + def get_remaining(self, max_tokens: int | None = None) -> List[Token]: + """Return the remaining tokens on the line, until an EOL or EOF is seen. + + max_tokens: If not None, stop after this number of tokens. + + Returns a list of tokens. + """ + + tokens = [] + while True: + token = self.get() + if token.is_eol_or_eof(): + self.unget(token) + break + tokens.append(token) + if len(tokens) == max_tokens: + break + return tokens + + def concatenate_remaining_identifiers(self, allow_empty: bool = False) -> str: + """Read the remaining tokens on the line, which should be identifiers. + + Raises dns.exception.SyntaxError if there are no remaining tokens, + unless `allow_empty=True` is given. + + Raises dns.exception.SyntaxError if a token is seen that is not an + identifier. + + Returns a string containing a concatenation of the remaining + identifiers. + """ + s = "" + while True: + token = self.get().unescape() + if token.is_eol_or_eof(): + self.unget(token) + break + if not token.is_identifier(): + raise dns.exception.SyntaxError + s += token.value + if not (allow_empty or s): + raise dns.exception.SyntaxError("expecting another identifier") + return s + + def as_name( + self, + token: Token, + origin: dns.name.Name | None = None, + relativize: bool = False, + relativize_to: dns.name.Name | None = None, + ) -> dns.name.Name: + """Try to interpret the token as a DNS name. + + Raises dns.exception.SyntaxError if not a name. + + Returns a dns.name.Name. + """ + if not token.is_identifier(): + raise dns.exception.SyntaxError("expecting an identifier") + name = dns.name.from_text(token.value, origin, self.idna_codec) + return name.choose_relativity(relativize_to or origin, relativize) + + def get_name( + self, + origin: dns.name.Name | None = None, + relativize: bool = False, + relativize_to: dns.name.Name | None = None, + ) -> dns.name.Name: + """Read the next token and interpret it as a DNS name. + + Raises dns.exception.SyntaxError if not a name. + + Returns a dns.name.Name. + """ + + token = self.get() + return self.as_name(token, origin, relativize, relativize_to) + + def get_eol_as_token(self) -> Token: + """Read the next token and raise an exception if it isn't EOL or + EOF. + + Returns a string. + """ + + token = self.get() + if not token.is_eol_or_eof(): + raise dns.exception.SyntaxError( + f'expected EOL or EOF, got {token.ttype} "{token.value}"' + ) + return token + + def get_eol(self) -> str: + return self.get_eol_as_token().value + + def get_ttl(self) -> int: + """Read the next token and interpret it as a DNS TTL. + + Raises dns.exception.SyntaxError or dns.ttl.BadTTL if not an + identifier or badly formed. + + Returns an int. + """ + + token = self.get().unescape() + if not token.is_identifier(): + raise dns.exception.SyntaxError("expecting an identifier") + return dns.ttl.from_text(token.value) diff --git a/venv/lib/python3.12/site-packages/dns/transaction.py b/venv/lib/python3.12/site-packages/dns/transaction.py new file mode 100644 index 0000000..9ecd737 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/transaction.py @@ -0,0 +1,651 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import collections +from typing import Any, Callable, Iterator, List, Tuple + +import dns.exception +import dns.name +import dns.node +import dns.rdata +import dns.rdataclass +import dns.rdataset +import dns.rdatatype +import dns.rrset +import dns.serial +import dns.ttl + + +class TransactionManager: + def reader(self) -> "Transaction": + """Begin a read-only transaction.""" + raise NotImplementedError # pragma: no cover + + def writer(self, replacement: bool = False) -> "Transaction": + """Begin a writable transaction. + + *replacement*, a ``bool``. If `True`, the content of the + transaction completely replaces any prior content. If False, + the default, then the content of the transaction updates the + existing content. + """ + raise NotImplementedError # pragma: no cover + + def origin_information( + self, + ) -> Tuple[dns.name.Name | None, bool, dns.name.Name | None]: + """Returns a tuple + + (absolute_origin, relativize, effective_origin) + + giving the absolute name of the default origin for any + relative domain names, the "effective origin", and whether + names should be relativized. The "effective origin" is the + absolute origin if relativize is False, and the empty name if + relativize is true. (The effective origin is provided even + though it can be computed from the absolute_origin and + relativize setting because it avoids a lot of code + duplication.) + + If the returned names are `None`, then no origin information is + available. + + This information is used by code working with transactions to + allow it to coordinate relativization. The transaction code + itself takes what it gets (i.e. does not change name + relativity). + + """ + raise NotImplementedError # pragma: no cover + + def get_class(self) -> dns.rdataclass.RdataClass: + """The class of the transaction manager.""" + raise NotImplementedError # pragma: no cover + + def from_wire_origin(self) -> dns.name.Name | None: + """Origin to use in from_wire() calls.""" + (absolute_origin, relativize, _) = self.origin_information() + if relativize: + return absolute_origin + else: + return None + + +class DeleteNotExact(dns.exception.DNSException): + """Existing data did not match data specified by an exact delete.""" + + +class ReadOnly(dns.exception.DNSException): + """Tried to write to a read-only transaction.""" + + +class AlreadyEnded(dns.exception.DNSException): + """Tried to use an already-ended transaction.""" + + +def _ensure_immutable_rdataset(rdataset): + if rdataset is None or isinstance(rdataset, dns.rdataset.ImmutableRdataset): + return rdataset + return dns.rdataset.ImmutableRdataset(rdataset) + + +def _ensure_immutable_node(node): + if node is None or node.is_immutable(): + return node + return dns.node.ImmutableNode(node) + + +CheckPutRdatasetType = Callable[ + ["Transaction", dns.name.Name, dns.rdataset.Rdataset], None +] +CheckDeleteRdatasetType = Callable[ + ["Transaction", dns.name.Name, dns.rdatatype.RdataType, dns.rdatatype.RdataType], + None, +] +CheckDeleteNameType = Callable[["Transaction", dns.name.Name], None] + + +class Transaction: + def __init__( + self, + manager: TransactionManager, + replacement: bool = False, + read_only: bool = False, + ): + self.manager = manager + self.replacement = replacement + self.read_only = read_only + self._ended = False + self._check_put_rdataset: List[CheckPutRdatasetType] = [] + self._check_delete_rdataset: List[CheckDeleteRdatasetType] = [] + self._check_delete_name: List[CheckDeleteNameType] = [] + + # + # This is the high level API + # + # Note that we currently use non-immutable types in the return type signature to + # avoid covariance problems, e.g. if the caller has a List[Rdataset], mypy will be + # unhappy if we return an ImmutableRdataset. + + def get( + self, + name: dns.name.Name | str | None, + rdtype: dns.rdatatype.RdataType | str, + covers: dns.rdatatype.RdataType | str = dns.rdatatype.NONE, + ) -> dns.rdataset.Rdataset: + """Return the rdataset associated with *name*, *rdtype*, and *covers*, + or `None` if not found. + + Note that the returned rdataset is immutable. + """ + self._check_ended() + if isinstance(name, str): + name = dns.name.from_text(name, None) + rdtype = dns.rdatatype.RdataType.make(rdtype) + covers = dns.rdatatype.RdataType.make(covers) + rdataset = self._get_rdataset(name, rdtype, covers) + return _ensure_immutable_rdataset(rdataset) + + def get_node(self, name: dns.name.Name) -> dns.node.Node | None: + """Return the node at *name*, if any. + + Returns an immutable node or ``None``. + """ + return _ensure_immutable_node(self._get_node(name)) + + def _check_read_only(self) -> None: + if self.read_only: + raise ReadOnly + + def add(self, *args: Any) -> None: + """Add records. + + The arguments may be: + + - rrset + + - name, rdataset... + + - name, ttl, rdata... + """ + self._check_ended() + self._check_read_only() + self._add(False, args) + + def replace(self, *args: Any) -> None: + """Replace the existing rdataset at the name with the specified + rdataset, or add the specified rdataset if there was no existing + rdataset. + + The arguments may be: + + - rrset + + - name, rdataset... + + - name, ttl, rdata... + + Note that if you want to replace the entire node, you should do + a delete of the name followed by one or more calls to add() or + replace(). + """ + self._check_ended() + self._check_read_only() + self._add(True, args) + + def delete(self, *args: Any) -> None: + """Delete records. + + It is not an error if some of the records are not in the existing + set. + + The arguments may be: + + - rrset + + - name + + - name, rdatatype, [covers] + + - name, rdataset... + + - name, rdata... + """ + self._check_ended() + self._check_read_only() + self._delete(False, args) + + def delete_exact(self, *args: Any) -> None: + """Delete records. + + The arguments may be: + + - rrset + + - name + + - name, rdatatype, [covers] + + - name, rdataset... + + - name, rdata... + + Raises dns.transaction.DeleteNotExact if some of the records + are not in the existing set. + + """ + self._check_ended() + self._check_read_only() + self._delete(True, args) + + def name_exists(self, name: dns.name.Name | str) -> bool: + """Does the specified name exist?""" + self._check_ended() + if isinstance(name, str): + name = dns.name.from_text(name, None) + return self._name_exists(name) + + def update_serial( + self, + value: int = 1, + relative: bool = True, + name: dns.name.Name = dns.name.empty, + ) -> None: + """Update the serial number. + + *value*, an `int`, is an increment if *relative* is `True`, or the + actual value to set if *relative* is `False`. + + Raises `KeyError` if there is no SOA rdataset at *name*. + + Raises `ValueError` if *value* is negative or if the increment is + so large that it would cause the new serial to be less than the + prior value. + """ + self._check_ended() + if value < 0: + raise ValueError("negative update_serial() value") + if isinstance(name, str): + name = dns.name.from_text(name, None) + rdataset = self._get_rdataset(name, dns.rdatatype.SOA, dns.rdatatype.NONE) + if rdataset is None or len(rdataset) == 0: + raise KeyError + if relative: + serial = dns.serial.Serial(rdataset[0].serial) + value + else: + serial = dns.serial.Serial(value) + serial = serial.value # convert back to int + if serial == 0: + serial = 1 + rdata = rdataset[0].replace(serial=serial) + new_rdataset = dns.rdataset.from_rdata(rdataset.ttl, rdata) + self.replace(name, new_rdataset) + + def __iter__(self): + self._check_ended() + return self._iterate_rdatasets() + + def changed(self) -> bool: + """Has this transaction changed anything? + + For read-only transactions, the result is always `False`. + + For writable transactions, the result is `True` if at some time + during the life of the transaction, the content was changed. + """ + self._check_ended() + return self._changed() + + def commit(self) -> None: + """Commit the transaction. + + Normally transactions are used as context managers and commit + or rollback automatically, but it may be done explicitly if needed. + A ``dns.transaction.Ended`` exception will be raised if you try + to use a transaction after it has been committed or rolled back. + + Raises an exception if the commit fails (in which case the transaction + is also rolled back. + """ + self._end(True) + + def rollback(self) -> None: + """Rollback the transaction. + + Normally transactions are used as context managers and commit + or rollback automatically, but it may be done explicitly if needed. + A ``dns.transaction.AlreadyEnded`` exception will be raised if you try + to use a transaction after it has been committed or rolled back. + + Rollback cannot otherwise fail. + """ + self._end(False) + + def check_put_rdataset(self, check: CheckPutRdatasetType) -> None: + """Call *check* before putting (storing) an rdataset. + + The function is called with the transaction, the name, and the rdataset. + + The check function may safely make non-mutating transaction method + calls, but behavior is undefined if mutating transaction methods are + called. The check function should raise an exception if it objects to + the put, and otherwise should return ``None``. + """ + self._check_put_rdataset.append(check) + + def check_delete_rdataset(self, check: CheckDeleteRdatasetType) -> None: + """Call *check* before deleting an rdataset. + + The function is called with the transaction, the name, the rdatatype, + and the covered rdatatype. + + The check function may safely make non-mutating transaction method + calls, but behavior is undefined if mutating transaction methods are + called. The check function should raise an exception if it objects to + the put, and otherwise should return ``None``. + """ + self._check_delete_rdataset.append(check) + + def check_delete_name(self, check: CheckDeleteNameType) -> None: + """Call *check* before putting (storing) an rdataset. + + The function is called with the transaction and the name. + + The check function may safely make non-mutating transaction method + calls, but behavior is undefined if mutating transaction methods are + called. The check function should raise an exception if it objects to + the put, and otherwise should return ``None``. + """ + self._check_delete_name.append(check) + + def iterate_rdatasets( + self, + ) -> Iterator[Tuple[dns.name.Name, dns.rdataset.Rdataset]]: + """Iterate all the rdatasets in the transaction, returning + (`dns.name.Name`, `dns.rdataset.Rdataset`) tuples. + + Note that as is usual with python iterators, adding or removing items + while iterating will invalidate the iterator and may raise `RuntimeError` + or fail to iterate over all entries.""" + self._check_ended() + return self._iterate_rdatasets() + + def iterate_names(self) -> Iterator[dns.name.Name]: + """Iterate all the names in the transaction. + + Note that as is usual with python iterators, adding or removing names + while iterating will invalidate the iterator and may raise `RuntimeError` + or fail to iterate over all entries.""" + self._check_ended() + return self._iterate_names() + + # + # Helper methods + # + + def _raise_if_not_empty(self, method, args): + if len(args) != 0: + raise TypeError(f"extra parameters to {method}") + + def _rdataset_from_args(self, method, deleting, args): + try: + arg = args.popleft() + if isinstance(arg, dns.rrset.RRset): + rdataset = arg.to_rdataset() + elif isinstance(arg, dns.rdataset.Rdataset): + rdataset = arg + else: + if deleting: + ttl = 0 + else: + if isinstance(arg, int): + ttl = arg + if ttl > dns.ttl.MAX_TTL: + raise ValueError(f"{method}: TTL value too big") + else: + raise TypeError(f"{method}: expected a TTL") + arg = args.popleft() + if isinstance(arg, dns.rdata.Rdata): + rdataset = dns.rdataset.from_rdata(ttl, arg) + else: + raise TypeError(f"{method}: expected an Rdata") + return rdataset + except IndexError: + if deleting: + return None + else: + # reraise + raise TypeError(f"{method}: expected more arguments") + + def _add(self, replace, args): + if replace: + method = "replace()" + else: + method = "add()" + try: + args = collections.deque(args) + arg = args.popleft() + if isinstance(arg, str): + arg = dns.name.from_text(arg, None) + if isinstance(arg, dns.name.Name): + name = arg + rdataset = self._rdataset_from_args(method, False, args) + elif isinstance(arg, dns.rrset.RRset): + rrset = arg + name = rrset.name + # rrsets are also rdatasets, but they don't print the + # same and can't be stored in nodes, so convert. + rdataset = rrset.to_rdataset() + else: + raise TypeError( + f"{method} requires a name or RRset as the first argument" + ) + assert rdataset is not None # for type checkers + if rdataset.rdclass != self.manager.get_class(): + raise ValueError(f"{method} has objects of wrong RdataClass") + if rdataset.rdtype == dns.rdatatype.SOA: + (_, _, origin) = self._origin_information() + if name != origin: + raise ValueError(f"{method} has non-origin SOA") + self._raise_if_not_empty(method, args) + if not replace: + existing = self._get_rdataset(name, rdataset.rdtype, rdataset.covers) + if existing is not None: + if isinstance(existing, dns.rdataset.ImmutableRdataset): + trds = dns.rdataset.Rdataset( + existing.rdclass, existing.rdtype, existing.covers + ) + trds.update(existing) + existing = trds + rdataset = existing.union(rdataset) + self._checked_put_rdataset(name, rdataset) + except IndexError: + raise TypeError(f"not enough parameters to {method}") + + def _delete(self, exact, args): + if exact: + method = "delete_exact()" + else: + method = "delete()" + try: + args = collections.deque(args) + arg = args.popleft() + if isinstance(arg, str): + arg = dns.name.from_text(arg, None) + if isinstance(arg, dns.name.Name): + name = arg + if len(args) > 0 and ( + isinstance(args[0], int) or isinstance(args[0], str) + ): + # deleting by type and (optionally) covers + rdtype = dns.rdatatype.RdataType.make(args.popleft()) + if len(args) > 0: + covers = dns.rdatatype.RdataType.make(args.popleft()) + else: + covers = dns.rdatatype.NONE + self._raise_if_not_empty(method, args) + existing = self._get_rdataset(name, rdtype, covers) + if existing is None: + if exact: + raise DeleteNotExact(f"{method}: missing rdataset") + else: + self._checked_delete_rdataset(name, rdtype, covers) + return + else: + rdataset = self._rdataset_from_args(method, True, args) + elif isinstance(arg, dns.rrset.RRset): + rdataset = arg # rrsets are also rdatasets + name = rdataset.name + else: + raise TypeError( + f"{method} requires a name or RRset as the first argument" + ) + self._raise_if_not_empty(method, args) + if rdataset: + if rdataset.rdclass != self.manager.get_class(): + raise ValueError(f"{method} has objects of wrong RdataClass") + existing = self._get_rdataset(name, rdataset.rdtype, rdataset.covers) + if existing is not None: + if exact: + intersection = existing.intersection(rdataset) + if intersection != rdataset: + raise DeleteNotExact(f"{method}: missing rdatas") + rdataset = existing.difference(rdataset) + if len(rdataset) == 0: + self._checked_delete_rdataset( + name, rdataset.rdtype, rdataset.covers + ) + else: + self._checked_put_rdataset(name, rdataset) + elif exact: + raise DeleteNotExact(f"{method}: missing rdataset") + else: + if exact and not self._name_exists(name): + raise DeleteNotExact(f"{method}: name not known") + self._checked_delete_name(name) + except IndexError: + raise TypeError(f"not enough parameters to {method}") + + def _check_ended(self): + if self._ended: + raise AlreadyEnded + + def _end(self, commit): + self._check_ended() + try: + self._end_transaction(commit) + finally: + self._ended = True + + def _checked_put_rdataset(self, name, rdataset): + for check in self._check_put_rdataset: + check(self, name, rdataset) + self._put_rdataset(name, rdataset) + + def _checked_delete_rdataset(self, name, rdtype, covers): + for check in self._check_delete_rdataset: + check(self, name, rdtype, covers) + self._delete_rdataset(name, rdtype, covers) + + def _checked_delete_name(self, name): + for check in self._check_delete_name: + check(self, name) + self._delete_name(name) + + # + # Transactions are context managers. + # + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if not self._ended: + if exc_type is None: + self.commit() + else: + self.rollback() + return False + + # + # This is the low level API, which must be implemented by subclasses + # of Transaction. + # + + def _get_rdataset(self, name, rdtype, covers): + """Return the rdataset associated with *name*, *rdtype*, and *covers*, + or `None` if not found. + """ + raise NotImplementedError # pragma: no cover + + def _put_rdataset(self, name, rdataset): + """Store the rdataset.""" + raise NotImplementedError # pragma: no cover + + def _delete_name(self, name): + """Delete all data associated with *name*. + + It is not an error if the name does not exist. + """ + raise NotImplementedError # pragma: no cover + + def _delete_rdataset(self, name, rdtype, covers): + """Delete all data associated with *name*, *rdtype*, and *covers*. + + It is not an error if the rdataset does not exist. + """ + raise NotImplementedError # pragma: no cover + + def _name_exists(self, name): + """Does name exist? + + Returns a bool. + """ + raise NotImplementedError # pragma: no cover + + def _changed(self): + """Has this transaction changed anything?""" + raise NotImplementedError # pragma: no cover + + def _end_transaction(self, commit): + """End the transaction. + + *commit*, a bool. If ``True``, commit the transaction, otherwise + roll it back. + + If committing and the commit fails, then roll back and raise an + exception. + """ + raise NotImplementedError # pragma: no cover + + def _set_origin(self, origin): + """Set the origin. + + This method is called when reading a possibly relativized + source, and an origin setting operation occurs (e.g. $ORIGIN + in a zone file). + """ + raise NotImplementedError # pragma: no cover + + def _iterate_rdatasets(self): + """Return an iterator that yields (name, rdataset) tuples.""" + raise NotImplementedError # pragma: no cover + + def _iterate_names(self): + """Return an iterator that yields a name.""" + raise NotImplementedError # pragma: no cover + + def _get_node(self, name): + """Return the node at *name*, if any. + + Returns a node or ``None``. + """ + raise NotImplementedError # pragma: no cover + + # + # Low-level API with a default implementation, in case a subclass needs + # to override. + # + + def _origin_information(self): + # This is only used by _add() + return self.manager.origin_information() diff --git a/venv/lib/python3.12/site-packages/dns/tsig.py b/venv/lib/python3.12/site-packages/dns/tsig.py new file mode 100644 index 0000000..333f9aa --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/tsig.py @@ -0,0 +1,359 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2001-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS TSIG support.""" + +import base64 +import hashlib +import hmac +import struct + +import dns.exception +import dns.name +import dns.rcode +import dns.rdataclass +import dns.rdatatype + + +class BadTime(dns.exception.DNSException): + """The current time is not within the TSIG's validity time.""" + + +class BadSignature(dns.exception.DNSException): + """The TSIG signature fails to verify.""" + + +class BadKey(dns.exception.DNSException): + """The TSIG record owner name does not match the key.""" + + +class BadAlgorithm(dns.exception.DNSException): + """The TSIG algorithm does not match the key.""" + + +class PeerError(dns.exception.DNSException): + """Base class for all TSIG errors generated by the remote peer""" + + +class PeerBadKey(PeerError): + """The peer didn't know the key we used""" + + +class PeerBadSignature(PeerError): + """The peer didn't like the signature we sent""" + + +class PeerBadTime(PeerError): + """The peer didn't like the time we sent""" + + +class PeerBadTruncation(PeerError): + """The peer didn't like amount of truncation in the TSIG we sent""" + + +# TSIG Algorithms + +HMAC_MD5 = dns.name.from_text("HMAC-MD5.SIG-ALG.REG.INT") +HMAC_SHA1 = dns.name.from_text("hmac-sha1") +HMAC_SHA224 = dns.name.from_text("hmac-sha224") +HMAC_SHA256 = dns.name.from_text("hmac-sha256") +HMAC_SHA256_128 = dns.name.from_text("hmac-sha256-128") +HMAC_SHA384 = dns.name.from_text("hmac-sha384") +HMAC_SHA384_192 = dns.name.from_text("hmac-sha384-192") +HMAC_SHA512 = dns.name.from_text("hmac-sha512") +HMAC_SHA512_256 = dns.name.from_text("hmac-sha512-256") +GSS_TSIG = dns.name.from_text("gss-tsig") + +default_algorithm = HMAC_SHA256 + +mac_sizes = { + HMAC_SHA1: 20, + HMAC_SHA224: 28, + HMAC_SHA256: 32, + HMAC_SHA256_128: 16, + HMAC_SHA384: 48, + HMAC_SHA384_192: 24, + HMAC_SHA512: 64, + HMAC_SHA512_256: 32, + HMAC_MD5: 16, + GSS_TSIG: 128, # This is what we assume to be the worst case! +} + + +class GSSTSig: + """ + GSS-TSIG TSIG implementation. This uses the GSS-API context established + in the TKEY message handshake to sign messages using GSS-API message + integrity codes, per the RFC. + + In order to avoid a direct GSSAPI dependency, the keyring holds a ref + to the GSSAPI object required, rather than the key itself. + """ + + def __init__(self, gssapi_context): + self.gssapi_context = gssapi_context + self.data = b"" + self.name = "gss-tsig" + + def update(self, data): + self.data += data + + def sign(self): + # defer to the GSSAPI function to sign + return self.gssapi_context.get_signature(self.data) + + def verify(self, expected): + try: + # defer to the GSSAPI function to verify + return self.gssapi_context.verify_signature(self.data, expected) + except Exception: + # note the usage of a bare exception + raise BadSignature + + +class GSSTSigAdapter: + def __init__(self, keyring): + self.keyring = keyring + + def __call__(self, message, keyname): + if keyname in self.keyring: + key = self.keyring[keyname] + if isinstance(key, Key) and key.algorithm == GSS_TSIG: + if message: + GSSTSigAdapter.parse_tkey_and_step(key, message, keyname) + return key + else: + return None + + @classmethod + def parse_tkey_and_step(cls, key, message, keyname): + # if the message is a TKEY type, absorb the key material + # into the context using step(); this is used to allow the + # client to complete the GSSAPI negotiation before attempting + # to verify the signed response to a TKEY message exchange + try: + rrset = message.find_rrset( + message.answer, keyname, dns.rdataclass.ANY, dns.rdatatype.TKEY + ) + if rrset: + token = rrset[0].key + gssapi_context = key.secret + return gssapi_context.step(token) + except KeyError: + pass + + +class HMACTSig: + """ + HMAC TSIG implementation. This uses the HMAC python module to handle the + sign/verify operations. + """ + + _hashes = { + HMAC_SHA1: hashlib.sha1, + HMAC_SHA224: hashlib.sha224, + HMAC_SHA256: hashlib.sha256, + HMAC_SHA256_128: (hashlib.sha256, 128), + HMAC_SHA384: hashlib.sha384, + HMAC_SHA384_192: (hashlib.sha384, 192), + HMAC_SHA512: hashlib.sha512, + HMAC_SHA512_256: (hashlib.sha512, 256), + HMAC_MD5: hashlib.md5, + } + + def __init__(self, key, algorithm): + try: + hashinfo = self._hashes[algorithm] + except KeyError: + raise NotImplementedError(f"TSIG algorithm {algorithm} is not supported") + + # create the HMAC context + if isinstance(hashinfo, tuple): + self.hmac_context = hmac.new(key, digestmod=hashinfo[0]) + self.size = hashinfo[1] + else: + self.hmac_context = hmac.new(key, digestmod=hashinfo) + self.size = None + self.name = self.hmac_context.name + if self.size: + self.name += f"-{self.size}" + + def update(self, data): + return self.hmac_context.update(data) + + def sign(self): + # defer to the HMAC digest() function for that digestmod + digest = self.hmac_context.digest() + if self.size: + digest = digest[: (self.size // 8)] + return digest + + def verify(self, expected): + # re-digest and compare the results + mac = self.sign() + if not hmac.compare_digest(mac, expected): + raise BadSignature + + +def _digest(wire, key, rdata, time=None, request_mac=None, ctx=None, multi=None): + """Return a context containing the TSIG rdata for the input parameters + @rtype: dns.tsig.HMACTSig or dns.tsig.GSSTSig object + @raises ValueError: I{other_data} is too long + @raises NotImplementedError: I{algorithm} is not supported + """ + + first = not (ctx and multi) + if first: + ctx = get_context(key) + if request_mac: + ctx.update(struct.pack("!H", len(request_mac))) + ctx.update(request_mac) + assert ctx is not None # for type checkers + ctx.update(struct.pack("!H", rdata.original_id)) + ctx.update(wire[2:]) + if first: + ctx.update(key.name.to_digestable()) + ctx.update(struct.pack("!H", dns.rdataclass.ANY)) + ctx.update(struct.pack("!I", 0)) + if time is None: + time = rdata.time_signed + upper_time = (time >> 32) & 0xFFFF + lower_time = time & 0xFFFFFFFF + time_encoded = struct.pack("!HIH", upper_time, lower_time, rdata.fudge) + other_len = len(rdata.other) + if other_len > 65535: + raise ValueError("TSIG Other Data is > 65535 bytes") + if first: + ctx.update(key.algorithm.to_digestable() + time_encoded) + ctx.update(struct.pack("!HH", rdata.error, other_len) + rdata.other) + else: + ctx.update(time_encoded) + return ctx + + +def _maybe_start_digest(key, mac, multi): + """If this is the first message in a multi-message sequence, + start a new context. + @rtype: dns.tsig.HMACTSig or dns.tsig.GSSTSig object + """ + if multi: + ctx = get_context(key) + ctx.update(struct.pack("!H", len(mac))) + ctx.update(mac) + return ctx + else: + return None + + +def sign(wire, key, rdata, time=None, request_mac=None, ctx=None, multi=False): + """Return a (tsig_rdata, mac, ctx) tuple containing the HMAC TSIG rdata + for the input parameters, the HMAC MAC calculated by applying the + TSIG signature algorithm, and the TSIG digest context. + @rtype: (string, dns.tsig.HMACTSig or dns.tsig.GSSTSig object) + @raises ValueError: I{other_data} is too long + @raises NotImplementedError: I{algorithm} is not supported + """ + + ctx = _digest(wire, key, rdata, time, request_mac, ctx, multi) + mac = ctx.sign() + tsig = rdata.replace(time_signed=time, mac=mac) + + return (tsig, _maybe_start_digest(key, mac, multi)) + + +def validate( + wire, key, owner, rdata, now, request_mac, tsig_start, ctx=None, multi=False +): + """Validate the specified TSIG rdata against the other input parameters. + + @raises FormError: The TSIG is badly formed. + @raises BadTime: There is too much time skew between the client and the + server. + @raises BadSignature: The TSIG signature did not validate + @rtype: dns.tsig.HMACTSig or dns.tsig.GSSTSig object""" + + (adcount,) = struct.unpack("!H", wire[10:12]) + if adcount == 0: + raise dns.exception.FormError + adcount -= 1 + new_wire = wire[0:10] + struct.pack("!H", adcount) + wire[12:tsig_start] + if rdata.error != 0: + if rdata.error == dns.rcode.BADSIG: + raise PeerBadSignature + elif rdata.error == dns.rcode.BADKEY: + raise PeerBadKey + elif rdata.error == dns.rcode.BADTIME: + raise PeerBadTime + elif rdata.error == dns.rcode.BADTRUNC: + raise PeerBadTruncation + else: + raise PeerError(f"unknown TSIG error code {rdata.error}") + if abs(rdata.time_signed - now) > rdata.fudge: + raise BadTime + if key.name != owner: + raise BadKey + if key.algorithm != rdata.algorithm: + raise BadAlgorithm + ctx = _digest(new_wire, key, rdata, None, request_mac, ctx, multi) + ctx.verify(rdata.mac) + return _maybe_start_digest(key, rdata.mac, multi) + + +def get_context(key): + """Returns an HMAC context for the specified key. + + @rtype: HMAC context + @raises NotImplementedError: I{algorithm} is not supported + """ + + if key.algorithm == GSS_TSIG: + return GSSTSig(key.secret) + else: + return HMACTSig(key.secret, key.algorithm) + + +class Key: + def __init__( + self, + name: dns.name.Name | str, + secret: bytes | str, + algorithm: dns.name.Name | str = default_algorithm, + ): + if isinstance(name, str): + name = dns.name.from_text(name) + self.name = name + if isinstance(secret, str): + secret = base64.decodebytes(secret.encode()) + self.secret = secret + if isinstance(algorithm, str): + algorithm = dns.name.from_text(algorithm) + self.algorithm = algorithm + + def __eq__(self, other): + return ( + isinstance(other, Key) + and self.name == other.name + and self.secret == other.secret + and self.algorithm == other.algorithm + ) + + def __repr__(self): + r = f" Dict[dns.name.Name, Any]: + """Convert a dictionary containing (textual DNS name, base64 secret) + pairs into a binary keyring which has (dns.name.Name, bytes) pairs, or + a dictionary containing (textual DNS name, (algorithm, base64 secret)) + pairs into a binary keyring which has (dns.name.Name, dns.tsig.Key) pairs. + @rtype: dict""" + + keyring: Dict[dns.name.Name, Any] = {} + for name, value in textring.items(): + kname = dns.name.from_text(name) + if isinstance(value, str): + keyring[kname] = dns.tsig.Key(kname, value).secret + else: + (algorithm, secret) = value + keyring[kname] = dns.tsig.Key(kname, secret, algorithm) + return keyring + + +def to_text(keyring: Dict[dns.name.Name, Any]) -> Dict[str, Any]: + """Convert a dictionary containing (dns.name.Name, dns.tsig.Key) pairs + into a text keyring which has (textual DNS name, (textual algorithm, + base64 secret)) pairs, or a dictionary containing (dns.name.Name, bytes) + pairs into a text keyring which has (textual DNS name, base64 secret) pairs. + @rtype: dict""" + + textring = {} + + def b64encode(secret): + return base64.encodebytes(secret).decode().rstrip() + + for name, key in keyring.items(): + tname = name.to_text() + if isinstance(key, bytes): + textring[tname] = b64encode(key) + else: + if isinstance(key.secret, bytes): + text_secret = b64encode(key.secret) + else: + text_secret = str(key.secret) + + textring[tname] = (key.algorithm.to_text(), text_secret) + return textring diff --git a/venv/lib/python3.12/site-packages/dns/ttl.py b/venv/lib/python3.12/site-packages/dns/ttl.py new file mode 100644 index 0000000..16289cd --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/ttl.py @@ -0,0 +1,90 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS TTL conversion.""" + +import dns.exception + +# Technically TTLs are supposed to be between 0 and 2**31 - 1, with values +# greater than that interpreted as 0, but we do not impose this policy here +# as values > 2**31 - 1 occur in real world data. +# +# We leave it to applications to impose tighter bounds if desired. +MAX_TTL = 2**32 - 1 + + +class BadTTL(dns.exception.SyntaxError): + """DNS TTL value is not well-formed.""" + + +def from_text(text: str) -> int: + """Convert the text form of a TTL to an integer. + + The BIND 8 units syntax for TTLs (e.g. '1w6d4h3m10s') is supported. + + *text*, a ``str``, the textual TTL. + + Raises ``dns.ttl.BadTTL`` if the TTL is not well-formed. + + Returns an ``int``. + """ + + if text.isdigit(): + total = int(text) + elif len(text) == 0: + raise BadTTL + else: + total = 0 + current = 0 + need_digit = True + for c in text: + if c.isdigit(): + current *= 10 + current += int(c) + need_digit = False + else: + if need_digit: + raise BadTTL + c = c.lower() + if c == "w": + total += current * 604800 + elif c == "d": + total += current * 86400 + elif c == "h": + total += current * 3600 + elif c == "m": + total += current * 60 + elif c == "s": + total += current + else: + raise BadTTL(f"unknown unit '{c}'") + current = 0 + need_digit = True + if not current == 0: + raise BadTTL("trailing integer") + if total < 0 or total > MAX_TTL: + raise BadTTL("TTL should be between 0 and 2**32 - 1 (inclusive)") + return total + + +def make(value: int | str) -> int: + if isinstance(value, int): + return value + elif isinstance(value, str): + return from_text(value) + else: + raise ValueError("cannot convert value to TTL") diff --git a/venv/lib/python3.12/site-packages/dns/update.py b/venv/lib/python3.12/site-packages/dns/update.py new file mode 100644 index 0000000..0e4aee4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/update.py @@ -0,0 +1,389 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS Dynamic Update Support""" + +from typing import Any, List + +import dns.enum +import dns.exception +import dns.message +import dns.name +import dns.opcode +import dns.rdata +import dns.rdataclass +import dns.rdataset +import dns.rdatatype +import dns.rrset +import dns.tsig + + +class UpdateSection(dns.enum.IntEnum): + """Update sections""" + + ZONE = 0 + PREREQ = 1 + UPDATE = 2 + ADDITIONAL = 3 + + @classmethod + def _maximum(cls): + return 3 + + +class UpdateMessage(dns.message.Message): # lgtm[py/missing-equals] + # ignore the mypy error here as we mean to use a different enum + _section_enum = UpdateSection # type: ignore + + def __init__( + self, + zone: dns.name.Name | str | None = None, + rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, + keyring: Any | None = None, + keyname: dns.name.Name | None = None, + keyalgorithm: dns.name.Name | str = dns.tsig.default_algorithm, + id: int | None = None, + ): + """Initialize a new DNS Update object. + + See the documentation of the Message class for a complete + description of the keyring dictionary. + + *zone*, a ``dns.name.Name``, ``str``, or ``None``, the zone + which is being updated. ``None`` should only be used by dnspython's + message constructors, as a zone is required for the convenience + methods like ``add()``, ``replace()``, etc. + + *rdclass*, an ``int`` or ``str``, the class of the zone. + + The *keyring*, *keyname*, and *keyalgorithm* parameters are passed to + ``use_tsig()``; see its documentation for details. + """ + super().__init__(id=id) + self.flags |= dns.opcode.to_flags(dns.opcode.UPDATE) + if isinstance(zone, str): + zone = dns.name.from_text(zone) + self.origin = zone + rdclass = dns.rdataclass.RdataClass.make(rdclass) + self.zone_rdclass = rdclass + if self.origin: + self.find_rrset( + self.zone, + self.origin, + rdclass, + dns.rdatatype.SOA, + create=True, + force_unique=True, + ) + if keyring is not None: + self.use_tsig(keyring, keyname, algorithm=keyalgorithm) + + @property + def zone(self) -> List[dns.rrset.RRset]: + """The zone section.""" + return self.sections[0] + + @zone.setter + def zone(self, v): + self.sections[0] = v + + @property + def prerequisite(self) -> List[dns.rrset.RRset]: + """The prerequisite section.""" + return self.sections[1] + + @prerequisite.setter + def prerequisite(self, v): + self.sections[1] = v + + @property + def update(self) -> List[dns.rrset.RRset]: + """The update section.""" + return self.sections[2] + + @update.setter + def update(self, v): + self.sections[2] = v + + def _add_rr(self, name, ttl, rd, deleting=None, section=None): + """Add a single RR to the update section.""" + + if section is None: + section = self.update + covers = rd.covers() + rrset = self.find_rrset( + section, name, self.zone_rdclass, rd.rdtype, covers, deleting, True, True + ) + rrset.add(rd, ttl) + + def _add(self, replace, section, name, *args): + """Add records. + + *replace* is the replacement mode. If ``False``, + RRs are added to an existing RRset; if ``True``, the RRset + is replaced with the specified contents. The second + argument is the section to add to. The third argument + is always a name. The other arguments can be: + + - rdataset... + + - ttl, rdata... + + - ttl, rdtype, string... + """ + + if isinstance(name, str): + name = dns.name.from_text(name, None) + if isinstance(args[0], dns.rdataset.Rdataset): + for rds in args: + if replace: + self.delete(name, rds.rdtype) + for rd in rds: + self._add_rr(name, rds.ttl, rd, section=section) + else: + args = list(args) + ttl = int(args.pop(0)) + if isinstance(args[0], dns.rdata.Rdata): + if replace: + self.delete(name, args[0].rdtype) + for rd in args: + self._add_rr(name, ttl, rd, section=section) + else: + rdtype = dns.rdatatype.RdataType.make(args.pop(0)) + if replace: + self.delete(name, rdtype) + for s in args: + rd = dns.rdata.from_text(self.zone_rdclass, rdtype, s, self.origin) + self._add_rr(name, ttl, rd, section=section) + + def add(self, name: dns.name.Name | str, *args: Any) -> None: + """Add records. + + The first argument is always a name. The other + arguments can be: + + - rdataset... + + - ttl, rdata... + + - ttl, rdtype, string... + """ + + self._add(False, self.update, name, *args) + + def delete(self, name: dns.name.Name | str, *args: Any) -> None: + """Delete records. + + The first argument is always a name. The other + arguments can be: + + - *empty* + + - rdataset... + + - rdata... + + - rdtype, [string...] + """ + + if isinstance(name, str): + name = dns.name.from_text(name, None) + if len(args) == 0: + self.find_rrset( + self.update, + name, + dns.rdataclass.ANY, + dns.rdatatype.ANY, + dns.rdatatype.NONE, + dns.rdataclass.ANY, + True, + True, + ) + elif isinstance(args[0], dns.rdataset.Rdataset): + for rds in args: + for rd in rds: + self._add_rr(name, 0, rd, dns.rdataclass.NONE) + else: + largs = list(args) + if isinstance(largs[0], dns.rdata.Rdata): + for rd in largs: + self._add_rr(name, 0, rd, dns.rdataclass.NONE) + else: + rdtype = dns.rdatatype.RdataType.make(largs.pop(0)) + if len(largs) == 0: + self.find_rrset( + self.update, + name, + self.zone_rdclass, + rdtype, + dns.rdatatype.NONE, + dns.rdataclass.ANY, + True, + True, + ) + else: + for s in largs: + rd = dns.rdata.from_text( + self.zone_rdclass, + rdtype, + s, # type: ignore[arg-type] + self.origin, + ) + self._add_rr(name, 0, rd, dns.rdataclass.NONE) + + def replace(self, name: dns.name.Name | str, *args: Any) -> None: + """Replace records. + + The first argument is always a name. The other + arguments can be: + + - rdataset... + + - ttl, rdata... + + - ttl, rdtype, string... + + Note that if you want to replace the entire node, you should do + a delete of the name followed by one or more calls to add. + """ + + self._add(True, self.update, name, *args) + + def present(self, name: dns.name.Name | str, *args: Any) -> None: + """Require that an owner name (and optionally an rdata type, + or specific rdataset) exists as a prerequisite to the + execution of the update. + + The first argument is always a name. + The other arguments can be: + + - rdataset... + + - rdata... + + - rdtype, string... + """ + + if isinstance(name, str): + name = dns.name.from_text(name, None) + if len(args) == 0: + self.find_rrset( + self.prerequisite, + name, + dns.rdataclass.ANY, + dns.rdatatype.ANY, + dns.rdatatype.NONE, + None, + True, + True, + ) + elif ( + isinstance(args[0], dns.rdataset.Rdataset) + or isinstance(args[0], dns.rdata.Rdata) + or len(args) > 1 + ): + if not isinstance(args[0], dns.rdataset.Rdataset): + # Add a 0 TTL + largs = list(args) + largs.insert(0, 0) # type: ignore[arg-type] + self._add(False, self.prerequisite, name, *largs) + else: + self._add(False, self.prerequisite, name, *args) + else: + rdtype = dns.rdatatype.RdataType.make(args[0]) + self.find_rrset( + self.prerequisite, + name, + dns.rdataclass.ANY, + rdtype, + dns.rdatatype.NONE, + None, + True, + True, + ) + + def absent( + self, + name: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str | None = None, + ) -> None: + """Require that an owner name (and optionally an rdata type) does + not exist as a prerequisite to the execution of the update.""" + + if isinstance(name, str): + name = dns.name.from_text(name, None) + if rdtype is None: + self.find_rrset( + self.prerequisite, + name, + dns.rdataclass.NONE, + dns.rdatatype.ANY, + dns.rdatatype.NONE, + None, + True, + True, + ) + else: + rdtype = dns.rdatatype.RdataType.make(rdtype) + self.find_rrset( + self.prerequisite, + name, + dns.rdataclass.NONE, + rdtype, + dns.rdatatype.NONE, + None, + True, + True, + ) + + def _get_one_rr_per_rrset(self, value): + # Updates are always one_rr_per_rrset + return True + + def _parse_rr_header(self, section, name, rdclass, rdtype): # pyright: ignore + deleting = None + empty = False + if section == UpdateSection.ZONE: + if ( + dns.rdataclass.is_metaclass(rdclass) + or rdtype != dns.rdatatype.SOA + or self.zone + ): + raise dns.exception.FormError + else: + if not self.zone: + raise dns.exception.FormError + if rdclass in (dns.rdataclass.ANY, dns.rdataclass.NONE): + deleting = rdclass + rdclass = self.zone[0].rdclass + empty = ( + deleting == dns.rdataclass.ANY or section == UpdateSection.PREREQ + ) + return (rdclass, rdtype, deleting, empty) + + +# backwards compatibility +Update = UpdateMessage + +### BEGIN generated UpdateSection constants + +ZONE = UpdateSection.ZONE +PREREQ = UpdateSection.PREREQ +UPDATE = UpdateSection.UPDATE +ADDITIONAL = UpdateSection.ADDITIONAL + +### END generated UpdateSection constants diff --git a/venv/lib/python3.12/site-packages/dns/version.py b/venv/lib/python3.12/site-packages/dns/version.py new file mode 100644 index 0000000..e11dd29 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/version.py @@ -0,0 +1,42 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""dnspython release version information.""" + +#: MAJOR +MAJOR = 2 +#: MINOR +MINOR = 8 +#: MICRO +MICRO = 0 +#: RELEASELEVEL +RELEASELEVEL = 0x0F +#: SERIAL +SERIAL = 0 + +if RELEASELEVEL == 0x0F: # pragma: no cover lgtm[py/unreachable-statement] + #: version + version = f"{MAJOR}.{MINOR}.{MICRO}" # lgtm[py/unreachable-statement] +elif RELEASELEVEL == 0x00: # pragma: no cover lgtm[py/unreachable-statement] + version = f"{MAJOR}.{MINOR}.{MICRO}dev{SERIAL}" # lgtm[py/unreachable-statement] +elif RELEASELEVEL == 0x0C: # pragma: no cover lgtm[py/unreachable-statement] + version = f"{MAJOR}.{MINOR}.{MICRO}rc{SERIAL}" # lgtm[py/unreachable-statement] +else: # pragma: no cover lgtm[py/unreachable-statement] + version = f"{MAJOR}.{MINOR}.{MICRO}{RELEASELEVEL:x}{SERIAL}" # lgtm[py/unreachable-statement] + +#: hexversion +hexversion = MAJOR << 24 | MINOR << 16 | MICRO << 8 | RELEASELEVEL << 4 | SERIAL diff --git a/venv/lib/python3.12/site-packages/dns/versioned.py b/venv/lib/python3.12/site-packages/dns/versioned.py new file mode 100644 index 0000000..3644711 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/versioned.py @@ -0,0 +1,320 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +"""DNS Versioned Zones.""" + +import collections +import threading +from typing import Callable, Deque, Set, cast + +import dns.exception +import dns.name +import dns.node +import dns.rdataclass +import dns.rdataset +import dns.rdatatype +import dns.rdtypes.ANY.SOA +import dns.zone + + +class UseTransaction(dns.exception.DNSException): + """To alter a versioned zone, use a transaction.""" + + +# Backwards compatibility +Node = dns.zone.VersionedNode +ImmutableNode = dns.zone.ImmutableVersionedNode +Version = dns.zone.Version +WritableVersion = dns.zone.WritableVersion +ImmutableVersion = dns.zone.ImmutableVersion +Transaction = dns.zone.Transaction + + +class Zone(dns.zone.Zone): # lgtm[py/missing-equals] + __slots__ = [ + "_versions", + "_versions_lock", + "_write_txn", + "_write_waiters", + "_write_event", + "_pruning_policy", + "_readers", + ] + + node_factory: Callable[[], dns.node.Node] = Node + + def __init__( + self, + origin: dns.name.Name | str | None, + rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, + relativize: bool = True, + pruning_policy: Callable[["Zone", Version], bool | None] | None = None, + ): + """Initialize a versioned zone object. + + *origin* is the origin of the zone. It may be a ``dns.name.Name``, + a ``str``, or ``None``. If ``None``, then the zone's origin will + be set by the first ``$ORIGIN`` line in a zone file. + + *rdclass*, an ``int``, the zone's rdata class; the default is class IN. + + *relativize*, a ``bool``, determine's whether domain names are + relativized to the zone's origin. The default is ``True``. + + *pruning policy*, a function taking a ``Zone`` and a ``Version`` and returning + a ``bool``, or ``None``. Should the version be pruned? If ``None``, + the default policy, which retains one version is used. + """ + super().__init__(origin, rdclass, relativize) + self._versions: Deque[Version] = collections.deque() + self._version_lock = threading.Lock() + if pruning_policy is None: + self._pruning_policy = self._default_pruning_policy + else: + self._pruning_policy = pruning_policy + self._write_txn: Transaction | None = None + self._write_event: threading.Event | None = None + self._write_waiters: Deque[threading.Event] = collections.deque() + self._readers: Set[Transaction] = set() + self._commit_version_unlocked( + None, WritableVersion(self, replacement=True), origin + ) + + def reader( + self, id: int | None = None, serial: int | None = None + ) -> Transaction: # pylint: disable=arguments-differ + if id is not None and serial is not None: + raise ValueError("cannot specify both id and serial") + with self._version_lock: + if id is not None: + version = None + for v in reversed(self._versions): + if v.id == id: + version = v + break + if version is None: + raise KeyError("version not found") + elif serial is not None: + if self.relativize: + oname = dns.name.empty + else: + assert self.origin is not None + oname = self.origin + version = None + for v in reversed(self._versions): + n = v.nodes.get(oname) + if n: + rds = n.get_rdataset(self.rdclass, dns.rdatatype.SOA) + if rds is None: + continue + soa = cast(dns.rdtypes.ANY.SOA.SOA, rds[0]) + if rds and soa.serial == serial: + version = v + break + if version is None: + raise KeyError("serial not found") + else: + version = self._versions[-1] + txn = Transaction(self, False, version) + self._readers.add(txn) + return txn + + def writer(self, replacement: bool = False) -> Transaction: + event = None + while True: + with self._version_lock: + # Checking event == self._write_event ensures that either + # no one was waiting before we got lucky and found no write + # txn, or we were the one who was waiting and got woken up. + # This prevents "taking cuts" when creating a write txn. + if self._write_txn is None and event == self._write_event: + # Creating the transaction defers version setup + # (i.e. copying the nodes dictionary) until we + # give up the lock, so that we hold the lock as + # short a time as possible. This is why we call + # _setup_version() below. + self._write_txn = Transaction( + self, replacement, make_immutable=True + ) + # give up our exclusive right to make a Transaction + self._write_event = None + break + # Someone else is writing already, so we will have to + # wait, but we want to do the actual wait outside the + # lock. + event = threading.Event() + self._write_waiters.append(event) + # wait (note we gave up the lock!) + # + # We only wake one sleeper at a time, so it's important + # that no event waiter can exit this method (e.g. via + # cancellation) without returning a transaction or waking + # someone else up. + # + # This is not a problem with Threading module threads as + # they cannot be canceled, but could be an issue with trio + # tasks when we do the async version of writer(). + # I.e. we'd need to do something like: + # + # try: + # event.wait() + # except trio.Cancelled: + # with self._version_lock: + # self._maybe_wakeup_one_waiter_unlocked() + # raise + # + event.wait() + # Do the deferred version setup. + self._write_txn._setup_version() + return self._write_txn + + def _maybe_wakeup_one_waiter_unlocked(self): + if len(self._write_waiters) > 0: + self._write_event = self._write_waiters.popleft() + self._write_event.set() + + # pylint: disable=unused-argument + def _default_pruning_policy(self, zone, version): + return True + + # pylint: enable=unused-argument + + def _prune_versions_unlocked(self): + assert len(self._versions) > 0 + # Don't ever prune a version greater than or equal to one that + # a reader has open. This pins versions in memory while the + # reader is open, and importantly lets the reader open a txn on + # a successor version (e.g. if generating an IXFR). + # + # Note our definition of least_kept also ensures we do not try to + # delete the greatest version. + if len(self._readers) > 0: + least_kept = min(txn.version.id for txn in self._readers) # pyright: ignore + else: + least_kept = self._versions[-1].id + while self._versions[0].id < least_kept and self._pruning_policy( + self, self._versions[0] + ): + self._versions.popleft() + + def set_max_versions(self, max_versions: int | None) -> None: + """Set a pruning policy that retains up to the specified number + of versions + """ + if max_versions is not None and max_versions < 1: + raise ValueError("max versions must be at least 1") + if max_versions is None: + # pylint: disable=unused-argument + def policy(zone, _): # pyright: ignore + return False + + else: + + def policy(zone, _): + return len(zone._versions) > max_versions + + self.set_pruning_policy(policy) + + def set_pruning_policy( + self, policy: Callable[["Zone", Version], bool | None] | None + ) -> None: + """Set the pruning policy for the zone. + + The *policy* function takes a `Version` and returns `True` if + the version should be pruned, and `False` otherwise. `None` + may also be specified for policy, in which case the default policy + is used. + + Pruning checking proceeds from the least version and the first + time the function returns `False`, the checking stops. I.e. the + retained versions are always a consecutive sequence. + """ + if policy is None: + policy = self._default_pruning_policy + with self._version_lock: + self._pruning_policy = policy + self._prune_versions_unlocked() + + def _end_read(self, txn): + with self._version_lock: + self._readers.remove(txn) + self._prune_versions_unlocked() + + def _end_write_unlocked(self, txn): + assert self._write_txn == txn + self._write_txn = None + self._maybe_wakeup_one_waiter_unlocked() + + def _end_write(self, txn): + with self._version_lock: + self._end_write_unlocked(txn) + + def _commit_version_unlocked(self, txn, version, origin): + self._versions.append(version) + self._prune_versions_unlocked() + self.nodes = version.nodes + if self.origin is None: + self.origin = origin + # txn can be None in __init__ when we make the empty version. + if txn is not None: + self._end_write_unlocked(txn) + + def _commit_version(self, txn, version, origin): + with self._version_lock: + self._commit_version_unlocked(txn, version, origin) + + def _get_next_version_id(self): + if len(self._versions) > 0: + id = self._versions[-1].id + 1 + else: + id = 1 + return id + + def find_node( + self, name: dns.name.Name | str, create: bool = False + ) -> dns.node.Node: + if create: + raise UseTransaction + return super().find_node(name) + + def delete_node(self, name: dns.name.Name | str) -> None: + raise UseTransaction + + def find_rdataset( + self, + name: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str, + covers: dns.rdatatype.RdataType | str = dns.rdatatype.NONE, + create: bool = False, + ) -> dns.rdataset.Rdataset: + if create: + raise UseTransaction + rdataset = super().find_rdataset(name, rdtype, covers) + return dns.rdataset.ImmutableRdataset(rdataset) + + def get_rdataset( + self, + name: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str, + covers: dns.rdatatype.RdataType | str = dns.rdatatype.NONE, + create: bool = False, + ) -> dns.rdataset.Rdataset | None: + if create: + raise UseTransaction + rdataset = super().get_rdataset(name, rdtype, covers) + if rdataset is not None: + return dns.rdataset.ImmutableRdataset(rdataset) + else: + return None + + def delete_rdataset( + self, + name: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str, + covers: dns.rdatatype.RdataType | str = dns.rdatatype.NONE, + ) -> None: + raise UseTransaction + + def replace_rdataset( + self, name: dns.name.Name | str, replacement: dns.rdataset.Rdataset + ) -> None: + raise UseTransaction diff --git a/venv/lib/python3.12/site-packages/dns/win32util.py b/venv/lib/python3.12/site-packages/dns/win32util.py new file mode 100644 index 0000000..2d77b4c --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/win32util.py @@ -0,0 +1,438 @@ +import sys + +import dns._features + +# pylint: disable=W0612,W0613,C0301 + +if sys.platform == "win32": + import ctypes + import ctypes.wintypes as wintypes + import winreg # pylint: disable=import-error + from enum import IntEnum + + import dns.name + + # Keep pylint quiet on non-windows. + try: + _ = WindowsError # pylint: disable=used-before-assignment + except NameError: + WindowsError = Exception + + class ConfigMethod(IntEnum): + Registry = 1 + WMI = 2 + Win32 = 3 + + class DnsInfo: + def __init__(self): + self.domain = None + self.nameservers = [] + self.search = [] + + _config_method = ConfigMethod.Registry + + if dns._features.have("wmi"): + import threading + + import pythoncom # pylint: disable=import-error + import wmi # pylint: disable=import-error + + # Prefer WMI by default if wmi is installed. + _config_method = ConfigMethod.WMI + + class _WMIGetter(threading.Thread): + # pylint: disable=possibly-used-before-assignment + def __init__(self): + super().__init__() + self.info = DnsInfo() + + def run(self): + pythoncom.CoInitialize() + try: + system = wmi.WMI() + for interface in system.Win32_NetworkAdapterConfiguration(): + if interface.IPEnabled and interface.DNSServerSearchOrder: + self.info.nameservers = list(interface.DNSServerSearchOrder) + if interface.DNSDomain: + self.info.domain = _config_domain(interface.DNSDomain) + if interface.DNSDomainSuffixSearchOrder: + self.info.search = [ + _config_domain(x) + for x in interface.DNSDomainSuffixSearchOrder + ] + break + finally: + pythoncom.CoUninitialize() + + def get(self): + # We always run in a separate thread to avoid any issues with + # the COM threading model. + self.start() + self.join() + return self.info + + else: + + class _WMIGetter: # type: ignore + pass + + def _config_domain(domain): + # Sometimes DHCP servers add a '.' prefix to the default domain, and + # Windows just stores such values in the registry (see #687). + # Check for this and fix it. + if domain.startswith("."): + domain = domain[1:] + return dns.name.from_text(domain) + + class _RegistryGetter: + def __init__(self): + self.info = DnsInfo() + + def _split(self, text): + # The windows registry has used both " " and "," as a delimiter, and while + # it is currently using "," in Windows 10 and later, updates can seemingly + # leave a space in too, e.g. "a, b". So we just convert all commas to + # spaces, and use split() in its default configuration, which splits on + # all whitespace and ignores empty strings. + return text.replace(",", " ").split() + + def _config_nameservers(self, nameservers): + for ns in self._split(nameservers): + if ns not in self.info.nameservers: + self.info.nameservers.append(ns) + + def _config_search(self, search): + for s in self._split(search): + s = _config_domain(s) + if s not in self.info.search: + self.info.search.append(s) + + def _config_fromkey(self, key, always_try_domain): + try: + servers, _ = winreg.QueryValueEx(key, "NameServer") + except WindowsError: + servers = None + if servers: + self._config_nameservers(servers) + if servers or always_try_domain: + try: + dom, _ = winreg.QueryValueEx(key, "Domain") + if dom: + self.info.domain = _config_domain(dom) + except WindowsError: + pass + else: + try: + servers, _ = winreg.QueryValueEx(key, "DhcpNameServer") + except WindowsError: + servers = None + if servers: + self._config_nameservers(servers) + try: + dom, _ = winreg.QueryValueEx(key, "DhcpDomain") + if dom: + self.info.domain = _config_domain(dom) + except WindowsError: + pass + try: + search, _ = winreg.QueryValueEx(key, "SearchList") + except WindowsError: + search = None + if search is None: + try: + search, _ = winreg.QueryValueEx(key, "DhcpSearchList") + except WindowsError: + search = None + if search: + self._config_search(search) + + def _is_nic_enabled(self, lm, guid): + # Look in the Windows Registry to determine whether the network + # interface corresponding to the given guid is enabled. + # + # (Code contributed by Paul Marks, thanks!) + # + try: + # This hard-coded location seems to be consistent, at least + # from Windows 2000 through Vista. + connection_key = winreg.OpenKey( + lm, + r"SYSTEM\CurrentControlSet\Control\Network" + r"\{4D36E972-E325-11CE-BFC1-08002BE10318}" + rf"\{guid}\Connection", + ) + + try: + # The PnpInstanceID points to a key inside Enum + (pnp_id, ttype) = winreg.QueryValueEx( + connection_key, "PnpInstanceID" + ) + + if ttype != winreg.REG_SZ: + raise ValueError # pragma: no cover + + device_key = winreg.OpenKey( + lm, rf"SYSTEM\CurrentControlSet\Enum\{pnp_id}" + ) + + try: + # Get ConfigFlags for this device + (flags, ttype) = winreg.QueryValueEx(device_key, "ConfigFlags") + + if ttype != winreg.REG_DWORD: + raise ValueError # pragma: no cover + + # Based on experimentation, bit 0x1 indicates that the + # device is disabled. + # + # XXXRTH I suspect we really want to & with 0x03 so + # that CONFIGFLAGS_REMOVED devices are also ignored, + # but we're shifting to WMI as ConfigFlags is not + # supposed to be used. + return not flags & 0x1 + + finally: + device_key.Close() + finally: + connection_key.Close() + except Exception: # pragma: no cover + return False + + def get(self): + """Extract resolver configuration from the Windows registry.""" + + lm = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + try: + tcp_params = winreg.OpenKey( + lm, r"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" + ) + try: + self._config_fromkey(tcp_params, True) + finally: + tcp_params.Close() + interfaces = winreg.OpenKey( + lm, + r"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces", + ) + try: + i = 0 + while True: + try: + guid = winreg.EnumKey(interfaces, i) + i += 1 + key = winreg.OpenKey(interfaces, guid) + try: + if not self._is_nic_enabled(lm, guid): + continue + self._config_fromkey(key, False) + finally: + key.Close() + except OSError: + break + finally: + interfaces.Close() + finally: + lm.Close() + return self.info + + class _Win32Getter(_RegistryGetter): + + def get(self): + """Get the attributes using the Windows API.""" + # Load the IP Helper library + # # https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses + IPHLPAPI = ctypes.WinDLL("Iphlpapi.dll") + + # Constants + AF_UNSPEC = 0 + ERROR_SUCCESS = 0 + GAA_FLAG_INCLUDE_PREFIX = 0x00000010 + AF_INET = 2 + AF_INET6 = 23 + IF_TYPE_SOFTWARE_LOOPBACK = 24 + + # Define necessary structures + class SOCKADDRV4(ctypes.Structure): + _fields_ = [ + ("sa_family", wintypes.USHORT), + ("sa_data", ctypes.c_ubyte * 14), + ] + + class SOCKADDRV6(ctypes.Structure): + _fields_ = [ + ("sa_family", wintypes.USHORT), + ("sa_data", ctypes.c_ubyte * 26), + ] + + class SOCKET_ADDRESS(ctypes.Structure): + _fields_ = [ + ("lpSockaddr", ctypes.POINTER(SOCKADDRV4)), + ("iSockaddrLength", wintypes.INT), + ] + + class IP_ADAPTER_DNS_SERVER_ADDRESS(ctypes.Structure): + pass # Forward declaration + + IP_ADAPTER_DNS_SERVER_ADDRESS._fields_ = [ + ("Length", wintypes.ULONG), + ("Reserved", wintypes.DWORD), + ("Next", ctypes.POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS)), + ("Address", SOCKET_ADDRESS), + ] + + class IF_LUID(ctypes.Structure): + _fields_ = [("Value", ctypes.c_ulonglong)] + + class NET_IF_NETWORK_GUID(ctypes.Structure): + _fields_ = [("Value", ctypes.c_ubyte * 16)] + + class IP_ADAPTER_PREFIX_XP(ctypes.Structure): + pass # Left undefined here for simplicity + + class IP_ADAPTER_GATEWAY_ADDRESS_LH(ctypes.Structure): + pass # Left undefined here for simplicity + + class IP_ADAPTER_DNS_SUFFIX(ctypes.Structure): + _fields_ = [ + ("String", ctypes.c_wchar * 256), + ("Next", ctypes.POINTER(ctypes.c_void_p)), + ] + + class IP_ADAPTER_UNICAST_ADDRESS_LH(ctypes.Structure): + pass # Left undefined here for simplicity + + class IP_ADAPTER_MULTICAST_ADDRESS_XP(ctypes.Structure): + pass # Left undefined here for simplicity + + class IP_ADAPTER_ANYCAST_ADDRESS_XP(ctypes.Structure): + pass # Left undefined here for simplicity + + class IP_ADAPTER_DNS_SERVER_ADDRESS_XP(ctypes.Structure): + pass # Left undefined here for simplicity + + class IP_ADAPTER_ADDRESSES(ctypes.Structure): + pass # Forward declaration + + IP_ADAPTER_ADDRESSES._fields_ = [ + ("Length", wintypes.ULONG), + ("IfIndex", wintypes.DWORD), + ("Next", ctypes.POINTER(IP_ADAPTER_ADDRESSES)), + ("AdapterName", ctypes.c_char_p), + ("FirstUnicastAddress", ctypes.POINTER(SOCKET_ADDRESS)), + ("FirstAnycastAddress", ctypes.POINTER(SOCKET_ADDRESS)), + ("FirstMulticastAddress", ctypes.POINTER(SOCKET_ADDRESS)), + ( + "FirstDnsServerAddress", + ctypes.POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS), + ), + ("DnsSuffix", wintypes.LPWSTR), + ("Description", wintypes.LPWSTR), + ("FriendlyName", wintypes.LPWSTR), + ("PhysicalAddress", ctypes.c_ubyte * 8), + ("PhysicalAddressLength", wintypes.ULONG), + ("Flags", wintypes.ULONG), + ("Mtu", wintypes.ULONG), + ("IfType", wintypes.ULONG), + ("OperStatus", ctypes.c_uint), + # Remaining fields removed for brevity + ] + + def format_ipv4(sockaddr_in): + return ".".join(map(str, sockaddr_in.sa_data[2:6])) + + def format_ipv6(sockaddr_in6): + # The sa_data is: + # + # USHORT sin6_port; + # ULONG sin6_flowinfo; + # IN6_ADDR sin6_addr; + # ULONG sin6_scope_id; + # + # which is 2 + 4 + 16 + 4 = 26 bytes, and we need the plus 6 below + # to be in the sin6_addr range. + parts = [ + sockaddr_in6.sa_data[i + 6] << 8 | sockaddr_in6.sa_data[i + 6 + 1] + for i in range(0, 16, 2) + ] + return ":".join(f"{part:04x}" for part in parts) + + buffer_size = ctypes.c_ulong(15000) + while True: + buffer = ctypes.create_string_buffer(buffer_size.value) + + ret_val = IPHLPAPI.GetAdaptersAddresses( + AF_UNSPEC, + GAA_FLAG_INCLUDE_PREFIX, + None, + buffer, + ctypes.byref(buffer_size), + ) + + if ret_val == ERROR_SUCCESS: + break + elif ret_val != 0x6F: # ERROR_BUFFER_OVERFLOW + print(f"Error retrieving adapter information: {ret_val}") + return + + adapter_addresses = ctypes.cast( + buffer, ctypes.POINTER(IP_ADAPTER_ADDRESSES) + ) + + current_adapter = adapter_addresses + while current_adapter: + + # Skip non-operational adapters. + oper_status = current_adapter.contents.OperStatus + if oper_status != 1: + current_adapter = current_adapter.contents.Next + continue + + # Exclude loopback adapters. + if current_adapter.contents.IfType == IF_TYPE_SOFTWARE_LOOPBACK: + current_adapter = current_adapter.contents.Next + continue + + # Get the domain from the DnsSuffix attribute. + dns_suffix = current_adapter.contents.DnsSuffix + if dns_suffix: + self.info.domain = dns.name.from_text(dns_suffix) + + current_dns_server = current_adapter.contents.FirstDnsServerAddress + while current_dns_server: + sockaddr = current_dns_server.contents.Address.lpSockaddr + sockaddr_family = sockaddr.contents.sa_family + + ip = None + if sockaddr_family == AF_INET: # IPv4 + ip = format_ipv4(sockaddr.contents) + elif sockaddr_family == AF_INET6: # IPv6 + sockaddr = ctypes.cast(sockaddr, ctypes.POINTER(SOCKADDRV6)) + ip = format_ipv6(sockaddr.contents) + + if ip: + if ip not in self.info.nameservers: + self.info.nameservers.append(ip) + + current_dns_server = current_dns_server.contents.Next + + current_adapter = current_adapter.contents.Next + + # Use the registry getter to get the search info, since it is set at the system level. + registry_getter = _RegistryGetter() + info = registry_getter.get() + self.info.search = info.search + return self.info + + def set_config_method(method: ConfigMethod) -> None: + global _config_method + _config_method = method + + def get_dns_info() -> DnsInfo: + """Extract resolver configuration.""" + if _config_method == ConfigMethod.Win32: + getter = _Win32Getter() + elif _config_method == ConfigMethod.WMI: + getter = _WMIGetter() + else: + getter = _RegistryGetter() + return getter.get() diff --git a/venv/lib/python3.12/site-packages/dns/wire.py b/venv/lib/python3.12/site-packages/dns/wire.py new file mode 100644 index 0000000..cd027fa --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/wire.py @@ -0,0 +1,98 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +import contextlib +import struct +from typing import Iterator, Optional, Tuple + +import dns.exception +import dns.name + + +class Parser: + """Helper class for parsing DNS wire format.""" + + def __init__(self, wire: bytes, current: int = 0): + """Initialize a Parser + + *wire*, a ``bytes`` contains the data to be parsed, and possibly other data. + Typically it is the whole message or a slice of it. + + *current*, an `int`, the offset within *wire* where parsing should begin. + """ + self.wire = wire + self.current = 0 + self.end = len(self.wire) + if current: + self.seek(current) + self.furthest = current + + def remaining(self) -> int: + return self.end - self.current + + def get_bytes(self, size: int) -> bytes: + assert size >= 0 + if size > self.remaining(): + raise dns.exception.FormError + output = self.wire[self.current : self.current + size] + self.current += size + self.furthest = max(self.furthest, self.current) + return output + + def get_counted_bytes(self, length_size: int = 1) -> bytes: + length = int.from_bytes(self.get_bytes(length_size), "big") + return self.get_bytes(length) + + def get_remaining(self) -> bytes: + return self.get_bytes(self.remaining()) + + def get_uint8(self) -> int: + return struct.unpack("!B", self.get_bytes(1))[0] + + def get_uint16(self) -> int: + return struct.unpack("!H", self.get_bytes(2))[0] + + def get_uint32(self) -> int: + return struct.unpack("!I", self.get_bytes(4))[0] + + def get_uint48(self) -> int: + return int.from_bytes(self.get_bytes(6), "big") + + def get_struct(self, format: str) -> Tuple: + return struct.unpack(format, self.get_bytes(struct.calcsize(format))) + + def get_name(self, origin: Optional["dns.name.Name"] = None) -> "dns.name.Name": + name = dns.name.from_wire_parser(self) + if origin: + name = name.relativize(origin) + return name + + def seek(self, where: int) -> None: + # Note that seeking to the end is OK! (If you try to read + # after such a seek, you'll get an exception as expected.) + if where < 0 or where > self.end: + raise dns.exception.FormError + self.current = where + + @contextlib.contextmanager + def restrict_to(self, size: int) -> Iterator: + assert size >= 0 + if size > self.remaining(): + raise dns.exception.FormError + saved_end = self.end + try: + self.end = self.current + size + yield + # We make this check here and not in the finally as we + # don't want to raise if we're already raising for some + # other reason. + if self.current != self.end: + raise dns.exception.FormError + finally: + self.end = saved_end + + @contextlib.contextmanager + def restore_furthest(self) -> Iterator: + try: + yield None + finally: + self.current = self.furthest diff --git a/venv/lib/python3.12/site-packages/dns/xfr.py b/venv/lib/python3.12/site-packages/dns/xfr.py new file mode 100644 index 0000000..219fdc8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/xfr.py @@ -0,0 +1,356 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2017 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +from typing import Any, List, Tuple, cast + +import dns.edns +import dns.exception +import dns.message +import dns.name +import dns.rcode +import dns.rdata +import dns.rdataset +import dns.rdatatype +import dns.rdtypes +import dns.rdtypes.ANY +import dns.rdtypes.ANY.SMIMEA +import dns.rdtypes.ANY.SOA +import dns.rdtypes.svcbbase +import dns.serial +import dns.transaction +import dns.tsig +import dns.zone + + +class TransferError(dns.exception.DNSException): + """A zone transfer response got a non-zero rcode.""" + + def __init__(self, rcode): + message = f"Zone transfer error: {dns.rcode.to_text(rcode)}" + super().__init__(message) + self.rcode = rcode + + +class SerialWentBackwards(dns.exception.FormError): + """The current serial number is less than the serial we know.""" + + +class UseTCP(dns.exception.DNSException): + """This IXFR cannot be completed with UDP.""" + + +class Inbound: + """ + State machine for zone transfers. + """ + + def __init__( + self, + txn_manager: dns.transaction.TransactionManager, + rdtype: dns.rdatatype.RdataType = dns.rdatatype.AXFR, + serial: int | None = None, + is_udp: bool = False, + ): + """Initialize an inbound zone transfer. + + *txn_manager* is a :py:class:`dns.transaction.TransactionManager`. + + *rdtype* can be `dns.rdatatype.AXFR` or `dns.rdatatype.IXFR` + + *serial* is the base serial number for IXFRs, and is required in + that case. + + *is_udp*, a ``bool`` indidicates if UDP is being used for this + XFR. + """ + self.txn_manager = txn_manager + self.txn: dns.transaction.Transaction | None = None + self.rdtype = rdtype + if rdtype == dns.rdatatype.IXFR: + if serial is None: + raise ValueError("a starting serial must be supplied for IXFRs") + self.incremental = True + elif rdtype == dns.rdatatype.AXFR: + if is_udp: + raise ValueError("is_udp specified for AXFR") + self.incremental = False + else: + raise ValueError("rdtype is not IXFR or AXFR") + self.serial = serial + self.is_udp = is_udp + (_, _, self.origin) = txn_manager.origin_information() + self.soa_rdataset: dns.rdataset.Rdataset | None = None + self.done = False + self.expecting_SOA = False + self.delete_mode = False + + def process_message(self, message: dns.message.Message) -> bool: + """Process one message in the transfer. + + The message should have the same relativization as was specified when + the `dns.xfr.Inbound` was created. The message should also have been + created with `one_rr_per_rrset=True` because order matters. + + Returns `True` if the transfer is complete, and `False` otherwise. + """ + if self.txn is None: + self.txn = self.txn_manager.writer(not self.incremental) + rcode = message.rcode() + if rcode != dns.rcode.NOERROR: + raise TransferError(rcode) + # + # We don't require a question section, but if it is present is + # should be correct. + # + if len(message.question) > 0: + if message.question[0].name != self.origin: + raise dns.exception.FormError("wrong question name") + if message.question[0].rdtype != self.rdtype: + raise dns.exception.FormError("wrong question rdatatype") + answer_index = 0 + if self.soa_rdataset is None: + # + # This is the first message. We're expecting an SOA at + # the origin. + # + if not message.answer or message.answer[0].name != self.origin: + raise dns.exception.FormError("No answer or RRset not for zone origin") + rrset = message.answer[0] + rdataset = rrset + if rdataset.rdtype != dns.rdatatype.SOA: + raise dns.exception.FormError("first RRset is not an SOA") + answer_index = 1 + self.soa_rdataset = rdataset.copy() # pyright: ignore + if self.incremental: + assert self.soa_rdataset is not None + soa = cast(dns.rdtypes.ANY.SOA.SOA, self.soa_rdataset[0]) + if soa.serial == self.serial: + # + # We're already up-to-date. + # + self.done = True + elif dns.serial.Serial(soa.serial) < self.serial: + # It went backwards! + raise SerialWentBackwards + else: + if self.is_udp and len(message.answer[answer_index:]) == 0: + # + # There are no more records, so this is the + # "truncated" response. Say to use TCP + # + raise UseTCP + # + # Note we're expecting another SOA so we can detect + # if this IXFR response is an AXFR-style response. + # + self.expecting_SOA = True + # + # Process the answer section (other than the initial SOA in + # the first message). + # + for rrset in message.answer[answer_index:]: + name = rrset.name + rdataset = rrset + if self.done: + raise dns.exception.FormError("answers after final SOA") + assert self.txn is not None # for mypy + if rdataset.rdtype == dns.rdatatype.SOA and name == self.origin: + # + # Every time we see an origin SOA delete_mode inverts + # + if self.incremental: + self.delete_mode = not self.delete_mode + # + # If this SOA Rdataset is equal to the first we saw + # then we're finished. If this is an IXFR we also + # check that we're seeing the record in the expected + # part of the response. + # + if rdataset == self.soa_rdataset and ( + (not self.incremental) or self.delete_mode + ): + # + # This is the final SOA + # + soa = cast(dns.rdtypes.ANY.SOA.SOA, rdataset[0]) + if self.expecting_SOA: + # We got an empty IXFR sequence! + raise dns.exception.FormError("empty IXFR sequence") + if self.incremental and self.serial != soa.serial: + raise dns.exception.FormError("unexpected end of IXFR sequence") + self.txn.replace(name, rdataset) + self.txn.commit() + self.txn = None + self.done = True + else: + # + # This is not the final SOA + # + self.expecting_SOA = False + soa = cast(dns.rdtypes.ANY.SOA.SOA, rdataset[0]) + if self.incremental: + if self.delete_mode: + # This is the start of an IXFR deletion set + if soa.serial != self.serial: + raise dns.exception.FormError( + "IXFR base serial mismatch" + ) + else: + # This is the start of an IXFR addition set + self.serial = soa.serial + self.txn.replace(name, rdataset) + else: + # We saw a non-final SOA for the origin in an AXFR. + raise dns.exception.FormError("unexpected origin SOA in AXFR") + continue + if self.expecting_SOA: + # + # We made an IXFR request and are expecting another + # SOA RR, but saw something else, so this must be an + # AXFR response. + # + self.incremental = False + self.expecting_SOA = False + self.delete_mode = False + self.txn.rollback() + self.txn = self.txn_manager.writer(True) + # + # Note we are falling through into the code below + # so whatever rdataset this was gets written. + # + # Add or remove the data + if self.delete_mode: + self.txn.delete_exact(name, rdataset) + else: + self.txn.add(name, rdataset) + if self.is_udp and not self.done: + # + # This is a UDP IXFR and we didn't get to done, and we didn't + # get the proper "truncated" response + # + raise dns.exception.FormError("unexpected end of UDP IXFR") + return self.done + + # + # Inbounds are context managers. + # + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.txn: + self.txn.rollback() + return False + + +def make_query( + txn_manager: dns.transaction.TransactionManager, + serial: int | None = 0, + use_edns: int | bool | None = None, + ednsflags: int | None = None, + payload: int | None = None, + request_payload: int | None = None, + options: List[dns.edns.Option] | None = None, + keyring: Any = None, + keyname: dns.name.Name | None = None, + keyalgorithm: dns.name.Name | str = dns.tsig.default_algorithm, +) -> Tuple[dns.message.QueryMessage, int | None]: + """Make an AXFR or IXFR query. + + *txn_manager* is a ``dns.transaction.TransactionManager``, typically a + ``dns.zone.Zone``. + + *serial* is an ``int`` or ``None``. If 0, then IXFR will be + attempted using the most recent serial number from the + *txn_manager*; it is the caller's responsibility to ensure there + are no write transactions active that could invalidate the + retrieved serial. If a serial cannot be determined, AXFR will be + forced. Other integer values are the starting serial to use. + ``None`` forces an AXFR. + + Please see the documentation for :py:func:`dns.message.make_query` and + :py:func:`dns.message.Message.use_tsig` for details on the other parameters + to this function. + + Returns a `(query, serial)` tuple. + """ + (zone_origin, _, origin) = txn_manager.origin_information() + if zone_origin is None: + raise ValueError("no zone origin") + if serial is None: + rdtype = dns.rdatatype.AXFR + elif not isinstance(serial, int): + raise ValueError("serial is not an integer") + elif serial == 0: + with txn_manager.reader() as txn: + rdataset = txn.get(origin, "SOA") + if rdataset: + soa = cast(dns.rdtypes.ANY.SOA.SOA, rdataset[0]) + serial = soa.serial + rdtype = dns.rdatatype.IXFR + else: + serial = None + rdtype = dns.rdatatype.AXFR + elif serial > 0 and serial < 4294967296: + rdtype = dns.rdatatype.IXFR + else: + raise ValueError("serial out-of-range") + rdclass = txn_manager.get_class() + q = dns.message.make_query( + zone_origin, + rdtype, + rdclass, + use_edns, + False, + ednsflags, + payload, + request_payload, + options, + ) + if serial is not None: + rdata = dns.rdata.from_text(rdclass, "SOA", f". . {serial} 0 0 0 0") + rrset = q.find_rrset( + q.authority, zone_origin, rdclass, dns.rdatatype.SOA, create=True + ) + rrset.add(rdata, 0) + if keyring is not None: + q.use_tsig(keyring, keyname, algorithm=keyalgorithm) + return (q, serial) + + +def extract_serial_from_query(query: dns.message.Message) -> int | None: + """Extract the SOA serial number from query if it is an IXFR and return + it, otherwise return None. + + *query* is a dns.message.QueryMessage that is an IXFR or AXFR request. + + Raises if the query is not an IXFR or AXFR, or if an IXFR doesn't have + an appropriate SOA RRset in the authority section. + """ + if not isinstance(query, dns.message.QueryMessage): + raise ValueError("query not a QueryMessage") + question = query.question[0] + if question.rdtype == dns.rdatatype.AXFR: + return None + elif question.rdtype != dns.rdatatype.IXFR: + raise ValueError("query is not an AXFR or IXFR") + soa_rrset = query.find_rrset( + query.authority, question.name, question.rdclass, dns.rdatatype.SOA + ) + soa = cast(dns.rdtypes.ANY.SOA.SOA, soa_rrset[0]) + return soa.serial diff --git a/venv/lib/python3.12/site-packages/dns/zone.py b/venv/lib/python3.12/site-packages/dns/zone.py new file mode 100644 index 0000000..f916ffe --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/zone.py @@ -0,0 +1,1462 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS Zones.""" + +import contextlib +import io +import os +import struct +from typing import ( + Any, + Callable, + Iterable, + Iterator, + List, + MutableMapping, + Set, + Tuple, + cast, +) + +import dns.exception +import dns.grange +import dns.immutable +import dns.name +import dns.node +import dns.rdata +import dns.rdataclass +import dns.rdataset +import dns.rdatatype +import dns.rdtypes.ANY.SOA +import dns.rdtypes.ANY.ZONEMD +import dns.rrset +import dns.tokenizer +import dns.transaction +import dns.ttl +import dns.zonefile +from dns.zonetypes import DigestHashAlgorithm, DigestScheme, _digest_hashers + + +class BadZone(dns.exception.DNSException): + """The DNS zone is malformed.""" + + +class NoSOA(BadZone): + """The DNS zone has no SOA RR at its origin.""" + + +class NoNS(BadZone): + """The DNS zone has no NS RRset at its origin.""" + + +class UnknownOrigin(BadZone): + """The DNS zone's origin is unknown.""" + + +class UnsupportedDigestScheme(dns.exception.DNSException): + """The zone digest's scheme is unsupported.""" + + +class UnsupportedDigestHashAlgorithm(dns.exception.DNSException): + """The zone digest's origin is unsupported.""" + + +class NoDigest(dns.exception.DNSException): + """The DNS zone has no ZONEMD RRset at its origin.""" + + +class DigestVerificationFailure(dns.exception.DNSException): + """The ZONEMD digest failed to verify.""" + + +def _validate_name( + name: dns.name.Name, + origin: dns.name.Name | None, + relativize: bool, +) -> dns.name.Name: + # This name validation code is shared by Zone and Version + if origin is None: + # This should probably never happen as other code (e.g. + # _rr_line) will notice the lack of an origin before us, but + # we check just in case! + raise KeyError("no zone origin is defined") + if name.is_absolute(): + if not name.is_subdomain(origin): + raise KeyError("name parameter must be a subdomain of the zone origin") + if relativize: + name = name.relativize(origin) + else: + # We have a relative name. Make sure that the derelativized name is + # not too long. + try: + abs_name = name.derelativize(origin) + except dns.name.NameTooLong: + # We map dns.name.NameTooLong to KeyError to be consistent with + # the other exceptions above. + raise KeyError("relative name too long for zone") + if not relativize: + # We have a relative name in a non-relative zone, so use the + # derelativized name. + name = abs_name + return name + + +class Zone(dns.transaction.TransactionManager): + """A DNS zone. + + A ``Zone`` is a mapping from names to nodes. The zone object may be + treated like a Python dictionary, e.g. ``zone[name]`` will retrieve + the node associated with that name. The *name* may be a + ``dns.name.Name object``, or it may be a string. In either case, + if the name is relative it is treated as relative to the origin of + the zone. + """ + + node_factory: Callable[[], dns.node.Node] = dns.node.Node + map_factory: Callable[[], MutableMapping[dns.name.Name, dns.node.Node]] = dict + # We only require the version types as "Version" to allow for flexibility, as + # only the version protocol matters + writable_version_factory: Callable[["Zone", bool], "Version"] | None = None + immutable_version_factory: Callable[["Version"], "Version"] | None = None + + __slots__ = ["rdclass", "origin", "nodes", "relativize"] + + def __init__( + self, + origin: dns.name.Name | str | None, + rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, + relativize: bool = True, + ): + """Initialize a zone object. + + *origin* is the origin of the zone. It may be a ``dns.name.Name``, + a ``str``, or ``None``. If ``None``, then the zone's origin will + be set by the first ``$ORIGIN`` line in a zone file. + + *rdclass*, an ``int``, the zone's rdata class; the default is class IN. + + *relativize*, a ``bool``, determine's whether domain names are + relativized to the zone's origin. The default is ``True``. + """ + + if origin is not None: + if isinstance(origin, str): + origin = dns.name.from_text(origin) + elif not isinstance(origin, dns.name.Name): + raise ValueError("origin parameter must be convertible to a DNS name") + if not origin.is_absolute(): + raise ValueError("origin parameter must be an absolute name") + self.origin = origin + self.rdclass = rdclass + self.nodes: MutableMapping[dns.name.Name, dns.node.Node] = self.map_factory() + self.relativize = relativize + + def __eq__(self, other): + """Two zones are equal if they have the same origin, class, and + nodes. + + Returns a ``bool``. + """ + + if not isinstance(other, Zone): + return False + if ( + self.rdclass != other.rdclass + or self.origin != other.origin + or self.nodes != other.nodes + ): + return False + return True + + def __ne__(self, other): + """Are two zones not equal? + + Returns a ``bool``. + """ + + return not self.__eq__(other) + + def _validate_name(self, name: dns.name.Name | str) -> dns.name.Name: + # Note that any changes in this method should have corresponding changes + # made in the Version _validate_name() method. + if isinstance(name, str): + name = dns.name.from_text(name, None) + elif not isinstance(name, dns.name.Name): + raise KeyError("name parameter must be convertible to a DNS name") + return _validate_name(name, self.origin, self.relativize) + + def __getitem__(self, key): + key = self._validate_name(key) + return self.nodes[key] + + def __setitem__(self, key, value): + key = self._validate_name(key) + self.nodes[key] = value + + def __delitem__(self, key): + key = self._validate_name(key) + del self.nodes[key] + + def __iter__(self): + return self.nodes.__iter__() + + def keys(self): + return self.nodes.keys() + + def values(self): + return self.nodes.values() + + def items(self): + return self.nodes.items() + + def get(self, key): + key = self._validate_name(key) + return self.nodes.get(key) + + def __contains__(self, key): + key = self._validate_name(key) + return key in self.nodes + + def find_node( + self, name: dns.name.Name | str, create: bool = False + ) -> dns.node.Node: + """Find a node in the zone, possibly creating it. + + *name*: the name of the node to find. + The value may be a ``dns.name.Name`` or a ``str``. If absolute, the + name must be a subdomain of the zone's origin. If ``zone.relativize`` + is ``True``, then the name will be relativized. + + *create*, a ``bool``. If true, the node will be created if it does + not exist. + + Raises ``KeyError`` if the name is not known and create was + not specified, or if the name was not a subdomain of the origin. + + Returns a ``dns.node.Node``. + """ + + name = self._validate_name(name) + node = self.nodes.get(name) + if node is None: + if not create: + raise KeyError + node = self.node_factory() + self.nodes[name] = node + return node + + def get_node( + self, name: dns.name.Name | str, create: bool = False + ) -> dns.node.Node | None: + """Get a node in the zone, possibly creating it. + + This method is like ``find_node()``, except it returns None instead + of raising an exception if the node does not exist and creation + has not been requested. + + *name*: the name of the node to find. + The value may be a ``dns.name.Name`` or a ``str``. If absolute, the + name must be a subdomain of the zone's origin. If ``zone.relativize`` + is ``True``, then the name will be relativized. + + *create*, a ``bool``. If true, the node will be created if it does + not exist. + + Returns a ``dns.node.Node`` or ``None``. + """ + + try: + node = self.find_node(name, create) + except KeyError: + node = None + return node + + def delete_node(self, name: dns.name.Name | str) -> None: + """Delete the specified node if it exists. + + *name*: the name of the node to find. + The value may be a ``dns.name.Name`` or a ``str``. If absolute, the + name must be a subdomain of the zone's origin. If ``zone.relativize`` + is ``True``, then the name will be relativized. + + It is not an error if the node does not exist. + """ + + name = self._validate_name(name) + if name in self.nodes: + del self.nodes[name] + + def find_rdataset( + self, + name: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str, + covers: dns.rdatatype.RdataType | str = dns.rdatatype.NONE, + create: bool = False, + ) -> dns.rdataset.Rdataset: + """Look for an rdataset with the specified name and type in the zone, + and return an rdataset encapsulating it. + + The rdataset returned is not a copy; changes to it will change + the zone. + + KeyError is raised if the name or type are not found. + + *name*: the name of the node to find. + The value may be a ``dns.name.Name`` or a ``str``. If absolute, the + name must be a subdomain of the zone's origin. If ``zone.relativize`` + is ``True``, then the name will be relativized. + + *rdtype*, a ``dns.rdatatype.RdataType`` or ``str``, the rdata type desired. + + *covers*, a ``dns.rdatatype.RdataType`` or ``str`` the covered type. + Usually this value is ``dns.rdatatype.NONE``, but if the + rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, + then the covers value will be the rdata type the SIG/RRSIG + covers. The library treats the SIG and RRSIG types as if they + were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). + This makes RRSIGs much easier to work with than if RRSIGs + covering different rdata types were aggregated into a single + RRSIG rdataset. + + *create*, a ``bool``. If true, the node will be created if it does + not exist. + + Raises ``KeyError`` if the name is not known and create was + not specified, or if the name was not a subdomain of the origin. + + Returns a ``dns.rdataset.Rdataset``. + """ + + name = self._validate_name(name) + rdtype = dns.rdatatype.RdataType.make(rdtype) + covers = dns.rdatatype.RdataType.make(covers) + node = self.find_node(name, create) + return node.find_rdataset(self.rdclass, rdtype, covers, create) + + def get_rdataset( + self, + name: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str, + covers: dns.rdatatype.RdataType | str = dns.rdatatype.NONE, + create: bool = False, + ) -> dns.rdataset.Rdataset | None: + """Look for an rdataset with the specified name and type in the zone. + + This method is like ``find_rdataset()``, except it returns None instead + of raising an exception if the rdataset does not exist and creation + has not been requested. + + The rdataset returned is not a copy; changes to it will change + the zone. + + *name*: the name of the node to find. + The value may be a ``dns.name.Name`` or a ``str``. If absolute, the + name must be a subdomain of the zone's origin. If ``zone.relativize`` + is ``True``, then the name will be relativized. + + *rdtype*, a ``dns.rdatatype.RdataType`` or ``str``, the rdata type desired. + + *covers*, a ``dns.rdatatype.RdataType`` or ``str``, the covered type. + Usually this value is ``dns.rdatatype.NONE``, but if the + rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, + then the covers value will be the rdata type the SIG/RRSIG + covers. The library treats the SIG and RRSIG types as if they + were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). + This makes RRSIGs much easier to work with than if RRSIGs + covering different rdata types were aggregated into a single + RRSIG rdataset. + + *create*, a ``bool``. If true, the node will be created if it does + not exist. + + Raises ``KeyError`` if the name is not known and create was + not specified, or if the name was not a subdomain of the origin. + + Returns a ``dns.rdataset.Rdataset`` or ``None``. + """ + + try: + rdataset = self.find_rdataset(name, rdtype, covers, create) + except KeyError: + rdataset = None + return rdataset + + def delete_rdataset( + self, + name: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str, + covers: dns.rdatatype.RdataType | str = dns.rdatatype.NONE, + ) -> None: + """Delete the rdataset matching *rdtype* and *covers*, if it + exists at the node specified by *name*. + + It is not an error if the node does not exist, or if there is no matching + rdataset at the node. + + If the node has no rdatasets after the deletion, it will itself be deleted. + + *name*: the name of the node to find. The value may be a ``dns.name.Name`` or a + ``str``. If absolute, the name must be a subdomain of the zone's origin. If + ``zone.relativize`` is ``True``, then the name will be relativized. + + *rdtype*, a ``dns.rdatatype.RdataType`` or ``str``, the rdata type desired. + + *covers*, a ``dns.rdatatype.RdataType`` or ``str`` or ``None``, the covered + type. Usually this value is ``dns.rdatatype.NONE``, but if the rdtype is + ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, then the covers value will be + the rdata type the SIG/RRSIG covers. The library treats the SIG and RRSIG types + as if they were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This + makes RRSIGs much easier to work with than if RRSIGs covering different rdata + types were aggregated into a single RRSIG rdataset. + """ + + name = self._validate_name(name) + rdtype = dns.rdatatype.RdataType.make(rdtype) + covers = dns.rdatatype.RdataType.make(covers) + node = self.get_node(name) + if node is not None: + node.delete_rdataset(self.rdclass, rdtype, covers) + if len(node) == 0: + self.delete_node(name) + + def replace_rdataset( + self, name: dns.name.Name | str, replacement: dns.rdataset.Rdataset + ) -> None: + """Replace an rdataset at name. + + It is not an error if there is no rdataset matching I{replacement}. + + Ownership of the *replacement* object is transferred to the zone; + in other words, this method does not store a copy of *replacement* + at the node, it stores *replacement* itself. + + If the node does not exist, it is created. + + *name*: the name of the node to find. + The value may be a ``dns.name.Name`` or a ``str``. If absolute, the + name must be a subdomain of the zone's origin. If ``zone.relativize`` + is ``True``, then the name will be relativized. + + *replacement*, a ``dns.rdataset.Rdataset``, the replacement rdataset. + """ + + if replacement.rdclass != self.rdclass: + raise ValueError("replacement.rdclass != zone.rdclass") + node = self.find_node(name, True) + node.replace_rdataset(replacement) + + def find_rrset( + self, + name: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str, + covers: dns.rdatatype.RdataType | str = dns.rdatatype.NONE, + ) -> dns.rrset.RRset: + """Look for an rdataset with the specified name and type in the zone, + and return an RRset encapsulating it. + + This method is less efficient than the similar + ``find_rdataset()`` because it creates an RRset instead of + returning the matching rdataset. It may be more convenient + for some uses since it returns an object which binds the owner + name to the rdataset. + + This method may not be used to create new nodes or rdatasets; + use ``find_rdataset`` instead. + + *name*: the name of the node to find. + The value may be a ``dns.name.Name`` or a ``str``. If absolute, the + name must be a subdomain of the zone's origin. If ``zone.relativize`` + is ``True``, then the name will be relativized. + + *rdtype*, a ``dns.rdatatype.RdataType`` or ``str``, the rdata type desired. + + *covers*, a ``dns.rdatatype.RdataType`` or ``str``, the covered type. + Usually this value is ``dns.rdatatype.NONE``, but if the + rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, + then the covers value will be the rdata type the SIG/RRSIG + covers. The library treats the SIG and RRSIG types as if they + were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). + This makes RRSIGs much easier to work with than if RRSIGs + covering different rdata types were aggregated into a single + RRSIG rdataset. + + *create*, a ``bool``. If true, the node will be created if it does + not exist. + + Raises ``KeyError`` if the name is not known and create was + not specified, or if the name was not a subdomain of the origin. + + Returns a ``dns.rrset.RRset`` or ``None``. + """ + + vname = self._validate_name(name) + rdtype = dns.rdatatype.RdataType.make(rdtype) + covers = dns.rdatatype.RdataType.make(covers) + rdataset = self.nodes[vname].find_rdataset(self.rdclass, rdtype, covers) + rrset = dns.rrset.RRset(vname, self.rdclass, rdtype, covers) + rrset.update(rdataset) + return rrset + + def get_rrset( + self, + name: dns.name.Name | str, + rdtype: dns.rdatatype.RdataType | str, + covers: dns.rdatatype.RdataType | str = dns.rdatatype.NONE, + ) -> dns.rrset.RRset | None: + """Look for an rdataset with the specified name and type in the zone, + and return an RRset encapsulating it. + + This method is less efficient than the similar ``get_rdataset()`` + because it creates an RRset instead of returning the matching + rdataset. It may be more convenient for some uses since it + returns an object which binds the owner name to the rdataset. + + This method may not be used to create new nodes or rdatasets; + use ``get_rdataset()`` instead. + + *name*: the name of the node to find. + The value may be a ``dns.name.Name`` or a ``str``. If absolute, the + name must be a subdomain of the zone's origin. If ``zone.relativize`` + is ``True``, then the name will be relativized. + + *rdtype*, a ``dns.rdataset.Rdataset`` or ``str``, the rdata type desired. + + *covers*, a ``dns.rdataset.Rdataset`` or ``str``, the covered type. + Usually this value is ``dns.rdatatype.NONE``, but if the + rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, + then the covers value will be the rdata type the SIG/RRSIG + covers. The library treats the SIG and RRSIG types as if they + were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). + This makes RRSIGs much easier to work with than if RRSIGs + covering different rdata types were aggregated into a single + RRSIG rdataset. + + *create*, a ``bool``. If true, the node will be created if it does + not exist. + + Returns a ``dns.rrset.RRset`` or ``None``. + """ + + try: + rrset = self.find_rrset(name, rdtype, covers) + except KeyError: + rrset = None + return rrset + + def iterate_rdatasets( + self, + rdtype: dns.rdatatype.RdataType | str = dns.rdatatype.ANY, + covers: dns.rdatatype.RdataType | str = dns.rdatatype.NONE, + ) -> Iterator[Tuple[dns.name.Name, dns.rdataset.Rdataset]]: + """Return a generator which yields (name, rdataset) tuples for + all rdatasets in the zone which have the specified *rdtype* + and *covers*. If *rdtype* is ``dns.rdatatype.ANY``, the default, + then all rdatasets will be matched. + + *rdtype*, a ``dns.rdataset.Rdataset`` or ``str``, the rdata type desired. + + *covers*, a ``dns.rdataset.Rdataset`` or ``str``, the covered type. + Usually this value is ``dns.rdatatype.NONE``, but if the + rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, + then the covers value will be the rdata type the SIG/RRSIG + covers. The library treats the SIG and RRSIG types as if they + were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). + This makes RRSIGs much easier to work with than if RRSIGs + covering different rdata types were aggregated into a single + RRSIG rdataset. + """ + + rdtype = dns.rdatatype.RdataType.make(rdtype) + covers = dns.rdatatype.RdataType.make(covers) + for name, node in self.items(): + for rds in node: + if rdtype == dns.rdatatype.ANY or ( + rds.rdtype == rdtype and rds.covers == covers + ): + yield (name, rds) + + def iterate_rdatas( + self, + rdtype: dns.rdatatype.RdataType | str = dns.rdatatype.ANY, + covers: dns.rdatatype.RdataType | str = dns.rdatatype.NONE, + ) -> Iterator[Tuple[dns.name.Name, int, dns.rdata.Rdata]]: + """Return a generator which yields (name, ttl, rdata) tuples for + all rdatas in the zone which have the specified *rdtype* + and *covers*. If *rdtype* is ``dns.rdatatype.ANY``, the default, + then all rdatas will be matched. + + *rdtype*, a ``dns.rdataset.Rdataset`` or ``str``, the rdata type desired. + + *covers*, a ``dns.rdataset.Rdataset`` or ``str``, the covered type. + Usually this value is ``dns.rdatatype.NONE``, but if the + rdtype is ``dns.rdatatype.SIG`` or ``dns.rdatatype.RRSIG``, + then the covers value will be the rdata type the SIG/RRSIG + covers. The library treats the SIG and RRSIG types as if they + were a family of types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). + This makes RRSIGs much easier to work with than if RRSIGs + covering different rdata types were aggregated into a single + RRSIG rdataset. + """ + + rdtype = dns.rdatatype.RdataType.make(rdtype) + covers = dns.rdatatype.RdataType.make(covers) + for name, node in self.items(): + for rds in node: + if rdtype == dns.rdatatype.ANY or ( + rds.rdtype == rdtype and rds.covers == covers + ): + for rdata in rds: + yield (name, rds.ttl, rdata) + + def to_file( + self, + f: Any, + sorted: bool = True, + relativize: bool = True, + nl: str | None = None, + want_comments: bool = False, + want_origin: bool = False, + ) -> None: + """Write a zone to a file. + + *f*, a file or `str`. If *f* is a string, it is treated + as the name of a file to open. + + *sorted*, a ``bool``. If True, the default, then the file + will be written with the names sorted in DNSSEC order from + least to greatest. Otherwise the names will be written in + whatever order they happen to have in the zone's dictionary. + + *relativize*, a ``bool``. If True, the default, then domain + names in the output will be relativized to the zone's origin + if possible. + + *nl*, a ``str`` or None. The end of line string. If not + ``None``, the output will use the platform's native + end-of-line marker (i.e. LF on POSIX, CRLF on Windows). + + *want_comments*, a ``bool``. If ``True``, emit end-of-line comments + as part of writing the file. If ``False``, the default, do not + emit them. + + *want_origin*, a ``bool``. If ``True``, emit a $ORIGIN line at + the start of the file. If ``False``, the default, do not emit + one. + """ + + if isinstance(f, str): + cm: contextlib.AbstractContextManager = open(f, "wb") + else: + cm = contextlib.nullcontext(f) + with cm as f: + # must be in this way, f.encoding may contain None, or even + # attribute may not be there + file_enc = getattr(f, "encoding", None) + if file_enc is None: + file_enc = "utf-8" + + if nl is None: + # binary mode, '\n' is not enough + nl_b = os.linesep.encode(file_enc) + nl = "\n" + elif isinstance(nl, str): + nl_b = nl.encode(file_enc) + else: + nl_b = nl + nl = nl.decode() + + if want_origin: + assert self.origin is not None + l = "$ORIGIN " + self.origin.to_text() + l_b = l.encode(file_enc) + try: + f.write(l_b) + f.write(nl_b) + except TypeError: # textual mode + f.write(l) + f.write(nl) + + if sorted: + names = list(self.keys()) + names.sort() + else: + names = self.keys() + for n in names: + l = self[n].to_text( + n, + origin=self.origin, # pyright: ignore + relativize=relativize, # pyright: ignore + want_comments=want_comments, # pyright: ignore + ) + l_b = l.encode(file_enc) + + try: + f.write(l_b) + f.write(nl_b) + except TypeError: # textual mode + f.write(l) + f.write(nl) + + def to_text( + self, + sorted: bool = True, + relativize: bool = True, + nl: str | None = None, + want_comments: bool = False, + want_origin: bool = False, + ) -> str: + """Return a zone's text as though it were written to a file. + + *sorted*, a ``bool``. If True, the default, then the file + will be written with the names sorted in DNSSEC order from + least to greatest. Otherwise the names will be written in + whatever order they happen to have in the zone's dictionary. + + *relativize*, a ``bool``. If True, the default, then domain + names in the output will be relativized to the zone's origin + if possible. + + *nl*, a ``str`` or None. The end of line string. If not + ``None``, the output will use the platform's native + end-of-line marker (i.e. LF on POSIX, CRLF on Windows). + + *want_comments*, a ``bool``. If ``True``, emit end-of-line comments + as part of writing the file. If ``False``, the default, do not + emit them. + + *want_origin*, a ``bool``. If ``True``, emit a $ORIGIN line at + the start of the output. If ``False``, the default, do not emit + one. + + Returns a ``str``. + """ + temp_buffer = io.StringIO() + self.to_file(temp_buffer, sorted, relativize, nl, want_comments, want_origin) + return_value = temp_buffer.getvalue() + temp_buffer.close() + return return_value + + def check_origin(self) -> None: + """Do some simple checking of the zone's origin. + + Raises ``dns.zone.NoSOA`` if there is no SOA RRset. + + Raises ``dns.zone.NoNS`` if there is no NS RRset. + + Raises ``KeyError`` if there is no origin node. + """ + if self.relativize: + name = dns.name.empty + else: + assert self.origin is not None + name = self.origin + if self.get_rdataset(name, dns.rdatatype.SOA) is None: + raise NoSOA + if self.get_rdataset(name, dns.rdatatype.NS) is None: + raise NoNS + + def get_soa( + self, txn: dns.transaction.Transaction | None = None + ) -> dns.rdtypes.ANY.SOA.SOA: + """Get the zone SOA rdata. + + Raises ``dns.zone.NoSOA`` if there is no SOA RRset. + + Returns a ``dns.rdtypes.ANY.SOA.SOA`` Rdata. + """ + if self.relativize: + origin_name = dns.name.empty + else: + if self.origin is None: + # get_soa() has been called very early, and there must not be + # an SOA if there is no origin. + raise NoSOA + origin_name = self.origin + soa_rds: dns.rdataset.Rdataset | None + if txn: + soa_rds = txn.get(origin_name, dns.rdatatype.SOA) + else: + soa_rds = self.get_rdataset(origin_name, dns.rdatatype.SOA) + if soa_rds is None: + raise NoSOA + else: + soa = cast(dns.rdtypes.ANY.SOA.SOA, soa_rds[0]) + return soa + + def _compute_digest( + self, + hash_algorithm: DigestHashAlgorithm, + scheme: DigestScheme = DigestScheme.SIMPLE, + ) -> bytes: + hashinfo = _digest_hashers.get(hash_algorithm) + if not hashinfo: + raise UnsupportedDigestHashAlgorithm + if scheme != DigestScheme.SIMPLE: + raise UnsupportedDigestScheme + + if self.relativize: + origin_name = dns.name.empty + else: + assert self.origin is not None + origin_name = self.origin + hasher = hashinfo() + for name, node in sorted(self.items()): + rrnamebuf = name.to_digestable(self.origin) + for rdataset in sorted(node, key=lambda rds: (rds.rdtype, rds.covers)): + if name == origin_name and dns.rdatatype.ZONEMD in ( + rdataset.rdtype, + rdataset.covers, + ): + continue + rrfixed = struct.pack( + "!HHI", rdataset.rdtype, rdataset.rdclass, rdataset.ttl + ) + rdatas = [rdata.to_digestable(self.origin) for rdata in rdataset] + for rdata in sorted(rdatas): + rrlen = struct.pack("!H", len(rdata)) + hasher.update(rrnamebuf + rrfixed + rrlen + rdata) + return hasher.digest() + + def compute_digest( + self, + hash_algorithm: DigestHashAlgorithm, + scheme: DigestScheme = DigestScheme.SIMPLE, + ) -> dns.rdtypes.ANY.ZONEMD.ZONEMD: + serial = self.get_soa().serial + digest = self._compute_digest(hash_algorithm, scheme) + return dns.rdtypes.ANY.ZONEMD.ZONEMD( + self.rdclass, dns.rdatatype.ZONEMD, serial, scheme, hash_algorithm, digest + ) + + def verify_digest( + self, zonemd: dns.rdtypes.ANY.ZONEMD.ZONEMD | None = None + ) -> None: + digests: dns.rdataset.Rdataset | List[dns.rdtypes.ANY.ZONEMD.ZONEMD] + if zonemd: + digests = [zonemd] + else: + assert self.origin is not None + rds = self.get_rdataset(self.origin, dns.rdatatype.ZONEMD) + if rds is None: + raise NoDigest + digests = rds + for digest in digests: + try: + computed = self._compute_digest(digest.hash_algorithm, digest.scheme) + if computed == digest.digest: + return + except Exception: + pass + raise DigestVerificationFailure + + # TransactionManager methods + + def reader(self) -> "Transaction": + return Transaction(self, False, Version(self, 1, self.nodes, self.origin)) + + def writer(self, replacement: bool = False) -> "Transaction": + txn = Transaction(self, replacement) + txn._setup_version() + return txn + + def origin_information( + self, + ) -> Tuple[dns.name.Name | None, bool, dns.name.Name | None]: + effective: dns.name.Name | None + if self.relativize: + effective = dns.name.empty + else: + effective = self.origin + return (self.origin, self.relativize, effective) + + def get_class(self): + return self.rdclass + + # Transaction methods + + def _end_read(self, txn): + pass + + def _end_write(self, txn): + pass + + def _commit_version(self, txn, version, origin): + self.nodes = version.nodes + if self.origin is None: + self.origin = origin + + def _get_next_version_id(self) -> int: + # Versions are ephemeral and all have id 1 + return 1 + + +# These classes used to be in dns.versioned, but have moved here so we can use +# the copy-on-write transaction mechanism for both kinds of zones. In a +# regular zone, the version only exists during the transaction, and the nodes +# are regular dns.node.Nodes. + +# A node with a version id. + + +class VersionedNode(dns.node.Node): # lgtm[py/missing-equals] + __slots__ = ["id"] + + def __init__(self): + super().__init__() + # A proper id will get set by the Version + self.id = 0 + + +@dns.immutable.immutable +class ImmutableVersionedNode(VersionedNode): + def __init__(self, node): + super().__init__() + self.id = node.id + self.rdatasets = tuple( + [dns.rdataset.ImmutableRdataset(rds) for rds in node.rdatasets] + ) + + def find_rdataset( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + create: bool = False, + ) -> dns.rdataset.Rdataset: + if create: + raise TypeError("immutable") + return super().find_rdataset(rdclass, rdtype, covers, False) + + def get_rdataset( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + create: bool = False, + ) -> dns.rdataset.Rdataset | None: + if create: + raise TypeError("immutable") + return super().get_rdataset(rdclass, rdtype, covers, False) + + def delete_rdataset( + self, + rdclass: dns.rdataclass.RdataClass, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType = dns.rdatatype.NONE, + ) -> None: + raise TypeError("immutable") + + def replace_rdataset(self, replacement: dns.rdataset.Rdataset) -> None: + raise TypeError("immutable") + + def is_immutable(self) -> bool: + return True + + +class Version: + def __init__( + self, + zone: Zone, + id: int, + nodes: MutableMapping[dns.name.Name, dns.node.Node] | None = None, + origin: dns.name.Name | None = None, + ): + self.zone = zone + self.id = id + if nodes is not None: + self.nodes = nodes + else: + self.nodes = zone.map_factory() + self.origin = origin + + def _validate_name(self, name: dns.name.Name) -> dns.name.Name: + return _validate_name(name, self.origin, self.zone.relativize) + + def get_node(self, name: dns.name.Name) -> dns.node.Node | None: + name = self._validate_name(name) + return self.nodes.get(name) + + def get_rdataset( + self, + name: dns.name.Name, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType, + ) -> dns.rdataset.Rdataset | None: + node = self.get_node(name) + if node is None: + return None + return node.get_rdataset(self.zone.rdclass, rdtype, covers) + + def keys(self): + return self.nodes.keys() + + def items(self): + return self.nodes.items() + + +class WritableVersion(Version): + def __init__(self, zone: Zone, replacement: bool = False): + # The zone._versions_lock must be held by our caller in a versioned + # zone. + id = zone._get_next_version_id() + super().__init__(zone, id) + if not replacement: + # We copy the map, because that gives us a simple and thread-safe + # way of doing versions, and we have a garbage collector to help + # us. We only make new node objects if we actually change the + # node. + self.nodes.update(zone.nodes) + # We have to copy the zone origin as it may be None in the first + # version, and we don't want to mutate the zone until we commit. + self.origin = zone.origin + self.changed: Set[dns.name.Name] = set() + + def _maybe_cow_with_name( + self, name: dns.name.Name + ) -> Tuple[dns.node.Node, dns.name.Name]: + name = self._validate_name(name) + node = self.nodes.get(name) + if node is None or name not in self.changed: + new_node = self.zone.node_factory() + if hasattr(new_node, "id"): + # We keep doing this for backwards compatibility, as earlier + # code used new_node.id != self.id for the "do we need to CoW?" + # test. Now we use the changed set as this works with both + # regular zones and versioned zones. + # + # We ignore the mypy error as this is safe but it doesn't see it. + new_node.id = self.id # type: ignore + if node is not None: + # moo! copy on write! + new_node.rdatasets.extend(node.rdatasets) + self.nodes[name] = new_node + self.changed.add(name) + return (new_node, name) + else: + return (node, name) + + def _maybe_cow(self, name: dns.name.Name) -> dns.node.Node: + return self._maybe_cow_with_name(name)[0] + + def delete_node(self, name: dns.name.Name) -> None: + name = self._validate_name(name) + if name in self.nodes: + del self.nodes[name] + self.changed.add(name) + + def put_rdataset( + self, name: dns.name.Name, rdataset: dns.rdataset.Rdataset + ) -> None: + node = self._maybe_cow(name) + node.replace_rdataset(rdataset) + + def delete_rdataset( + self, + name: dns.name.Name, + rdtype: dns.rdatatype.RdataType, + covers: dns.rdatatype.RdataType, + ) -> None: + node = self._maybe_cow(name) + node.delete_rdataset(self.zone.rdclass, rdtype, covers) + if len(node) == 0: + del self.nodes[name] + + +@dns.immutable.immutable +class ImmutableVersion(Version): + def __init__(self, version: Version): + if not isinstance(version, WritableVersion): + raise ValueError( + "a dns.zone.ImmutableVersion requires a dns.zone.WritableVersion" + ) + # We tell super() that it's a replacement as we don't want it + # to copy the nodes, as we're about to do that with an + # immutable Dict. + super().__init__(version.zone, True) + # set the right id! + self.id = version.id + # keep the origin + self.origin = version.origin + # Make changed nodes immutable + for name in version.changed: + node = version.nodes.get(name) + # it might not exist if we deleted it in the version + if node: + version.nodes[name] = ImmutableVersionedNode(node) + # We're changing the type of the nodes dictionary here on purpose, so + # we ignore the mypy error. + self.nodes = dns.immutable.Dict( + version.nodes, True, self.zone.map_factory + ) # type: ignore + + +class Transaction(dns.transaction.Transaction): + def __init__(self, zone, replacement, version=None, make_immutable=False): + read_only = version is not None + super().__init__(zone, replacement, read_only) + self.version = version + self.make_immutable = make_immutable + + @property + def zone(self): + return self.manager + + def _setup_version(self): + assert self.version is None + factory = self.manager.writable_version_factory # pyright: ignore + if factory is None: + factory = WritableVersion + self.version = factory(self.zone, self.replacement) # pyright: ignore + + def _get_rdataset(self, name, rdtype, covers): + assert self.version is not None + return self.version.get_rdataset(name, rdtype, covers) + + def _put_rdataset(self, name, rdataset): + assert not self.read_only + assert self.version is not None + self.version.put_rdataset(name, rdataset) + + def _delete_name(self, name): + assert not self.read_only + assert self.version is not None + self.version.delete_node(name) + + def _delete_rdataset(self, name, rdtype, covers): + assert not self.read_only + assert self.version is not None + self.version.delete_rdataset(name, rdtype, covers) + + def _name_exists(self, name): + assert self.version is not None + return self.version.get_node(name) is not None + + def _changed(self): + if self.read_only: + return False + else: + assert self.version is not None + return len(self.version.changed) > 0 + + def _end_transaction(self, commit): + assert self.zone is not None + assert self.version is not None + if self.read_only: + self.zone._end_read(self) # pyright: ignore + elif commit and len(self.version.changed) > 0: + if self.make_immutable: + factory = self.manager.immutable_version_factory # pyright: ignore + if factory is None: + factory = ImmutableVersion + version = factory(self.version) + else: + version = self.version + self.zone._commit_version( # pyright: ignore + self, version, self.version.origin + ) + + else: + # rollback + self.zone._end_write(self) # pyright: ignore + + def _set_origin(self, origin): + assert self.version is not None + if self.version.origin is None: + self.version.origin = origin + + def _iterate_rdatasets(self): + assert self.version is not None + for name, node in self.version.items(): + for rdataset in node: + yield (name, rdataset) + + def _iterate_names(self): + assert self.version is not None + return self.version.keys() + + def _get_node(self, name): + assert self.version is not None + return self.version.get_node(name) + + def _origin_information(self): + assert self.version is not None + (absolute, relativize, effective) = self.manager.origin_information() + if absolute is None and self.version.origin is not None: + # No origin has been committed yet, but we've learned one as part of + # this txn. Use it. + absolute = self.version.origin + if relativize: + effective = dns.name.empty + else: + effective = absolute + return (absolute, relativize, effective) + + +def _from_text( + text: Any, + origin: dns.name.Name | str | None = None, + rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, + relativize: bool = True, + zone_factory: Any = Zone, + filename: str | None = None, + allow_include: bool = False, + check_origin: bool = True, + idna_codec: dns.name.IDNACodec | None = None, + allow_directives: bool | Iterable[str] = True, +) -> Zone: + # See the comments for the public APIs from_text() and from_file() for + # details. + + # 'text' can also be a file, but we don't publish that fact + # since it's an implementation detail. The official file + # interface is from_file(). + + if filename is None: + filename = "" + zone = zone_factory(origin, rdclass, relativize=relativize) + with zone.writer(True) as txn: + tok = dns.tokenizer.Tokenizer(text, filename, idna_codec=idna_codec) + reader = dns.zonefile.Reader( + tok, + rdclass, + txn, + allow_include=allow_include, + allow_directives=allow_directives, + ) + try: + reader.read() + except dns.zonefile.UnknownOrigin: + # for backwards compatibility + raise UnknownOrigin + # Now that we're done reading, do some basic checking of the zone. + if check_origin: + zone.check_origin() + return zone + + +def from_text( + text: str, + origin: dns.name.Name | str | None = None, + rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, + relativize: bool = True, + zone_factory: Any = Zone, + filename: str | None = None, + allow_include: bool = False, + check_origin: bool = True, + idna_codec: dns.name.IDNACodec | None = None, + allow_directives: bool | Iterable[str] = True, +) -> Zone: + """Build a zone object from a zone file format string. + + *text*, a ``str``, the zone file format input. + + *origin*, a ``dns.name.Name``, a ``str``, or ``None``. The origin + of the zone; if not specified, the first ``$ORIGIN`` statement in the + zone file will determine the origin of the zone. + + *rdclass*, a ``dns.rdataclass.RdataClass``, the zone's rdata class; the default is + class IN. + + *relativize*, a ``bool``, determine's whether domain names are + relativized to the zone's origin. The default is ``True``. + + *zone_factory*, the zone factory to use or ``None``. If ``None``, then + ``dns.zone.Zone`` will be used. The value may be any class or callable + that returns a subclass of ``dns.zone.Zone``. + + *filename*, a ``str`` or ``None``, the filename to emit when + describing where an error occurred; the default is ``''``. + + *allow_include*, a ``bool``. If ``True``, the default, then ``$INCLUDE`` + directives are permitted. If ``False``, then encoutering a ``$INCLUDE`` + will raise a ``SyntaxError`` exception. + + *check_origin*, a ``bool``. If ``True``, the default, then sanity + checks of the origin node will be made by calling the zone's + ``check_origin()`` method. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder + is used. + + *allow_directives*, a ``bool`` or an iterable of `str`. If ``True``, the default, + then directives are permitted, and the *allow_include* parameter controls whether + ``$INCLUDE`` is permitted. If ``False`` or an empty iterable, then no directive + processing is done and any directive-like text will be treated as a regular owner + name. If a non-empty iterable, then only the listed directives (including the + ``$``) are allowed. + + Raises ``dns.zone.NoSOA`` if there is no SOA RRset. + + Raises ``dns.zone.NoNS`` if there is no NS RRset. + + Raises ``KeyError`` if there is no origin node. + + Returns a subclass of ``dns.zone.Zone``. + """ + return _from_text( + text, + origin, + rdclass, + relativize, + zone_factory, + filename, + allow_include, + check_origin, + idna_codec, + allow_directives, + ) + + +def from_file( + f: Any, + origin: dns.name.Name | str | None = None, + rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, + relativize: bool = True, + zone_factory: Any = Zone, + filename: str | None = None, + allow_include: bool = True, + check_origin: bool = True, + idna_codec: dns.name.IDNACodec | None = None, + allow_directives: bool | Iterable[str] = True, +) -> Zone: + """Read a zone file and build a zone object. + + *f*, a file or ``str``. If *f* is a string, it is treated + as the name of a file to open. + + *origin*, a ``dns.name.Name``, a ``str``, or ``None``. The origin + of the zone; if not specified, the first ``$ORIGIN`` statement in the + zone file will determine the origin of the zone. + + *rdclass*, an ``int``, the zone's rdata class; the default is class IN. + + *relativize*, a ``bool``, determine's whether domain names are + relativized to the zone's origin. The default is ``True``. + + *zone_factory*, the zone factory to use or ``None``. If ``None``, then + ``dns.zone.Zone`` will be used. The value may be any class or callable + that returns a subclass of ``dns.zone.Zone``. + + *filename*, a ``str`` or ``None``, the filename to emit when + describing where an error occurred; the default is ``''``. + + *allow_include*, a ``bool``. If ``True``, the default, then ``$INCLUDE`` + directives are permitted. If ``False``, then encoutering a ``$INCLUDE`` + will raise a ``SyntaxError`` exception. + + *check_origin*, a ``bool``. If ``True``, the default, then sanity + checks of the origin node will be made by calling the zone's + ``check_origin()`` method. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder + is used. + + *allow_directives*, a ``bool`` or an iterable of `str`. If ``True``, the default, + then directives are permitted, and the *allow_include* parameter controls whether + ``$INCLUDE`` is permitted. If ``False`` or an empty iterable, then no directive + processing is done and any directive-like text will be treated as a regular owner + name. If a non-empty iterable, then only the listed directives (including the + ``$``) are allowed. + + Raises ``dns.zone.NoSOA`` if there is no SOA RRset. + + Raises ``dns.zone.NoNS`` if there is no NS RRset. + + Raises ``KeyError`` if there is no origin node. + + Returns a subclass of ``dns.zone.Zone``. + """ + + if isinstance(f, str): + if filename is None: + filename = f + cm: contextlib.AbstractContextManager = open(f, encoding="utf-8") + else: + cm = contextlib.nullcontext(f) + with cm as f: + return _from_text( + f, + origin, + rdclass, + relativize, + zone_factory, + filename, + allow_include, + check_origin, + idna_codec, + allow_directives, + ) + assert False # make mypy happy lgtm[py/unreachable-statement] + + +def from_xfr( + xfr: Any, + zone_factory: Any = Zone, + relativize: bool = True, + check_origin: bool = True, +) -> Zone: + """Convert the output of a zone transfer generator into a zone object. + + *xfr*, a generator of ``dns.message.Message`` objects, typically + ``dns.query.xfr()``. + + *relativize*, a ``bool``, determine's whether domain names are + relativized to the zone's origin. The default is ``True``. + It is essential that the relativize setting matches the one specified + to the generator. + + *check_origin*, a ``bool``. If ``True``, the default, then sanity + checks of the origin node will be made by calling the zone's + ``check_origin()`` method. + + Raises ``dns.zone.NoSOA`` if there is no SOA RRset. + + Raises ``dns.zone.NoNS`` if there is no NS RRset. + + Raises ``KeyError`` if there is no origin node. + + Raises ``ValueError`` if no messages are yielded by the generator. + + Returns a subclass of ``dns.zone.Zone``. + """ + + z = None + for r in xfr: + if z is None: + if relativize: + origin = r.origin + else: + origin = r.answer[0].name + rdclass = r.answer[0].rdclass + z = zone_factory(origin, rdclass, relativize=relativize) + for rrset in r.answer: + znode = z.nodes.get(rrset.name) + if not znode: + znode = z.node_factory() + z.nodes[rrset.name] = znode + zrds = znode.find_rdataset(rrset.rdclass, rrset.rdtype, rrset.covers, True) + zrds.update_ttl(rrset.ttl) + for rd in rrset: + zrds.add(rd) + if z is None: + raise ValueError("empty transfer") + if check_origin: + z.check_origin() + return z diff --git a/venv/lib/python3.12/site-packages/dns/zonefile.py b/venv/lib/python3.12/site-packages/dns/zonefile.py new file mode 100644 index 0000000..7a81454 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/zonefile.py @@ -0,0 +1,756 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009-2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""DNS Zones.""" + +import re +import sys +from typing import Any, Iterable, List, Set, Tuple, cast + +import dns.exception +import dns.grange +import dns.name +import dns.node +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.rdtypes.ANY.SOA +import dns.rrset +import dns.tokenizer +import dns.transaction +import dns.ttl + + +class UnknownOrigin(dns.exception.DNSException): + """Unknown origin""" + + +class CNAMEAndOtherData(dns.exception.DNSException): + """A node has a CNAME and other data""" + + +def _check_cname_and_other_data(txn, name, rdataset): + rdataset_kind = dns.node.NodeKind.classify_rdataset(rdataset) + node = txn.get_node(name) + if node is None: + # empty nodes are neutral. + return + node_kind = node.classify() + if ( + node_kind == dns.node.NodeKind.CNAME + and rdataset_kind == dns.node.NodeKind.REGULAR + ): + raise CNAMEAndOtherData("rdataset type is not compatible with a CNAME node") + elif ( + node_kind == dns.node.NodeKind.REGULAR + and rdataset_kind == dns.node.NodeKind.CNAME + ): + raise CNAMEAndOtherData( + "CNAME rdataset is not compatible with a regular data node" + ) + # Otherwise at least one of the node and the rdataset is neutral, so + # adding the rdataset is ok + + +SavedStateType = Tuple[ + dns.tokenizer.Tokenizer, + dns.name.Name | None, # current_origin + dns.name.Name | None, # last_name + Any | None, # current_file + int, # last_ttl + bool, # last_ttl_known + int, # default_ttl + bool, +] # default_ttl_known + + +def _upper_dollarize(s): + s = s.upper() + if not s.startswith("$"): + s = "$" + s + return s + + +class Reader: + """Read a DNS zone file into a transaction.""" + + def __init__( + self, + tok: dns.tokenizer.Tokenizer, + rdclass: dns.rdataclass.RdataClass, + txn: dns.transaction.Transaction, + allow_include: bool = False, + allow_directives: bool | Iterable[str] = True, + force_name: dns.name.Name | None = None, + force_ttl: int | None = None, + force_rdclass: dns.rdataclass.RdataClass | None = None, + force_rdtype: dns.rdatatype.RdataType | None = None, + default_ttl: int | None = None, + ): + self.tok = tok + (self.zone_origin, self.relativize, _) = txn.manager.origin_information() + self.current_origin = self.zone_origin + self.last_ttl = 0 + self.last_ttl_known = False + if force_ttl is not None: + default_ttl = force_ttl + if default_ttl is None: + self.default_ttl = 0 + self.default_ttl_known = False + else: + self.default_ttl = default_ttl + self.default_ttl_known = True + self.last_name = self.current_origin + self.zone_rdclass = rdclass + self.txn = txn + self.saved_state: List[SavedStateType] = [] + self.current_file: Any | None = None + self.allowed_directives: Set[str] + if allow_directives is True: + self.allowed_directives = {"$GENERATE", "$ORIGIN", "$TTL"} + if allow_include: + self.allowed_directives.add("$INCLUDE") + elif allow_directives is False: + # allow_include was ignored in earlier releases if allow_directives was + # False, so we continue that. + self.allowed_directives = set() + else: + # Note that if directives are explicitly specified, then allow_include + # is ignored. + self.allowed_directives = set(_upper_dollarize(d) for d in allow_directives) + self.force_name = force_name + self.force_ttl = force_ttl + self.force_rdclass = force_rdclass + self.force_rdtype = force_rdtype + self.txn.check_put_rdataset(_check_cname_and_other_data) + + def _eat_line(self): + while 1: + token = self.tok.get() + if token.is_eol_or_eof(): + break + + def _get_identifier(self): + token = self.tok.get() + if not token.is_identifier(): + raise dns.exception.SyntaxError + return token + + def _rr_line(self): + """Process one line from a DNS zone file.""" + token = None + # Name + if self.force_name is not None: + name = self.force_name + else: + if self.current_origin is None: + raise UnknownOrigin + token = self.tok.get(want_leading=True) + if not token.is_whitespace(): + self.last_name = self.tok.as_name(token, self.current_origin) + else: + token = self.tok.get() + if token.is_eol_or_eof(): + # treat leading WS followed by EOL/EOF as if they were EOL/EOF. + return + self.tok.unget(token) + name = self.last_name + if name is None: + raise dns.exception.SyntaxError("the last used name is undefined") + assert self.zone_origin is not None + if not name.is_subdomain(self.zone_origin): + self._eat_line() + return + if self.relativize: + name = name.relativize(self.zone_origin) + + # TTL + if self.force_ttl is not None: + ttl = self.force_ttl + self.last_ttl = ttl + self.last_ttl_known = True + else: + token = self._get_identifier() + ttl = None + try: + ttl = dns.ttl.from_text(token.value) + self.last_ttl = ttl + self.last_ttl_known = True + token = None + except dns.ttl.BadTTL: + self.tok.unget(token) + + # Class + if self.force_rdclass is not None: + rdclass = self.force_rdclass + else: + token = self._get_identifier() + try: + rdclass = dns.rdataclass.from_text(token.value) + except dns.exception.SyntaxError: + raise + except Exception: + rdclass = self.zone_rdclass + self.tok.unget(token) + if rdclass != self.zone_rdclass: + raise dns.exception.SyntaxError("RR class is not zone's class") + + if ttl is None: + # support for syntax + token = self._get_identifier() + ttl = None + try: + ttl = dns.ttl.from_text(token.value) + self.last_ttl = ttl + self.last_ttl_known = True + token = None + except dns.ttl.BadTTL: + if self.default_ttl_known: + ttl = self.default_ttl + elif self.last_ttl_known: + ttl = self.last_ttl + self.tok.unget(token) + + # Type + if self.force_rdtype is not None: + rdtype = self.force_rdtype + else: + token = self._get_identifier() + try: + rdtype = dns.rdatatype.from_text(token.value) + except Exception: + raise dns.exception.SyntaxError(f"unknown rdatatype '{token.value}'") + + try: + rd = dns.rdata.from_text( + rdclass, + rdtype, + self.tok, + self.current_origin, + self.relativize, + self.zone_origin, + ) + except dns.exception.SyntaxError: + # Catch and reraise. + raise + except Exception: + # All exceptions that occur in the processing of rdata + # are treated as syntax errors. This is not strictly + # correct, but it is correct almost all of the time. + # We convert them to syntax errors so that we can emit + # helpful filename:line info. + (ty, va) = sys.exc_info()[:2] + raise dns.exception.SyntaxError(f"caught exception {str(ty)}: {str(va)}") + + if not self.default_ttl_known and rdtype == dns.rdatatype.SOA: + # The pre-RFC2308 and pre-BIND9 behavior inherits the zone default + # TTL from the SOA minttl if no $TTL statement is present before the + # SOA is parsed. + soa_rd = cast(dns.rdtypes.ANY.SOA.SOA, rd) + self.default_ttl = soa_rd.minimum + self.default_ttl_known = True + if ttl is None: + # if we didn't have a TTL on the SOA, set it! + ttl = soa_rd.minimum + + # TTL check. We had to wait until now to do this as the SOA RR's + # own TTL can be inferred from its minimum. + if ttl is None: + raise dns.exception.SyntaxError("Missing default TTL value") + + self.txn.add(name, ttl, rd) + + def _parse_modify(self, side: str) -> Tuple[str, str, int, int, str]: + # Here we catch everything in '{' '}' in a group so we can replace it + # with ''. + is_generate1 = re.compile(r"^.*\$({(\+|-?)(\d+),(\d+),(.)}).*$") + is_generate2 = re.compile(r"^.*\$({(\+|-?)(\d+)}).*$") + is_generate3 = re.compile(r"^.*\$({(\+|-?)(\d+),(\d+)}).*$") + # Sometimes there are modifiers in the hostname. These come after + # the dollar sign. They are in the form: ${offset[,width[,base]]}. + # Make names + mod = "" + sign = "+" + offset = "0" + width = "0" + base = "d" + g1 = is_generate1.match(side) + if g1: + mod, sign, offset, width, base = g1.groups() + if sign == "": + sign = "+" + else: + g2 = is_generate2.match(side) + if g2: + mod, sign, offset = g2.groups() + if sign == "": + sign = "+" + width = "0" + base = "d" + else: + g3 = is_generate3.match(side) + if g3: + mod, sign, offset, width = g3.groups() + if sign == "": + sign = "+" + base = "d" + + ioffset = int(offset) + iwidth = int(width) + + if sign not in ["+", "-"]: + raise dns.exception.SyntaxError(f"invalid offset sign {sign}") + if base not in ["d", "o", "x", "X", "n", "N"]: + raise dns.exception.SyntaxError(f"invalid type {base}") + + return mod, sign, ioffset, iwidth, base + + def _generate_line(self): + # range lhs [ttl] [class] type rhs [ comment ] + """Process one line containing the GENERATE statement from a DNS + zone file.""" + if self.current_origin is None: + raise UnknownOrigin + + token = self.tok.get() + # Range (required) + try: + start, stop, step = dns.grange.from_text(token.value) + token = self.tok.get() + if not token.is_identifier(): + raise dns.exception.SyntaxError + except Exception: + raise dns.exception.SyntaxError + + # lhs (required) + try: + lhs = token.value + token = self.tok.get() + if not token.is_identifier(): + raise dns.exception.SyntaxError + except Exception: + raise dns.exception.SyntaxError + + # TTL + try: + ttl = dns.ttl.from_text(token.value) + self.last_ttl = ttl + self.last_ttl_known = True + token = self.tok.get() + if not token.is_identifier(): + raise dns.exception.SyntaxError + except dns.ttl.BadTTL: + if not (self.last_ttl_known or self.default_ttl_known): + raise dns.exception.SyntaxError("Missing default TTL value") + if self.default_ttl_known: + ttl = self.default_ttl + elif self.last_ttl_known: + ttl = self.last_ttl + else: + # We don't go to the extra "look at the SOA" level of effort for + # $GENERATE, because the user really ought to have defined a TTL + # somehow! + raise dns.exception.SyntaxError("Missing default TTL value") + + # Class + try: + rdclass = dns.rdataclass.from_text(token.value) + token = self.tok.get() + if not token.is_identifier(): + raise dns.exception.SyntaxError + except dns.exception.SyntaxError: + raise dns.exception.SyntaxError + except Exception: + rdclass = self.zone_rdclass + if rdclass != self.zone_rdclass: + raise dns.exception.SyntaxError("RR class is not zone's class") + # Type + try: + rdtype = dns.rdatatype.from_text(token.value) + token = self.tok.get() + if not token.is_identifier(): + raise dns.exception.SyntaxError + except Exception: + raise dns.exception.SyntaxError(f"unknown rdatatype '{token.value}'") + + # rhs (required) + rhs = token.value + + def _calculate_index(counter: int, offset_sign: str, offset: int) -> int: + """Calculate the index from the counter and offset.""" + if offset_sign == "-": + offset *= -1 + return counter + offset + + def _format_index(index: int, base: str, width: int) -> str: + """Format the index with the given base, and zero-fill it + to the given width.""" + if base in ["d", "o", "x", "X"]: + return format(index, base).zfill(width) + + # base can only be n or N here + hexa = _format_index(index, "x", width) + nibbles = ".".join(hexa[::-1])[:width] + if base == "N": + nibbles = nibbles.upper() + return nibbles + + lmod, lsign, loffset, lwidth, lbase = self._parse_modify(lhs) + rmod, rsign, roffset, rwidth, rbase = self._parse_modify(rhs) + for i in range(start, stop + 1, step): + # +1 because bind is inclusive and python is exclusive + + lindex = _calculate_index(i, lsign, loffset) + rindex = _calculate_index(i, rsign, roffset) + + lzfindex = _format_index(lindex, lbase, lwidth) + rzfindex = _format_index(rindex, rbase, rwidth) + + name = lhs.replace(f"${lmod}", lzfindex) + rdata = rhs.replace(f"${rmod}", rzfindex) + + self.last_name = dns.name.from_text( + name, self.current_origin, self.tok.idna_codec + ) + name = self.last_name + assert self.zone_origin is not None + if not name.is_subdomain(self.zone_origin): + self._eat_line() + return + if self.relativize: + name = name.relativize(self.zone_origin) + + try: + rd = dns.rdata.from_text( + rdclass, + rdtype, + rdata, + self.current_origin, + self.relativize, + self.zone_origin, + ) + except dns.exception.SyntaxError: + # Catch and reraise. + raise + except Exception: + # All exceptions that occur in the processing of rdata + # are treated as syntax errors. This is not strictly + # correct, but it is correct almost all of the time. + # We convert them to syntax errors so that we can emit + # helpful filename:line info. + (ty, va) = sys.exc_info()[:2] + raise dns.exception.SyntaxError( + f"caught exception {str(ty)}: {str(va)}" + ) + + self.txn.add(name, ttl, rd) + + def read(self) -> None: + """Read a DNS zone file and build a zone object. + + @raises dns.zone.NoSOA: No SOA RR was found at the zone origin + @raises dns.zone.NoNS: No NS RRset was found at the zone origin + """ + + try: + while 1: + token = self.tok.get(True, True) + if token.is_eof(): + if self.current_file is not None: + self.current_file.close() + if len(self.saved_state) > 0: + ( + self.tok, + self.current_origin, + self.last_name, + self.current_file, + self.last_ttl, + self.last_ttl_known, + self.default_ttl, + self.default_ttl_known, + ) = self.saved_state.pop(-1) + continue + break + elif token.is_eol(): + continue + elif token.is_comment(): + self.tok.get_eol() + continue + elif token.value[0] == "$" and len(self.allowed_directives) > 0: + # Note that we only run directive processing code if at least + # one directive is allowed in order to be backwards compatible + c = token.value.upper() + if c not in self.allowed_directives: + raise dns.exception.SyntaxError( + f"zone file directive '{c}' is not allowed" + ) + if c == "$TTL": + token = self.tok.get() + if not token.is_identifier(): + raise dns.exception.SyntaxError("bad $TTL") + self.default_ttl = dns.ttl.from_text(token.value) + self.default_ttl_known = True + self.tok.get_eol() + elif c == "$ORIGIN": + self.current_origin = self.tok.get_name() + self.tok.get_eol() + if self.zone_origin is None: + self.zone_origin = self.current_origin + self.txn._set_origin(self.current_origin) + elif c == "$INCLUDE": + token = self.tok.get() + filename = token.value + token = self.tok.get() + new_origin: dns.name.Name | None + if token.is_identifier(): + new_origin = dns.name.from_text( + token.value, self.current_origin, self.tok.idna_codec + ) + self.tok.get_eol() + elif not token.is_eol_or_eof(): + raise dns.exception.SyntaxError("bad origin in $INCLUDE") + else: + new_origin = self.current_origin + self.saved_state.append( + ( + self.tok, + self.current_origin, + self.last_name, + self.current_file, + self.last_ttl, + self.last_ttl_known, + self.default_ttl, + self.default_ttl_known, + ) + ) + self.current_file = open(filename, encoding="utf-8") + self.tok = dns.tokenizer.Tokenizer(self.current_file, filename) + self.current_origin = new_origin + elif c == "$GENERATE": + self._generate_line() + else: + raise dns.exception.SyntaxError( + f"Unknown zone file directive '{c}'" + ) + continue + self.tok.unget(token) + self._rr_line() + except dns.exception.SyntaxError as detail: + (filename, line_number) = self.tok.where() + if detail is None: + detail = "syntax error" + ex = dns.exception.SyntaxError(f"{filename}:{line_number}: {detail}") + tb = sys.exc_info()[2] + raise ex.with_traceback(tb) from None + + +class RRsetsReaderTransaction(dns.transaction.Transaction): + def __init__(self, manager, replacement, read_only): + assert not read_only + super().__init__(manager, replacement, read_only) + self.rdatasets = {} + + def _get_rdataset(self, name, rdtype, covers): + return self.rdatasets.get((name, rdtype, covers)) + + def _get_node(self, name): + rdatasets = [] + for (rdataset_name, _, _), rdataset in self.rdatasets.items(): + if name == rdataset_name: + rdatasets.append(rdataset) + if len(rdatasets) == 0: + return None + node = dns.node.Node() + node.rdatasets = rdatasets + return node + + def _put_rdataset(self, name, rdataset): + self.rdatasets[(name, rdataset.rdtype, rdataset.covers)] = rdataset + + def _delete_name(self, name): + # First remove any changes involving the name + remove = [] + for key in self.rdatasets: + if key[0] == name: + remove.append(key) + if len(remove) > 0: + for key in remove: + del self.rdatasets[key] + + def _delete_rdataset(self, name, rdtype, covers): + try: + del self.rdatasets[(name, rdtype, covers)] + except KeyError: + pass + + def _name_exists(self, name): + for n, _, _ in self.rdatasets: + if n == name: + return True + return False + + def _changed(self): + return len(self.rdatasets) > 0 + + def _end_transaction(self, commit): + if commit and self._changed(): + rrsets = [] + for (name, _, _), rdataset in self.rdatasets.items(): + rrset = dns.rrset.RRset( + name, rdataset.rdclass, rdataset.rdtype, rdataset.covers + ) + rrset.update(rdataset) + rrsets.append(rrset) + self.manager.set_rrsets(rrsets) # pyright: ignore + + def _set_origin(self, origin): + pass + + def _iterate_rdatasets(self): + raise NotImplementedError # pragma: no cover + + def _iterate_names(self): + raise NotImplementedError # pragma: no cover + + +class RRSetsReaderManager(dns.transaction.TransactionManager): + def __init__( + self, + origin: dns.name.Name | None = dns.name.root, + relativize: bool = False, + rdclass: dns.rdataclass.RdataClass = dns.rdataclass.IN, + ): + self.origin = origin + self.relativize = relativize + self.rdclass = rdclass + self.rrsets: List[dns.rrset.RRset] = [] + + def reader(self): # pragma: no cover + raise NotImplementedError + + def writer(self, replacement=False): + assert replacement is True + return RRsetsReaderTransaction(self, True, False) + + def get_class(self): + return self.rdclass + + def origin_information(self): + if self.relativize: + effective = dns.name.empty + else: + effective = self.origin + return (self.origin, self.relativize, effective) + + def set_rrsets(self, rrsets: List[dns.rrset.RRset]) -> None: + self.rrsets = rrsets + + +def read_rrsets( + text: Any, + name: dns.name.Name | str | None = None, + ttl: int | None = None, + rdclass: dns.rdataclass.RdataClass | str | None = dns.rdataclass.IN, + default_rdclass: dns.rdataclass.RdataClass | str = dns.rdataclass.IN, + rdtype: dns.rdatatype.RdataType | str | None = None, + default_ttl: int | str | None = None, + idna_codec: dns.name.IDNACodec | None = None, + origin: dns.name.Name | str | None = dns.name.root, + relativize: bool = False, +) -> List[dns.rrset.RRset]: + """Read one or more rrsets from the specified text, possibly subject + to restrictions. + + *text*, a file object or a string, is the input to process. + + *name*, a string, ``dns.name.Name``, or ``None``, is the owner name of + the rrset. If not ``None``, then the owner name is "forced", and the + input must not specify an owner name. If ``None``, then any owner names + are allowed and must be present in the input. + + *ttl*, an ``int``, string, or None. If not ``None``, the the TTL is + forced to be the specified value and the input must not specify a TTL. + If ``None``, then a TTL may be specified in the input. If it is not + specified, then the *default_ttl* will be used. + + *rdclass*, a ``dns.rdataclass.RdataClass``, string, or ``None``. If + not ``None``, then the class is forced to the specified value, and the + input must not specify a class. If ``None``, then the input may specify + a class that matches *default_rdclass*. Note that it is not possible to + return rrsets with differing classes; specifying ``None`` for the class + simply allows the user to optionally type a class as that may be convenient + when cutting and pasting. + + *default_rdclass*, a ``dns.rdataclass.RdataClass`` or string. The class + of the returned rrsets. + + *rdtype*, a ``dns.rdatatype.RdataType``, string, or ``None``. If not + ``None``, then the type is forced to the specified value, and the + input must not specify a type. If ``None``, then a type must be present + for each RR. + + *default_ttl*, an ``int``, string, or ``None``. If not ``None``, then if + the TTL is not forced and is not specified, then this value will be used. + if ``None``, then if the TTL is not forced an error will occur if the TTL + is not specified. + + *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA + encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder + is used. Note that codecs only apply to the owner name; dnspython does + not do IDNA for names in rdata, as there is no IDNA zonefile format. + + *origin*, a string, ``dns.name.Name``, or ``None``, is the origin for any + relative names in the input, and also the origin to relativize to if + *relativize* is ``True``. + + *relativize*, a bool. If ``True``, names are relativized to the *origin*; + if ``False`` then any relative names in the input are made absolute by + appending the *origin*. + """ + if isinstance(origin, str): + origin = dns.name.from_text(origin, dns.name.root, idna_codec) + if isinstance(name, str): + name = dns.name.from_text(name, origin, idna_codec) + if isinstance(ttl, str): + ttl = dns.ttl.from_text(ttl) + if isinstance(default_ttl, str): + default_ttl = dns.ttl.from_text(default_ttl) + if rdclass is not None: + rdclass = dns.rdataclass.RdataClass.make(rdclass) + else: + rdclass = None + default_rdclass = dns.rdataclass.RdataClass.make(default_rdclass) + if rdtype is not None: + rdtype = dns.rdatatype.RdataType.make(rdtype) + else: + rdtype = None + manager = RRSetsReaderManager(origin, relativize, default_rdclass) + with manager.writer(True) as txn: + tok = dns.tokenizer.Tokenizer(text, "", idna_codec=idna_codec) + reader = Reader( + tok, + default_rdclass, + txn, + allow_directives=False, + force_name=name, + force_ttl=ttl, + force_rdclass=rdclass, + force_rdtype=rdtype, + default_ttl=default_ttl, + ) + reader.read() + return manager.rrsets diff --git a/venv/lib/python3.12/site-packages/dns/zonetypes.py b/venv/lib/python3.12/site-packages/dns/zonetypes.py new file mode 100644 index 0000000..195ee2e --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/zonetypes.py @@ -0,0 +1,37 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +"""Common zone-related types.""" + +# This is a separate file to avoid import circularity between dns.zone and +# the implementation of the ZONEMD type. + +import hashlib + +import dns.enum + + +class DigestScheme(dns.enum.IntEnum): + """ZONEMD Scheme""" + + SIMPLE = 1 + + @classmethod + def _maximum(cls): + return 255 + + +class DigestHashAlgorithm(dns.enum.IntEnum): + """ZONEMD Hash Algorithm""" + + SHA384 = 1 + SHA512 = 2 + + @classmethod + def _maximum(cls): + return 255 + + +_digest_hashers = { + DigestHashAlgorithm.SHA384: hashlib.sha384, + DigestHashAlgorithm.SHA512: hashlib.sha512, +} diff --git a/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/METADATA b/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/METADATA new file mode 100644 index 0000000..eaaf09b --- /dev/null +++ b/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/METADATA @@ -0,0 +1,149 @@ +Metadata-Version: 2.4 +Name: dnspython +Version: 2.8.0 +Summary: DNS toolkit +Project-URL: homepage, https://www.dnspython.org +Project-URL: repository, https://github.com/rthalley/dnspython.git +Project-URL: documentation, https://dnspython.readthedocs.io/en/stable/ +Project-URL: issues, https://github.com/rthalley/dnspython/issues +Author-email: Bob Halley +License: ISC +License-File: LICENSE +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: ISC License (ISCL) +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +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: Topic :: Internet :: Name Service (DNS) +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.10 +Provides-Extra: dev +Requires-Dist: black>=25.1.0; extra == 'dev' +Requires-Dist: coverage>=7.0; extra == 'dev' +Requires-Dist: flake8>=7; extra == 'dev' +Requires-Dist: hypercorn>=0.17.0; extra == 'dev' +Requires-Dist: mypy>=1.17; extra == 'dev' +Requires-Dist: pylint>=3; extra == 'dev' +Requires-Dist: pytest-cov>=6.2.0; extra == 'dev' +Requires-Dist: pytest>=8.4; extra == 'dev' +Requires-Dist: quart-trio>=0.12.0; extra == 'dev' +Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == 'dev' +Requires-Dist: sphinx>=8.2.0; extra == 'dev' +Requires-Dist: twine>=6.1.0; extra == 'dev' +Requires-Dist: wheel>=0.45.0; extra == 'dev' +Provides-Extra: dnssec +Requires-Dist: cryptography>=45; extra == 'dnssec' +Provides-Extra: doh +Requires-Dist: h2>=4.2.0; extra == 'doh' +Requires-Dist: httpcore>=1.0.0; extra == 'doh' +Requires-Dist: httpx>=0.28.0; extra == 'doh' +Provides-Extra: doq +Requires-Dist: aioquic>=1.2.0; extra == 'doq' +Provides-Extra: idna +Requires-Dist: idna>=3.10; extra == 'idna' +Provides-Extra: trio +Requires-Dist: trio>=0.30; extra == 'trio' +Provides-Extra: wmi +Requires-Dist: wmi>=1.5.1; (platform_system == 'Windows') and extra == 'wmi' +Description-Content-Type: text/markdown + +# dnspython + +[![Build Status](https://github.com/rthalley/dnspython/actions/workflows/ci.yml/badge.svg)](https://github.com/rthalley/dnspython/actions/) +[![Documentation Status](https://readthedocs.org/projects/dnspython/badge/?version=latest)](https://dnspython.readthedocs.io/en/latest/?badge=latest) +[![PyPI version](https://badge.fury.io/py/dnspython.svg)](https://badge.fury.io/py/dnspython) +[![License: ISC](https://img.shields.io/badge/License-ISC-brightgreen.svg)](https://opensource.org/licenses/ISC) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) + +## INTRODUCTION + +`dnspython` is a DNS toolkit for Python. It supports almost all record types. It +can be used for queries, zone transfers, and dynamic updates. It supports +TSIG-authenticated messages and EDNS0. + +`dnspython` provides both high- and low-level access to DNS. The high-level +classes perform queries for data of a given name, type, and class, and return an +answer set. The low-level classes allow direct manipulation of DNS zones, +messages, names, and records. + +To see a few of the ways `dnspython` can be used, look in the `examples/` +directory. + +`dnspython` is a utility to work with DNS, `/etc/hosts` is thus not used. For +simple forward DNS lookups, it's better to use `socket.getaddrinfo()` or +`socket.gethostbyname()`. + +`dnspython` originated at Nominum where it was developed to facilitate the +testing of DNS software. + +## ABOUT THIS RELEASE + +This is of `dnspython` 2.8.0. +Please read +[What's New](https://dnspython.readthedocs.io/en/stable/whatsnew.html) for +information about the changes in this release. + +## INSTALLATION + +* Many distributions have dnspython packaged for you, so you should check there + first. +* To use a wheel downloaded from PyPi, run: + +``` + pip install dnspython +``` + +* To install from the source code, go into the top-level of the source code + and run: + +``` + pip install --upgrade pip build + python -m build + pip install dist/*.whl +``` + +* To install the latest from the main branch, run +`pip install git+https://github.com/rthalley/dnspython.git` + +`dnspython`'s default installation does not depend on any modules other than +those in the Python standard library. To use some features, additional modules +must be installed. For convenience, `pip` options are defined for the +requirements. + +If you want to use DNS-over-HTTPS, run +`pip install dnspython[doh]`. + +If you want to use DNSSEC functionality, run +`pip install dnspython[dnssec]`. + +If you want to use internationalized domain names (IDNA) +functionality, you must run +`pip install dnspython[idna]` + +If you want to use the Trio asynchronous I/O package, run +`pip install dnspython[trio]`. + +If you want to use WMI on Windows to determine the active DNS settings +instead of the default registry scanning method, run +`pip install dnspython[wmi]`. + +If you want to try the experimental DNS-over-QUIC code, run +`pip install dnspython[doq]`. + +Note that you can install any combination of the above, e.g.: +`pip install dnspython[doh,dnssec,idna]` + +### Notices + +Python 2.x support ended with the release of 1.16.0. `dnspython` supports Python 3.10 +and later. Future support is aligned with the lifetime of the Python 3 versions. + +Documentation has moved to +[dnspython.readthedocs.io](https://dnspython.readthedocs.io). diff --git a/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/RECORD b/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/RECORD new file mode 100644 index 0000000..a1aa736 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/RECORD @@ -0,0 +1,304 @@ +dns/__init__.py,sha256=2TTaN3FRnBIkYhrrkDUs7XYnu4h9zTlfOWdQ4nLuxnA,1693 +dns/__pycache__/__init__.cpython-312.pyc,, +dns/__pycache__/_asyncbackend.cpython-312.pyc,, +dns/__pycache__/_asyncio_backend.cpython-312.pyc,, +dns/__pycache__/_ddr.cpython-312.pyc,, +dns/__pycache__/_features.cpython-312.pyc,, +dns/__pycache__/_immutable_ctx.cpython-312.pyc,, +dns/__pycache__/_no_ssl.cpython-312.pyc,, +dns/__pycache__/_tls_util.cpython-312.pyc,, +dns/__pycache__/_trio_backend.cpython-312.pyc,, +dns/__pycache__/asyncbackend.cpython-312.pyc,, +dns/__pycache__/asyncquery.cpython-312.pyc,, +dns/__pycache__/asyncresolver.cpython-312.pyc,, +dns/__pycache__/btree.cpython-312.pyc,, +dns/__pycache__/btreezone.cpython-312.pyc,, +dns/__pycache__/dnssec.cpython-312.pyc,, +dns/__pycache__/dnssectypes.cpython-312.pyc,, +dns/__pycache__/e164.cpython-312.pyc,, +dns/__pycache__/edns.cpython-312.pyc,, +dns/__pycache__/entropy.cpython-312.pyc,, +dns/__pycache__/enum.cpython-312.pyc,, +dns/__pycache__/exception.cpython-312.pyc,, +dns/__pycache__/flags.cpython-312.pyc,, +dns/__pycache__/grange.cpython-312.pyc,, +dns/__pycache__/immutable.cpython-312.pyc,, +dns/__pycache__/inet.cpython-312.pyc,, +dns/__pycache__/ipv4.cpython-312.pyc,, +dns/__pycache__/ipv6.cpython-312.pyc,, +dns/__pycache__/message.cpython-312.pyc,, +dns/__pycache__/name.cpython-312.pyc,, +dns/__pycache__/namedict.cpython-312.pyc,, +dns/__pycache__/nameserver.cpython-312.pyc,, +dns/__pycache__/node.cpython-312.pyc,, +dns/__pycache__/opcode.cpython-312.pyc,, +dns/__pycache__/query.cpython-312.pyc,, +dns/__pycache__/rcode.cpython-312.pyc,, +dns/__pycache__/rdata.cpython-312.pyc,, +dns/__pycache__/rdataclass.cpython-312.pyc,, +dns/__pycache__/rdataset.cpython-312.pyc,, +dns/__pycache__/rdatatype.cpython-312.pyc,, +dns/__pycache__/renderer.cpython-312.pyc,, +dns/__pycache__/resolver.cpython-312.pyc,, +dns/__pycache__/reversename.cpython-312.pyc,, +dns/__pycache__/rrset.cpython-312.pyc,, +dns/__pycache__/serial.cpython-312.pyc,, +dns/__pycache__/set.cpython-312.pyc,, +dns/__pycache__/tokenizer.cpython-312.pyc,, +dns/__pycache__/transaction.cpython-312.pyc,, +dns/__pycache__/tsig.cpython-312.pyc,, +dns/__pycache__/tsigkeyring.cpython-312.pyc,, +dns/__pycache__/ttl.cpython-312.pyc,, +dns/__pycache__/update.cpython-312.pyc,, +dns/__pycache__/version.cpython-312.pyc,, +dns/__pycache__/versioned.cpython-312.pyc,, +dns/__pycache__/win32util.cpython-312.pyc,, +dns/__pycache__/wire.cpython-312.pyc,, +dns/__pycache__/xfr.cpython-312.pyc,, +dns/__pycache__/zone.cpython-312.pyc,, +dns/__pycache__/zonefile.cpython-312.pyc,, +dns/__pycache__/zonetypes.cpython-312.pyc,, +dns/_asyncbackend.py,sha256=bv-2iaDTEDH4Esx2tc2GeVCnaqHtsQqb3WWqoYZngzA,2403 +dns/_asyncio_backend.py,sha256=08Ezq3L8G190Sdr8qMgjwnWNhbyMa1MFB3pWYkGQ0a0,9147 +dns/_ddr.py,sha256=rHXKC8kncCTT9N4KBh1flicl79nyDjQ-DDvq30MJ3B8,5247 +dns/_features.py,sha256=VYTUetGL5x8IEtxMUQk9_ftat2cvyYJw8HfIfpMM8D8,2493 +dns/_immutable_ctx.py,sha256=Schj9tuGUAQ_QMh612H7Uq6XcvPo5AkVwoBxZJJ8liA,2478 +dns/_no_ssl.py,sha256=M8mj_xYkpsuhny_vHaTWCjI1pNvekYG6V52kdqFkUYY,1502 +dns/_tls_util.py,sha256=kcvrPdGnSGP1fP9sNKekBZ3j-599HwZkmAk6ybyCebM,528 +dns/_trio_backend.py,sha256=Tqzm46FuRSYkUJDYL8qp6Qk8hbc6ZxiLBc8z-NsTULg,8597 +dns/asyncbackend.py,sha256=82fXTFls_m7F_ekQbgUGOkoBbs4BI-GBLDZAWNGUvJ0,2796 +dns/asyncquery.py,sha256=34B1EIekX3oSg0jF8ZSqEiUbNZTsJa3r2oqC01OIY7U,32329 +dns/asyncresolver.py,sha256=TncJ7UukzA0vF79AwNa2gel0y9UO02tCdQf3zUHbygg,17728 +dns/btree.py,sha256=QPz4IzW_yTtSmz_DC6LKvZdJvTs50CQRKbAa0UAFMTs,30757 +dns/btreezone.py,sha256=H9orKjQaMhnPjtAhHpRZlV5wd91N17iuqOmTUVzv6sU,13082 +dns/dnssec.py,sha256=zXqhmUM4k6M-9YVR49crEI6Jc0zhZSk7NX9BWDafhTQ,41356 +dns/dnssecalgs/__init__.py,sha256=B4hebjElugf8zhCauhH6kvACqI50iYLSKxEqUfL6970,4350 +dns/dnssecalgs/__pycache__/__init__.cpython-312.pyc,, +dns/dnssecalgs/__pycache__/base.cpython-312.pyc,, +dns/dnssecalgs/__pycache__/cryptography.cpython-312.pyc,, +dns/dnssecalgs/__pycache__/dsa.cpython-312.pyc,, +dns/dnssecalgs/__pycache__/ecdsa.cpython-312.pyc,, +dns/dnssecalgs/__pycache__/eddsa.cpython-312.pyc,, +dns/dnssecalgs/__pycache__/rsa.cpython-312.pyc,, +dns/dnssecalgs/base.py,sha256=4Oq9EhKBEYupojZ3hENBiuq2Js3Spimy_NeDb9Rl1a8,2497 +dns/dnssecalgs/cryptography.py,sha256=utsBa_s8OOOKUeudvFullBNMRMjHmeoa66RNA6UiJMw,2428 +dns/dnssecalgs/dsa.py,sha256=ONilkD8Hhartj3Mwe7LKBT0vXS4E0KgfvTtV2ysZLhM,3605 +dns/dnssecalgs/ecdsa.py,sha256=TK8PclMAt7xVQTv6FIse9jZwXVCv_B-_AAgfhK0rTWQ,3283 +dns/dnssecalgs/eddsa.py,sha256=Yc0L9O2A_ySOSSalJiq5h7TU1LWtJgW1JIJWsGx96FI,2000 +dns/dnssecalgs/rsa.py,sha256=YOPPtpfOKdgBfBJvOcDofYTiC4mGmwCfqdYUvEbdHf8,3663 +dns/dnssectypes.py,sha256=CyeuGTS_rM3zXr8wD9qMT9jkzvVfTY2JWckUcogG83E,1799 +dns/e164.py,sha256=Sc-Ctv8lXpaDot_Su02wLFxLpxLReVW7_23YiGrnMC4,3937 +dns/edns.py,sha256=E5HRHMJNGGOyNvkR4iKY2jkaoQasa4K61Feuko9uY5s,17436 +dns/entropy.py,sha256=dSbsNoNVoypURvOu-clqMiD-dFQ-fsKOPYSHwoTjaec,4247 +dns/enum.py,sha256=PBphGzrIWOi8l3MgvkEMpsJapKIejkaQUqFuMWUcZXc,3685 +dns/exception.py,sha256=zEdlBUUsjb3dqk0etKxbFXUng0lLB7TPj7JFsNN7HzQ,5936 +dns/flags.py,sha256=cQ3kTFyvcKiWHAxI5AwchNqxVOrsIrgJ6brgrH42Wq8,2750 +dns/grange.py,sha256=ZqjNVDtb7i6E9D3ai6mcWR_nFNHyCXPp7j3dLFidtvY,2154 +dns/immutable.py,sha256=InrtpKvPxl-74oYbzsyneZwAuX78hUqeG22f2aniZbk,2017 +dns/inet.py,sha256=DbkUeb4PNLmxgUVPXX1GeWQH6e7a5WZ2AP_-befdg-o,5753 +dns/ipv4.py,sha256=dRiZRfyZAOlwlj3YlfbvZChRQAKstYh9k0ibNZwHu5U,2487 +dns/ipv6.py,sha256=GccOccOFZGFlwNFgV79GffZJv6u1GW28jM_amdiLqeM,6517 +dns/message.py,sha256=YVNQjYYFDSY6ttuwz_zvJnsCGuY1t11DdchsNlcBHG0,69152 +dns/name.py,sha256=rHvrUjhkCoR0_ANOH3fHJcY1swefx62SfBTDRvoGTsI,42910 +dns/namedict.py,sha256=hJRYpKeQv6Bd2LaUOPV0L_a0eXEIuqgggPXaH4c3Tow,4000 +dns/nameserver.py,sha256=LLOUGTjdAcj4cs-zAXeaH7Pf90IW0P64MQOrAb9PAPE,10007 +dns/node.py,sha256=Z2lzeqvPjqoR-Pbevp0OJqI_bGxwYzJIIevUccTElaM,12627 +dns/opcode.py,sha256=2EgPHQaGBRXN5q4C0KslagWbmWAbyT9Cw_cBj_sMXeA,2774 +dns/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +dns/query.py,sha256=85EWlMD1hDJO5xozZ7tFazMbZldpQ04L0sQFoQfBZiI,61686 +dns/quic/__init__.py,sha256=eqHPKj8SUk5rdeQxJSS-x3XSVqwcUPZlzTUio8mOpSg,2575 +dns/quic/__pycache__/__init__.cpython-312.pyc,, +dns/quic/__pycache__/_asyncio.cpython-312.pyc,, +dns/quic/__pycache__/_common.cpython-312.pyc,, +dns/quic/__pycache__/_sync.cpython-312.pyc,, +dns/quic/__pycache__/_trio.cpython-312.pyc,, +dns/quic/_asyncio.py,sha256=YgoU65THKtpHfV8UPAnNr-HkpbkR7XY01E7R3oh5apg,10314 +dns/quic/_common.py,sha256=M7lfxwUfr07fHkefo9BbRogQmwB_lEbittc7ZAQ_ulI,11087 +dns/quic/_sync.py,sha256=Ixj0BR6ngRWaKqTUiTrYbLw0rWVsUE6uJuNJB5oUlI0,10982 +dns/quic/_trio.py,sha256=NdClJJ80TY4kg8wM34JCfzX75fhhDb0vLy-WZkSyW6E,9452 +dns/rcode.py,sha256=A7UyvwbaFDz1PZaoYcAmXcerpZV-bRC2Zv3uJepiXa4,4181 +dns/rdata.py,sha256=7OAmPoSVEysCF84bjvaGXrfB1K69bpswaKtM1X89tXQ,31977 +dns/rdataclass.py,sha256=TK4W4ywB1L_X7EZqk2Gmwnu7vdQpolQF5DtQWyNk5xo,2984 +dns/rdataset.py,sha256=aoOatp7pbWhs2JieS0vcHnNc4dfwA0SBuvXAoqe3vxE,16627 +dns/rdatatype.py,sha256=W7r_B43ja4ZTHIJgqbb2eR99lXOYntf3ngGj396AvKg,7487 +dns/rdtypes/ANY/AFSDB.py,sha256=k75wMwreF1DAfDymu4lHh16BUx7ulVP3PLeQBZnkurY,1661 +dns/rdtypes/ANY/AMTRELAY.py,sha256=zE5xls02_NvbQwXUy-MnpV-uVVSJJuaKtZ86H8_X4ic,3355 +dns/rdtypes/ANY/AVC.py,sha256=SpsXYzlBirRWN0mGnQe0MdN6H8fvlgXPJX5PjOHnEak,1024 +dns/rdtypes/ANY/CAA.py,sha256=Hq1tHBrFW-BdxkjrGCq9u6ezaUHj6nFspBD5ClpkRYc,2456 +dns/rdtypes/ANY/CDNSKEY.py,sha256=bJAdrBMsFHIJz8TF1AxZoNbdxVWBCRTG-bR_uR_r_G4,1225 +dns/rdtypes/ANY/CDS.py,sha256=Y9nIRUCAabztVLbxm2SXAdYapFemCOUuGh5JqroCDUs,1163 +dns/rdtypes/ANY/CERT.py,sha256=OAYbtDdcwRhW8w_lbxHbgyWUHxYkTHV2zbiQff00X74,3547 +dns/rdtypes/ANY/CNAME.py,sha256=IHGGq2BDpeKUahTr1pvyBQgm0NGBI_vQ3Vs5mKTXO4w,1206 +dns/rdtypes/ANY/CSYNC.py,sha256=TnO2TjHfc9Cccfsz8dSsuH9Y53o-HllMVeU2DSAglrc,2431 +dns/rdtypes/ANY/DLV.py,sha256=J-pOrw5xXsDoaB9G0r6znlYXJtqtcqhsl1OXs6CPRU4,986 +dns/rdtypes/ANY/DNAME.py,sha256=yqXRtx4dAWwB4YCCv-qW6uaxeGhg2LPQ2uyKwWaMdXs,1150 +dns/rdtypes/ANY/DNSKEY.py,sha256=MD8HUVH5XXeAGOnFWg5aVz_w-2tXYwCeVXmzExhiIeQ,1223 +dns/rdtypes/ANY/DS.py,sha256=_gf8vk1O_uY8QXFjsfUw-bny-fm6e-QpCk3PT0JCyoM,995 +dns/rdtypes/ANY/DSYNC.py,sha256=q-26ceC4f2A2A6OmVaiOwDwAe_LAHvRsra1PZ4GyotA,2154 +dns/rdtypes/ANY/EUI48.py,sha256=x0BkK0sY_tgzuCwfDYpw6tyuChHjjtbRpAgYhO0Y44o,1151 +dns/rdtypes/ANY/EUI64.py,sha256=1jCff2-SXHJLDnNDnMW8Cd_o-ok0P3x6zKy_bcCU5h4,1161 +dns/rdtypes/ANY/GPOS.py,sha256=u4qwiDBVoC7bsKfxDKGbPjnOKddpdjy2p1AhziDWcPw,4439 +dns/rdtypes/ANY/HINFO.py,sha256=D2WvjTsvD_XqT8BepBIyjPL2iYGMgYqb1VQa9ApO0qE,2217 +dns/rdtypes/ANY/HIP.py,sha256=WSw31w96y1JM6ufasx7gRHUPTQuI5ejtyLxpD7vcINE,3216 +dns/rdtypes/ANY/ISDN.py,sha256=L4C2Rxrr4JJN17lmJRbZN8RhM_ujjwIskY_4V4Gd3r4,2723 +dns/rdtypes/ANY/L32.py,sha256=I0HcPHmvRUz2_yeDd0c5uueNKwcxmbz6V-7upNOc1GA,1302 +dns/rdtypes/ANY/L64.py,sha256=rbdYukNdezhQGH6vowKu1VbUWwi5cYSg_VbWEDWyYGA,1609 +dns/rdtypes/ANY/LOC.py,sha256=jxbB0bmbnMW8AVrElmoSW0SOmLPoEf5AwQLwUeAyMsY,11962 +dns/rdtypes/ANY/LP.py,sha256=X0xGo9vr1b3AQ8J8LPMyn_ooKRuEmjwdi7TGE2mqK_k,1332 +dns/rdtypes/ANY/MX.py,sha256=qQk83idY0-SbRMDmB15JOpJi7cSyiheF-ALUD0Ev19E,995 +dns/rdtypes/ANY/NID.py,sha256=8D8RDttb0BPObs0dXbFKajAhA05iZlqAq-51b6wusEI,1561 +dns/rdtypes/ANY/NINFO.py,sha256=bdL_-6Bejb2EH-xwR1rfSr_9E3SDXLTAnov7x2924FI,1041 +dns/rdtypes/ANY/NS.py,sha256=ThfaPalUlhbyZyNyvBM3k-7onl3eJKq5wCORrOGtkMM,995 +dns/rdtypes/ANY/NSEC.py,sha256=kicEYxcKaLBpV6C_M8cHdDaqBoiYl6EYtPvjyR6kExI,2465 +dns/rdtypes/ANY/NSEC3.py,sha256=NUG3AT626zu3My8QeNMiPVfpn3PRK9AGBkKW3cIZDzM,4250 +dns/rdtypes/ANY/NSEC3PARAM.py,sha256=-r5rBTMezSh7J9Wb7bWng_TXPKIETs2AXY4WFdhz7tM,2625 +dns/rdtypes/ANY/OPENPGPKEY.py,sha256=3LHryx1g0g-WrOI19PhGzGZG0anIJw2CCn93P4aT-Lk,1870 +dns/rdtypes/ANY/OPT.py,sha256=W36RslT_Psp95OPUC70knumOYjKpaRHvGT27I-NV2qc,2561 +dns/rdtypes/ANY/PTR.py,sha256=5HcR1D77Otyk91vVY4tmqrfZfSxSXWyWvwIW-rIH5gc,997 +dns/rdtypes/ANY/RESINFO.py,sha256=Kf2NcKbkeI5gFE1bJfQNqQCaitYyXfV_9nQYl1luUZ0,1008 +dns/rdtypes/ANY/RP.py,sha256=8doJlhjYDYiAT6KNF1mAaemJ20YJFUPvit8LOx4-I-U,2174 +dns/rdtypes/ANY/RRSIG.py,sha256=_ohbap8Dp_3VMU4w7ozVWGyFCtpm8A-l1F1wQiFZogA,4941 +dns/rdtypes/ANY/RT.py,sha256=2t9q3FZQ28iEyceeU25KU2Ur0T5JxELAu8BTwfOUgVw,1013 +dns/rdtypes/ANY/SMIMEA.py,sha256=6yjHuVDfIEodBU9wxbCGCDZ5cWYwyY6FCk-aq2VNU0s,222 +dns/rdtypes/ANY/SOA.py,sha256=tbbpP7RK2kpTTYCgdAWGCxlIMcX9U5MTOhz7vLP4p0I,3034 +dns/rdtypes/ANY/SPF.py,sha256=rA3Srs9ECQx-37lqm7Zf7aYmMpp_asv4tGS8_fSQ-CU,1022 +dns/rdtypes/ANY/SSHFP.py,sha256=F5vrZB-MAmeGJFAgEwRjXxgxerhoAd6kT9AcNNmkcF4,2550 +dns/rdtypes/ANY/TKEY.py,sha256=qvMJd0HGQF1wHGk1eWdITBVnAkj1oTHHbP5zSzV4cTc,4848 +dns/rdtypes/ANY/TLSA.py,sha256=cytzebS3W7FFr9qeJ9gFSHq_bOwUk9aRVlXWHfnVrRs,218 +dns/rdtypes/ANY/TSIG.py,sha256=4fNQJSNWZXUKZejCciwQuUJtTw2g-YbPmqHrEj_pitg,4750 +dns/rdtypes/ANY/TXT.py,sha256=F1U9gIAhwXIV4UVT7CwOCEn_su6G1nJIdgWJsLktk20,1000 +dns/rdtypes/ANY/URI.py,sha256=JyPYKh2RXzI34oABDiJ2oDh3TE_l-zmut4jBNA-ONt4,2913 +dns/rdtypes/ANY/WALLET.py,sha256=IaP2g7Nq26jWGKa8MVxvJjWXLQ0wrNR1IWJVyyMG8oU,219 +dns/rdtypes/ANY/X25.py,sha256=BzEM7uOY7CMAm7QN-dSLj-_LvgnnohwJDUjMstzwqYo,1942 +dns/rdtypes/ANY/ZONEMD.py,sha256=DjBYvHY13nF70uxTM77zf3R9n0Uy8Frbj1LuBXbC7jU,2389 +dns/rdtypes/ANY/__init__.py,sha256=2UKaYp81SLH6ofE021on9pR7jzmB47D1iXjQ3M7FXrw,1539 +dns/rdtypes/ANY/__pycache__/AFSDB.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/AMTRELAY.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/AVC.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/CAA.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/CDNSKEY.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/CDS.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/CERT.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/CNAME.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/CSYNC.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/DLV.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/DNAME.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/DNSKEY.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/DS.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/DSYNC.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/EUI48.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/EUI64.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/GPOS.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/HINFO.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/HIP.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/ISDN.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/L32.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/L64.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/LOC.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/LP.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/MX.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/NID.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/NINFO.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/NS.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/NSEC.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/NSEC3.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/NSEC3PARAM.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/OPENPGPKEY.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/OPT.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/PTR.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/RESINFO.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/RP.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/RRSIG.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/RT.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/SMIMEA.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/SOA.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/SPF.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/SSHFP.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/TKEY.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/TLSA.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/TSIG.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/TXT.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/URI.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/WALLET.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/X25.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/ZONEMD.cpython-312.pyc,, +dns/rdtypes/ANY/__pycache__/__init__.cpython-312.pyc,, +dns/rdtypes/CH/A.py,sha256=Iq82L3RLM-OwB5hyvtX1Das9oToiZMzNgs979cAkDz8,2229 +dns/rdtypes/CH/__init__.py,sha256=GD9YeDKb9VBDo-J5rrChX1MWEGyQXuR9Htnbhg_iYLc,923 +dns/rdtypes/CH/__pycache__/A.cpython-312.pyc,, +dns/rdtypes/CH/__pycache__/__init__.cpython-312.pyc,, +dns/rdtypes/IN/A.py,sha256=FfFn3SqbpneL9Ky63COP50V2ZFxqS1ldCKJh39Enwug,1814 +dns/rdtypes/IN/AAAA.py,sha256=AxrOlYy-1TTTWeQypDKeXrDCrdHGor0EKCE4fxzSQGo,1820 +dns/rdtypes/IN/APL.py,sha256=4Kz56antsRGu-cfV2MCHN8rmVo90wnZXnLWA6uQpnk4,5081 +dns/rdtypes/IN/DHCID.py,sha256=x9vedfzJ3vvxPC1ihWTTcxXBMYL0Q24Wmj6O67aY5og,1875 +dns/rdtypes/IN/HTTPS.py,sha256=P-IjwcvDQMmtoBgsDHglXF7KgLX73G6jEDqCKsnaGpQ,220 +dns/rdtypes/IN/IPSECKEY.py,sha256=jMO-aGl1eglWDqMxAkM2BvKDjfe9O1X0avBoWCtWi30,3261 +dns/rdtypes/IN/KX.py,sha256=K1JwItL0n5G-YGFCjWeh0C9DyDD8G8VzicsBeQiNAv0,1013 +dns/rdtypes/IN/NAPTR.py,sha256=JhGpvtCn_qlNWWlW9ilrWh9PNElBgNq1SWJPqD3LRzA,3741 +dns/rdtypes/IN/NSAP.py,sha256=6YfWCVSIPTTBmRAzG8nVBj3LnohncXUhSFJHgp-TRdc,2163 +dns/rdtypes/IN/NSAP_PTR.py,sha256=iTxlV6fr_Y9lqivLLncSHxEhmFqz5UEElDW3HMBtuCU,1015 +dns/rdtypes/IN/PX.py,sha256=zRg_5eGQdpzCRUsXIccxJOs7xoTAn7i4PIrj0Zwv-1A,2748 +dns/rdtypes/IN/SRV.py,sha256=TVai6Rtfx0_73wH999uPGuz-p2m6BTVIleXy1Tlm5Dc,2759 +dns/rdtypes/IN/SVCB.py,sha256=HeFmi2v01F00Hott8FlvQ4R7aPxFmT7RF-gt45R5K_M,218 +dns/rdtypes/IN/WKS.py,sha256=4_dLY3Bh6ePkfgku11QzLJv74iSyoSpt8EflIp_AMNc,3644 +dns/rdtypes/IN/__init__.py,sha256=HbI8aw9HWroI6SgEvl8Sx6FdkDswCCXMbSRuJy5o8LQ,1083 +dns/rdtypes/IN/__pycache__/A.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/AAAA.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/APL.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/DHCID.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/HTTPS.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/IPSECKEY.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/KX.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/NAPTR.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/NSAP.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/NSAP_PTR.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/PX.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/SRV.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/SVCB.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/WKS.cpython-312.pyc,, +dns/rdtypes/IN/__pycache__/__init__.cpython-312.pyc,, +dns/rdtypes/__init__.py,sha256=NYizfGglJfhqt_GMtSSXf7YQXIEHHCiJ_Y_qaLVeiOI,1073 +dns/rdtypes/__pycache__/__init__.cpython-312.pyc,, +dns/rdtypes/__pycache__/dnskeybase.cpython-312.pyc,, +dns/rdtypes/__pycache__/dsbase.cpython-312.pyc,, +dns/rdtypes/__pycache__/euibase.cpython-312.pyc,, +dns/rdtypes/__pycache__/mxbase.cpython-312.pyc,, +dns/rdtypes/__pycache__/nsbase.cpython-312.pyc,, +dns/rdtypes/__pycache__/svcbbase.cpython-312.pyc,, +dns/rdtypes/__pycache__/tlsabase.cpython-312.pyc,, +dns/rdtypes/__pycache__/txtbase.cpython-312.pyc,, +dns/rdtypes/__pycache__/util.cpython-312.pyc,, +dns/rdtypes/dnskeybase.py,sha256=GXSOvGtiRjY3fhqlI_T-4ukF4JQvvh3sk7UF0vipmPc,2824 +dns/rdtypes/dsbase.py,sha256=elOLkRb45vYzyh36_1FSJWWO9AI2wnK3GpddmQNdj3Y,3423 +dns/rdtypes/euibase.py,sha256=2DluC_kTi2io2ICgzFEdSxKGPFx3ib3ZXnA6YaAhAp0,2675 +dns/rdtypes/mxbase.py,sha256=N_3EX_2BgY0wMdGADL6_5nxBRUdx4ZcdNIYfGg5rMP8,3190 +dns/rdtypes/nsbase.py,sha256=tueXVV6E8lelebOmrmoOPq47eeRvOpsxHVXH4cOFxcs,2323 +dns/rdtypes/svcbbase.py,sha256=0VnPpt7fSCNt_MtGnWOiYtkY-6jQRWIli8JTRROakys,17717 +dns/rdtypes/tlsabase.py,sha256=hHuRO_MQ5g_tWBIDyTNArAWwbUc-MdZlXcjQxy5defA,2588 +dns/rdtypes/txtbase.py,sha256=lEzlKS6dx6UnhgoBPGIzqC3G0e8iWBetrkDtkwM16Ic,3723 +dns/rdtypes/util.py,sha256=WjiRlxsu_sq40XpSdR6wN54WWavKe7PLh-V9UaNhk7A,9680 +dns/renderer.py,sha256=sj_m9NRJoY8gdQ9zOhSVu0pTAUyBtM5AGpfea83jGpQ,11500 +dns/resolver.py,sha256=FRa-pJApeV_DFgLEwiwZP-2g7RHAg0kVCbg9EdNYLnc,73967 +dns/reversename.py,sha256=pPDGRfg7iq09cjEhKLKEcahdoyViS0y0ORip--r5vk8,3845 +dns/rrset.py,sha256=f8avzbtBb-y93jdyhhTJ8EJx1zOTcNTK3DtiK84eGNY,9129 +dns/serial.py,sha256=-t5rPW-TcJwzBMfIJo7Tl-uDtaYtpqOfCVYx9dMaDCY,3606 +dns/set.py,sha256=hublMKCIhd9zp5Hz_fvQTwF-Ze28jn7mjqei6vTGWfs,9213 +dns/tokenizer.py,sha256=dqQvBF3oUjP7URC7ZzBuQVLMVXhvf1gJusIpkV-IQ6U,23490 +dns/transaction.py,sha256=HnHa4nKL_ddtuWH4FaiKPEt81ImELL1fumZb3ll4KbI,22579 +dns/tsig.py,sha256=mWjZGZL75atl-jf3va1FhP9LfLGWT5g9Y9DgsSan4Mo,11576 +dns/tsigkeyring.py,sha256=1xSBgaV1KLR_9FQGsGWbkBD3XJjK8IFQx-H_olH1qyQ,2650 +dns/ttl.py,sha256=Rl8UOKV0_QyZzOdQ-JoB7nSHvBFehZXe_M0cxIBVc3Y,2937 +dns/update.py,sha256=iqZEO-_U0ooAqLlIRo1OhAKI8d-jpwPhBy-vC8v1dtY,12236 +dns/version.py,sha256=d7ViavUC8gYfrWbeyH8WMAldyGk_WVF5_zkCmCJv0ZQ,1763 +dns/versioned.py,sha256=yJ76QfKdIEKBtKX_DLA_IZGUZoFB1id1mMKzIj2eRm8,11841 +dns/win32util.py,sha256=iz5Gw0CTHAIqumdE25xdYUbhhSFiaZTRM-HXskglB2o,16799 +dns/wire.py,sha256=hylnQ30yjA3UcJSElhSAqYKMt5HICYqQ_N5b71K2smA,3155 +dns/xfr.py,sha256=UE4xAyfRDNH14x4os8yC-4Tl8brc_kCpBLxT0h6x-AM,13637 +dns/zone.py,sha256=ZferSA6wMN46uuBNkrgbRcSM8FSCCxMrNiLT3WoISbw,53098 +dns/zonefile.py,sha256=Xz24A8wH97NoA_iTbastSzUZ-S-DmLFG0SgIfVzQinY,28517 +dns/zonetypes.py,sha256=HrQNZxZ_gWLWI9dskix71msi9wkYK5pgrBBbPb1T74Y,690 +dnspython-2.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +dnspython-2.8.0.dist-info/METADATA,sha256=dPdZU5uJ4pkVGy1pfGEjBzRbdm27fpQ1z4Y6Bpgf04U,5680 +dnspython-2.8.0.dist-info/RECORD,, +dnspython-2.8.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87 +dnspython-2.8.0.dist-info/licenses/LICENSE,sha256=w-o_9WVLMpwZ07xfdIGvYjw93tSmFFWFSZ-EOtPXQc0,1526 diff --git a/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/WHEEL b/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/WHEEL new file mode 100644 index 0000000..12228d4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.27.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/licenses/LICENSE b/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..390a726 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dnspython-2.8.0.dist-info/licenses/LICENSE @@ -0,0 +1,35 @@ +ISC License + +Copyright (C) Dnspython Contributors + +Permission to use, copy, modify, and/or 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. + + + +Copyright (C) 2001-2017 Nominum, Inc. +Copyright (C) Google Inc. + +Permission to use, copy, modify, and distribute this software and its +documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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/venv/lib/python3.12/site-packages/engineio/__init__.py b/venv/lib/python3.12/site-packages/engineio/__init__.py new file mode 100644 index 0000000..4919efd --- /dev/null +++ b/venv/lib/python3.12/site-packages/engineio/__init__.py @@ -0,0 +1,13 @@ +from .client import Client +from .middleware import WSGIApp, Middleware +from .server import Server +from .async_server import AsyncServer +from .async_client import AsyncClient +from .async_drivers.asgi import ASGIApp +try: + from .async_drivers.tornado import get_tornado_handler +except ImportError: # pragma: no cover + get_tornado_handler = None + +__all__ = ['Server', 'WSGIApp', 'Middleware', 'Client', + 'AsyncServer', 'ASGIApp', 'get_tornado_handler', 'AsyncClient'] diff --git a/venv/lib/python3.12/site-packages/engineio/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b66c0867c06d7ba821b1794de36b5f6de62e170 GIT binary patch literal 675 zcmZ8eJ#5r482x<7CAr)WKNYIffg1p!2=yY`4be&`gwP4W(g8J#l_pjaVJDFj?wSpD zc6KJ1S(#X2VnUVV%h3XkjYC)<&;2R$fro^3k7gvVy^gS-96Xp! z%Tl5ptpg2ZYZFm#jannmqciY}=8rr)TX_xRH<=*9FhRxWH+cOEdo1bGRjs zbyi3B0bSfS6n_* z#XOT2swQETWcp}v<=>4qx=tkeGgcP7zR!8Vg^h7YDaLQ%R2ss1=BEWCepJw-#*fml zt{Z<&^0`m5Bfo6ww8(cSw|D$1tI2j5Mo+_pR6fa*EGJpvW1Qt#jq#*x*3Mm4#Lp<% z=bO@5{Z6$nVPznMmaz2=Zb@9i4LxtJItCgq%{wO!>OVv;>|ZMzoQ)GeX#8dC>SuFP V^^MonKw7-9cHy19XS_#y#&6TRxdi|K literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/__pycache__/async_client.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/__pycache__/async_client.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6ee737ff2c607a4fd8893bc97064210ff9bf0a7 GIT binary patch literal 40044 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 BpBVrE literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/engineio/__pycache__/async_server.cpython-312.pyc b/venv/lib/python3.12/site-packages/engineio/__pycache__/async_server.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4575a35240c9f42bfec4c5d7af9c37e5120c9c88 GIT binary patch literal 30807 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

aw>o{5Bm4ej7)I-^TIo8M(>szPn#;v+tIHSpBdJ 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 QB}~OH&ME6<@i$@qH;c^}B>(^b literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc b/venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dbe101c6c228563a4ed342bc2ae56a031e3c0e1c GIT binary patch literal 14757 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%0DOvpIG@ zckzz1{4z(SrYNXc5jzVY;NVv%VWo(jDZ?6UTriPkvIHtmAPtnUa;n3kKSat7r3`8k zR%{W+v4xAB)39}Asg6H>g}}Y;dQ=topx}DIwJ=S@3?;*1!LXT%Z>0kwV!HK(2&grI zjX54xSHm`x@+MH+s%jB_rk!81gRqB<98InQq@i6Xh%wT_{wV zM`#?Y9jY@Y$CMX>5-KqlQ%G0{iqG}6$!Tyy!cz;5(6PXjyF7 zf)Lw-#80OlPSIZA$k;B;($ zUSl_Q+^9*Bi0MnVt}S*Pp`_Q;!6TLoS}kQZO2Dh+*9k^4t(F~v$N)>3?Hy9SO5^() z6_jzV8Mkj!jf!p>GYP>hE-#$aS`fWxpd^ww_!ZLV#h`)x)&fbv*zj`;4&jPC*zJNgUYlD@yF3jA$g*CPrC~EHl^0AW9@vU|0#cD~zHi`w~Ti zr6tqAQA``4rX4!uRAzCh?|SD2dRvk57jZY`rO!15 zc@NDeSq{kYrVfZ_HJelX^hZkw6qLWj&Ac5c(FGk}9w*XIGt_82fuCk*DPtvTcpDul zRBWer(q;&yiIFGe;cD9{`I~oiYAZ^uAZac1#2ZvFSB*G4&_!wLBS|E_BTZeQmCpN+ zy&r`eLO%lQ#eR3*PK7`&VBU#HJeV)fClu0bn(z|;UACh;cfbsuDSP;X0sY=pRykgtw7Es{e>$+0OI) z$6UuRxlRAV)qTu~A9EXi$#s3qZU2~S0)5FYeBzp%>DdDZ3SJJSa~<#1vP|G&_r Bm)8IQ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc b/venv/lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef970b534334d295049eb12fc338afb1aa3124b6 GIT binary patch literal 13653 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 G+5Z43uz literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc b/venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52842e1ffe9e53c07d068580bce0c6e00d7cd3cf GIT binary patch literal 2946 zcmbVOO>7&-6`olxDT<^-Mv)!YO_Xt(KPE0|D@a=qMO|C4QzeEH*t%FjK@E3?;>yci zVs=Q1g3?N^0XOI&y}5@TTA*m-I)@&6ZB9XRk>Lb(wszqnx%g&O4N$m;_PyDqOsj4W zc>rhU=gph<`#pXZ?q;D-ya zPJ4mFy}L9%9h(b$E!VVz>rqM(LU+(Pm&t%!p$k`Fh%%w;NneM^%|m-!=3YdMTS+!bDi_m6)$LTFRXM1(c>#p z*PX6XAi3_u>Zl2L0UaHtVb~-$PtEVZg#3RsRTfy(I1U)_SWSguj65@x@E{R-?1Y+x z+FHouWGxO>Ukg-yj;5G^9;d;G#zW@)Mk-Dq=km;6W zi94BbHcOmZVQjJGhRoqUYe?1&rC>Z{ob~yD=^)2|xZ}uhkBm4ZENHAsN3*61+Kl^y zeoHDj6bVu}YX?d)x7~pNyE>NUX{mh3y`F>zjApfF*LP~m9Ug|mU@tQvoj`FNsG&S$ z!i;5ZXaseX+vdvk2B>wj>#=5_SW9{xsX`WZofaCBh?Y((SZ4i}3%hN1rKQ1GBgBPF zS2EOKw(B!USbxR!SD33q;Arul3o;uVy#fzEid9#4c+Dy#>(gNa-SDQY$LYUM~Qzt-W3w^>|jHRd*1C^fooc~Nyi7;2_W ztXYqHU2u8GmKOpaj)2z>hT%5KVoXN}#~WT8I|;sT@~($YN?ZUu@R;ImC>ej0 zkeXbB2%hQs+HK2|N(E}UV)zsmz}Ar7&G40t#h7s_K1)sgM+P)C`% zL8T}iYQTwT^0?N@ZFF@kKFZDeLav#oqQdCIWlQDZ&4|hnON_BS)sDy;F2b-$?#Al{&y`Q_k+5fvIfBoe9jcbRFUN6pWXJ`L;t8nm_uW#pH_&c$H@w4K-TT?UJ zPoKCxHTThD+lAM6Qlzy1UWOD(|GGDh9QQ+%e&P8txl%k{O8s&2cs})~d>WvOmm*Vf_t} z!8p32_JOp8vaQgRC|j=!*U|NQl!KH@5+lm+PDlCzT1U#!Ya^f<6|fG5Rji%{5r^Ry z9)e+$d|~GLu) z?783uTDI1oJ0sgc5Bz`yqk|6fWKRNeAb#Oi?16=H${24v5v4joCvG6v7)&xndDz=E z@)_7Z3cqj)hSZ>)7R|jPwoBx-A}-xQL8q{mbF37j&v1aeXxu3&;jUHVS_#@ zHO`_{_WJMV5ygD&CGdxFg$8OY8kTYl3qjLtLVE^WYkGVIz;(Wvl=LogAP^%U>KDQU zRbU0^i|8oDVHSX^R3EXxuy{dm9vM2ukEZI;m&2?Yb{ccAuxQ#a)F`&JC~Y)>)w8ft zGcb(SY_t!cIq-TCTn!4>6I;}C!1pcWeHjL*;vC6NY>yqdS)Bf9;fvf;zgqkx_tNIv zow3~Jk8c*I-W$9-*v=lflP{V`l<6tRZA6ImlCGSF{~?NdZ=Zrc9`c7#CI(RhT4h>E z7$dt81fF^x?@T5z&Ndk0D{!FJ=7M2}Y_n~dg?bUsu(<;tns`5tl9IbIOBqj#QLbKZ zcA;6zdR<|DL>WEkKr2$1RMEI@Dai${ZCp<*o_p(LeeMS*=iZoKcnts(+H9;tT(pqm z$_u!N|Kng4D$G*jxjngjKl|NwAiAFXo;m_72pi$sFx*Q~N^g^AJ||PR$=C0s>`TWk z9J_5l^|$fKpFOde0o1cnmkw?n{0o@}7_7 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc b/venv/lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00e833bdaa22a439944fd3020b0c4940e1adb73b GIT binary patch literal 20423 zcmc(HYj9iFo!`9xF5VDu|SneG=pq!7VU8E)4*(|qU*ZEBlD zGo9)0f9?Yy4LQ3XI=vFlJ@>qyzyIU>5B{#K%+BHYyI=g~!jV%P_mA|UJtoD&ue`-` z+zn3RdN_%f3^9Jd&|~1qZ;TlSOg)0?H}_Z&W{Oz{Y&|v>CdBLm_8tfGo8fo%IN`TQ z)>zp+u(QgxR~D_a}QMXR4~5-{>q+8=6AyH>G3ds8T{TJFV96R{cT!P3vam@ z<(H#OUyqNacfnuXQ_cMDSj|9fPaX4Dz`w3%9rIVhU*FTf{2urld)D)ufogc7P_pKe zydPjZ=;>*)afN?UlT`J(sb_<zq{kfkDmJ!ounhwoVV* z#KP(k)}V*Avam*kt=GdgOU;sqa{a?be@p0qk>l)VE<}^!KtdXdMZ{z(B&Q;hm>5cl zp`;iK$>$@nVKF3z6NAIzU?`c2h<$QmKuler#8NDJt~F^-45kJ{ax$Woaygp1z*6)@ zLa8A+k`(30K;jb0Ob#m{dQFGOO4 zA`%RQQYi$8h(P`rsvk(m5r4pr5r~L`u~1yZK%^3CV_7UqOv#j9qsEAcj>uvvIuKEF z%8^hKO-j));(th#qJ4cxh<2-^N;Oh<^>JD=gm$WzMh5*v1tI)tWrQ(!==jdS71oqp z^gc(kLlY2@<%BE_M3TwS`AE`!J`#_}A(}p_P_GQxDX}tJBE~i;o{ywbkt-=06*Npd zpGZjPHFd*ISqCt-DN&h+A?j3eD3U~PmEmV4V7^mHG0|73=CT|`p>grtuy|aKoKMKZ z;JVlnoRF|2;#h=OX>wq2m>PstDYM4z+`fHBJDkVXRvr*LVlj~=qGgDzScietId>{K zzpgKdZ)y7@z@1>- zRt3H)&ji<2fl$ktpjL@htAT=#CgQXt;5F;z*`5u_p@B#$BIo%R!YceQ5HiCxavZ$(*tDfh4@{95tqS`6C0D##>KkN}I;` zH_Or{$#`)y$H_HmLqFMPgMYAMk&UgqEY2<5jHwMHMUv|-$G5ScI|T&hCL zO`(!;1fLRLw<@g`x{fObAqqnDuP>NA|i2=n+G6)tmrOK1WM<#_ZGfP(NAODl8^ zbO%5JQp1)z5R?FVbz~f|NijJX2}k>)VP&UnwTpP7DeP(xnK&VDY6uiVWaKA=T~8=8 z6dw$QFA^pe%2&{{FDfTfKolu*g_XprC9H@LRxwIZ$U*=O42^^nai9lFe+B_1+Qo3yGnaB%X@^(&9<aViekBFZ5BM8kQpWi)cbe-jPNhz@wg*mQ-}jy>$$ zb|Enk*%nU3&JAx1W8_l9+px|qZ3C0CZIBV0U4b1tw_zY7?X)O}Xl@G=OR-HSDtU9T zFOmvh7^x2`Dy38I5`Qg|m6Hvi?ULFNkSTLwCDOgPk{r z1_vW?DKE$&(4V|T2D=3EnHShj$=l9=RbqphH^w4yHnMrEnn=o!|{P1?Kr+p1&eDAS7vpLn{!^6LKS^aTX7yybVFSC?S|<8oviMoU>xW z`@I8Wor@K|iIMq=4P&Qr`wxyC$yKi#>-=YvMX3Fvl5%D#;W610tx8__G8Pj5I)6|BkGt(V6U!3Zh z3e4B;pvZ})@qL;7nexxkhsy6g{+5*8eZ7CiAPeywC z5Pt*PPGT@U%BT6S+n};ZGNd_zW8{GBRR_|*$f}){K;N8Xl(;%24bM`kUw%_JTbp_A zx3oHmU5F+_F(4eap%IK`USMFV>_kzSbvsr{ESd@;T7C?<68KA=fiuSCoURGOL`CNK zSXa*Cd}H+0(bxYJLp8B0b2(FiGB8b(3g-$DLTgIh@Z4)-pGR<1$87!z+QaWN7P#!2XiMMomRzM*)Sje_4O??#%u zF`T-RHxJ5@zUY-C)s5|#^L|_25>zL&lbHhBrqzJ7g^4nBa%Vrn6j_Rx5z7Z^ZZ>(=QCr%YbQQ5yeA@C3 zCmG%~YUy6XO7GVRk=_ulUuV1uVQH&mti`967bMK3#ip%VD4058SKEr>Y-{4`QKy!% z=-scgV1>Z6-XorS^9u46FLE{6AItZz>7mwA)b_E7OPj_Fk}zrlW6lK&?5qi2M1|Rt z7VzcJzs&e@>R%Y$pgxkroZs9%(tI+03A;j4lno*%wHykA5oFRRRy=Vzn;-IiMhO+% z5t1nq)ywmaAow4dfeHvxFqVj)CxA)jjmbzVZ&mhFB+0f~-u^-;Hl&fakR-{Q&@8zL zPTnL%k||KfFx4h+r67U0F>o7+bNzlhIEzTEPkxf6r3&N|l=mb#lv+MT4((+50Gzyl z9zjN?F~LSwA7wvVgF$eUsbFx#t?q|_7BYt9$$x|cE~1jFZ_JqPT5EE3^%>KfuEh;4 zKWc%eY{^soC1WsWY^Z<4Y-WCim;@qZrM85` zUjjP&4c&f&+<|m#`useb3A_lkl2oV)o{m)LVk8-4voYe)#=9u+CS|CEGsZ2sJl9?s ze`Tg|$Bb*ojCF_7LLFX^hNMQ;LId1Kw~%f3b!i?_S&7Ywei1EN^AM^}@bT@B=(T~B z*<5<#{%h-r|DQ+HqR7>cl`gMe-|mH`m+s4XPE#-T>o8E1N0;a!^WJ$xOBBd_;{S0= zN&F`$6=aO?SnYD3}EF@i*F0b?$(|U>57D$R@E!|cSH`^-W zX6PqTW6e`?p16M67i*rv=m5O!-E0r$9Sn|x3@{Zubt-a(5=2ls6i*fKd&E5yzZg$k zhRn|ZS8RRAxBBf0oIZ^j6?7#`&D#OwQlu|56iX>Z8=*Iok5QD6kRjnRPzD=CTVL*G z;n4f0BCEM5%b=Wq?ld{#(m+5B_z7|(A$$@m<#1g)Fn%E0I_GH0HTyGN*G`R}nn;lh zH}7o9l~r7OdHm(<({pA1T=VAlPu)CKoMNt!;>288bIw^oxfh&Gv(Bce=J)+K{nOu^ zcWz&B?wxh+y(8Rn9?}Zzo-1qnBgsvFWBt?NW z+1>B$dwbtKYcs>WTSkS&@Af+tss|0J98vf;nWpGrWjJ&$95_vyO(@x4PNw9uY}y{$ z;f3Vcoq+b45lCX^6q;&;&XBR;Gq1D1{SPV%cKDsa;CF@~lCT*087c(aKw>Z=r-tR{@F~9x z=N890%Luc4h(gKvQ~V|S;JnT)JGsUUxhh|-al^7}y->YWR=q6Xy=>-OP0JSYSvgnj zvWVjTX(sj@M9xYel4LtW2zBFHH zl>boBdZFPn#28*Ovo@KF+r+pMpH^DoUAi`+GN4K%#jbh!*R2V_C(5Y*YN*yCo&&|O zD5$bZfkAV;i{^DFJWp~wo7=Mxq)3I4(hD2{WA~<0;{KAq!9y6SFmc2DN5;!Mz=Ooe zZkc8dvbG_xU8WUYtbBNAJ4lCNR|e%nBh7`isFHw!HcwCW3o3wy>3@<+YOZ>3HZ;YF^RMwQ74#e1$-#Ngfp$n@OM0mW_&vL=d=T$eSpE zA~r;+%`OETaq6cXS>{ayu6e75t`KL21{86HL<*VMD!Cm_-mDyCD%+2d&X*{zi5%LO zP!H;aQNdE-LLvr2xO$fo7>-nYWn}&R&c;RVVlr&QRrBF`B9?mpM~KC14af z!6R1>EP87*T}xt1M!04lw@>W8@%ZH9v(|>MA!OaRc(y)3R%kr(PNHMlTq(n*(itCI;+0%Qgy6sc;zsPU<68*s4zi}PykUA`8+vnNB2V>)+s_U!#p#O(dA6hPE<$G?HOryh2_e!+|7eo$-=sg<{vT zb;)QgRl15^_*T(-oo3uax+F=5pc$JsGYM1NP%K)uZG~uQXXAk*1DOhcXd|Vy!k&ou zR;I;Na$45R2%AcB?&2>=AVV`^655pkXJdyz{ZV_*uly$j6%9DwW0cCIa%jec9+Rp` za}`tX)qcqhotXgLS%p+73GkV@Jeg``)+P~l8Wrjz_6~I_ndk`a zoD9A%$w~@|Nu2*J;F4)Vx)M714Jj2c+aU>R{)4h_H0y z@DgR}JwP_Bd9^n%ok+pd1;rvSYe&JPMK_IMXMA)(r^lgZn1Qt>E=ip9qbgPmS-7B5 zSyUyZBw4Z0*zZTpp#c%f<^Adm;Dk^yx7g3RxWD&L*ojx*sB+%lD_&+cC(L?$1<)JVgxS^emiJC6spYbTB>TCvTf zgV0~Ijl_UP-(?c zrVCW7ZbQJJkGNCzfXflO&m4Qmw?8zrxBD&fegx;MG46@W(EMS2u{K^vwD{p}Dg+(>K0J8}VzCOWe z3)spM6RI)1FwCffVTJ*D^a&v*7Xpn5nrQQf{0HjPjFO5(pC+_GeM%~}to_7jt$99B zTLm);YroFu7ZTz80UG;JOPXpbFNABpG&8Pv(*r%awUioWRsIsnmqPwwgK9)!ff0o< zG=he8ND`Sm#3Ps0osKhN*b<1nq}pa?1O#--mIg`^wo^q}7y##N%&p{RMnu;M`45Cngi zLG<=AwNY>HR?VxZix8lf(c7yil9WWnN~PXj!n@vHrdlGy1w9Y-tQT7QURbQL`cz^h zqA~GQv`>LQWuKv+4F8!X3D#vv1(pfv7!G4Jl+$#Kgl?p#j-g&5lusuy#f5`_Zs@Xz ze_~r-(JNfVXWl$C2!pnW+(mIm$jOj%l$>!mep``1&6xQ|oigOjDo)stO~YmFpcO|O zDvXTOtls;D7+BeI$+wXiyMF_>|In{ohi-3tt2~>W>U{si%@g>F@Gk=y9c zbY6RA{F&_0Ij2ab4sA=6qJY-J>NEm&)3 zt+hY&PaU0>?zG&kd*U7}2iZw-F-{Y~WD{jY5vvRW^#g!WoGSw<)oDO_{V5o?0c*K_ z9oh8+P)ii14JwKfXpGQO7+g{T-e)|`!5~Se4lqdC#A+r=wdQ%%hZdCxFGVqu{A=7x z+?&s7rc8c5)Pv!8mU!%f5m(U)Qy65;f~~iAv!XWGN=q=g6_5vwx4*Yn#~$KKbzEmg zzQjE!CUY8>O$_N}@Yfo%OIB25w5%7RO*Z(`J}M2&Co!PFX_ZhY@CY!3ois3#+4-L9 z2M3K1#y}TPpD-E>Iv7@JRyZ**KukO=uOq~ouQKaXQVx*VKxTSmLcON2%PQkI0CRB} zb`vl~UY%O)nZiU84gx~Mn6G>-cqFFqXADvh!=!UozOP2n;L5aE6lSdgcEQnD6x1R} zSP%JwiLvsZPzol!s>cm}3Lp*j>ca**qY$j-wFq2@!TREoBghMrr%JA(F)PixH)e!o zgJARF(00MuHtTGg-gDQvgMGEmI$NiOyUqaQes|Tik@1o2p7##CePCMn*m27-pi_+`O{T+A-JKk#kplVdOR+;g^j_^Ds&F1>fJ$ zQOSK)x%CKd`~}CuU8N7u;g*14^)4>Om6c|#sh8L@`tHJ~`hf2PC&P?xG5n(j?Aa1K z3p}3NL?)vKjq7999L7;oks`Ka`5H!mv#R4bmP;5l!+f_)YeC@+j!~$^CsevBjzP^> zPc{!}z94Xb+2;!a$1qyOj9CqrRVBHF8MG-&h4c>!7;l9yZ;QY}3ZgYQ0MfTXG+-(a zT0cxaRM$Oc8&UQxkRqzrWdxk}`)V@6_Z@%u z(#37=;u#(P&~wCYU$wAw@(^PS3kwI$T>Hq=rNc{+45^5$6w!K}Oh9jMkd$+Dei-Bf zW^&BPt<{W4p`@3080S=f%QW;o>Qh#G1AV{2ArYLC*g{Q+`7&sXlbcJb=P zTxIo*Et6YrY@6IR)iht(4C9w7u%*{u%p4<)sAodD`by62%T;(Mz6mqH>TKsieP9;< zD+0G4yVLR4`(j=6=}KL_;?SlNs}ij> zr}cJ?TH@`fqowFh0H3PT8d!7^Mn{5co-~j1EsQKZ+PL7PPCu1`z-=yh9%VuOI+SDO zo0XLhd({dU5d_1rL^2YD#xi^%lEleT85xg9&w=u%qOcj;kLpG?DJWYw)X~&;>~shG z5aZ7RFg;2lxxbvrad`_}*Mgb3d=r5((Hcb*e2#oM}<1}h;*(q)htF31EI z>~u#aJ5OeWtq=^WLNIny-f~UiR`M%{53IVsqD=pmoNhS9_SdR_=u-NuE0snX*6d3* zBzp9D41Myyp(v&(f~l@_BR(0Qbu{01?)ZoL_USOp4-S2DXukeP=IO=C#%%I-`=@9A z_QlU$yj$5l>SRLyOylw*UHDsCx)^S&8#7eZK^*yZ*vZd1UFLoQKMvlZ6mCq*=7-N z4aSTwkcv7E9K>(dXr=_10mFLqT_^)f@j}|tV|u4zxKJBV>IzZ8Y7~~Lu+SdC+ChtK z$vvevzFdi=@6dasdsbPAo-MjGM4BmeOX!K=`Ou`b764)^B{#`O)271FbWvhL0*1s;;#PXG8dcV!&E}4q8|$=SeTce$}m_hpwI;X)FmJdi-|@ zKeodGHFtS4rkt}Z!xN=`Zqf%pfO)HHsj^Bj-v5%TwfQnfu+68AP3@aLGQE3h{q5$T zZT@8QeB}Wsi>hjG1Sf+FRhwt4HcxxztJ*WiP~1et__0jKKX__$p6UfpV9pcBc{eV2 z+h)CO(>w2a@l_A@7nHyA;}f?|-1Y65aqr2wsuo;LbFkup^1JG-WOmo}D}Qk?=dHc* z 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 aV|D1=uyMss?;X3@ds%G1E8;iXtN#TiC(m*K literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc b/venv/lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..174f8f1e4862da1e5237fd27562c4d15cb137060 GIT binary patch literal 34504 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/click/__pycache__/testing.cpython-312.pyc b/venv/lib/python3.12/site-packages/click/__pycache__/testing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0e9d019eed5fd033dcc02705f747a69fa0f90ed GIT binary patch literal 27390 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 K#{z{6cm6Lo={VH@ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc b/venv/lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be0505e8c37fb2e91a765507892b667ec8b207b4 GIT binary patch literal 50019 zcmeIb3wTu5ohNv2y;P+tsYtDt*ooyZQc+cc0;vk$Dgi1> zF-hDLk;iAr=`3>FS>tYR4DIw7@AQn_+r1k*-P_~sp52is5-AnZqwKh6w)gePmK;0m z?9TrF|8rkeDv@L|>Hfaiy`XzfoqO(ipa1*(&tI37l}fn&`Uih=_S2Uo>EF-~?J5xg zdG1N8BwdrDQdI7f`sFTJrm&^Y(r@jusbPDUo#Px`4t|z&mGINq<>aTUs}xUbpS$1P z<>qf}eV%?#R~d)x2$y%2BkYKl^m+SzT|N#w5w7T};ION&vftn3=WuCXpg-6ZACelQn%}=*KzzB z#J8F1v7TesBDUQWyMbfZA$Gkfc4PFx=!WNQU7PsrM!el*db^opHzRgSY`NZ|Ezup( zozVxLw|6~&@m=wz+I?MH@oT02Ya4#G>A$w)SG)eJ1HabmzaGS|4f?MgT;^7kxeX(f zF^`Q1N4I~kL+^oIgO=7^e@8D`<(xa5NF>wYbUc|zb>g=(8cxU3@%|XXC1+ykbnJ4P zq7!QP?`RGpqOuzi-C^a-#qL-ll8nX^XAtX7UWg^SQ)wj@?!PJLJbS~@$HGduKbDRu zc?D*fSfcW=BemOw0`oJYf%!Gris;Z(Rc776$DAzZRAnNZ7i=~>*AbLIPz{TKRB z!(`%cI@X^n=a(l3FT|b<_YK4%qP^m-E&_S(f5Pu;QcUWSqf(dU2>_HXYt+(Zi&|s$ zsBH({M(qF~4*V+NUrvNv94ZAsapRYVe+8pu7;zP;aP&jl!8-Vr@x+qTLn+k;!(NH9rxENEA{v!2Usx*YFS4sBs z_mO0OKPBpmCya9dIZ$6{FWXUrcq)Yd;XX|4=%CTeSTuBcFcgjTh6nmgWp`l0pY7;TJ6O$K{T0IM zpn8c;4;AfBDxKO;G@o!J61$MrzdsvBXDcziZq&d?k_Sg`35|i#Gl$c9kA+Y7VX7px zsen=AYK7vxJjgvgk@RKs4o!rf9-c@&J$kNK`~x)B=oge<&|4AodomIkP(qi^#u8cq z)bIL0k!K6W_;=PeKbv;3@cBcPA2~oXhFe+c~@Wmk38??Te^|Fyyd2Bo|F*ElihIX&^}& zlA<;Z$17z+(mChCa)vDl%aA;bzadN1{)**<)auCm+e2YY1qG8|09^n~j438dBstKB zX&Va-q+-1TeIdNYxTn$Ep);{WOaU%PoY98|iIlay;RuafG#TLmXKbM^dYYkb~qjOEGogidgoP%f6$BX>ANdum~|*^%}Id zhv*yBKOBw5q8%NfZSCvzw@MKGq0oTj%5*$IKvAI{Z?)t~dD?c<5)e`LoQ;0uoZa0t zSGv1%E+k3E38myp&W2M2YD7|N&X$Vx^(u{22IZH+%#cvmd;8X%P4>suMUs7|2iHZ= zt?9va7csZi^~F!GyTGt=Q~QRE>uA=ly%3I^51)yp)S28?tJY&IxHyx+xPSQKAC8n>J+`Qs(Ro zT5@(CkDL{dB}L_-H$%#DN`L_&Qg6Dc0AAr(QT0OjB)fWGyJE5FzLAAh3B#$I5yxNe1YI4FQ1}b2B6{K;=m^M#s}C z)>bjNj;kqm0Dlc#W6p#k=-hDJAbbF{1_Vbygj?h+x2Px<$JdO#^(r0~goEg4&;T?R zC`|z zHu?%077bw{aG_j#&NQz)(+C*_h;`VC<&h6Ll@>&0DvX7vc6TqObm0b=*(UjdBObBp z2;yiLmX}y{yAhyOcUewZAdrxYf_kYK>GP(F6tG?`;Q{=;*) z3FZy}YvY3Uq>g7g3oKa)r*v1ofJbc~kmrFaT#koujTS>E?r1!+t*RL>X) zW^f(gcT-qR)C2KEq;CM!kTE%OMC{l(52X%c*=iAJ*P>iZ(SU1Mwvv;8X=gf^hbYrv zOr3>qzle`hOK`(1@<|o`7oDT0r@Tuhy-Tj!rb62%L))|7jtOVSLWr)D5KZ_xkCc{& ztV5RX$r{n{WkSfCACC2bM#C_G)8u9ocu=7OF>$DCJgQDA4KSdUgn0!HCXr&RN^O*= zDNNXzkPWS6fu3!Yd&UqSwA1eqBuV*k1F=;udF#iw-mu=`amx)|R(qPQwUj=L{v1cH%AWp^9yaibp zz0^UnkAaxFdbGa7>QtdQuP{^Pe+kn;1d%$^jQN)5^wTs-Y=T5=9S>1BMxOg#-kq%D+g-;)?T)oSVNyy;X{=~IaeSWc?q>jERsOG}yBn-+ceheS>Acn6K zyK{D#v~tarqZ5_OvbNRJjZIe$j69UJHO)FCcllQi1KMuhHesuos9T@4ZJo3E?b~M8 zOUqYHH!YiWmDo4UcmuO`JdxZNnk}J_Qz~!ZceABZsdvtexVcK%{-`|Xu-iSe)A>13W5$AX-Ytvp9#YQ{ZUMqiedRd@xFvcEH#KCB5YW6=YOl2mTWj6G8X z;swIsX^^Q|SZ+H@pT3KPDK~Ccqz}uNX3LjPIG1y4J8xQe4dtw$Xs~!_E-FXp_6Xg0 z$~gh5f%?*eVy+cTpJSAcZhwHk6p108m%#g8IXYu6``9Aeo9C>Oqx>TZiTE!?vglfwDDfACY=x^s}eSwm|6Yh+2^bDK5)h4xqS_T-m{aL`3iS2=%pg>HSWN zgXqa~+8fhzNb07JhjxX79QQg`xGVm?22?ZP&;;LGSHF@tR8VqH-uh2{Lo9X}H=@Rubt*wG9U-BeNPeJyieR0#;nXwO~ zd)IEul_;?beIVtr_^8`+rA!L&GG_#o^E{D=LxmONsGK7jqaOmCFUD*I^T$GExc(Wk zPepM9sqApNT2*n|_!C+G@(IuK&+d9_Kb9P>)>(_SZ2R4s`tbuVmyX##yX#-_u_Tvm zpRQ?q)$@|)`sZ%Mvo+gZw9i@*c@JQ>tm1{kR}YWI#-F&p??;}N*)rsDFGXPD%RayK zE!Uoq^*ioX%MBTUoArB2?C;ww2)^%-DO^%T;U)AE$eFqzQwQ-lXgy-Vo_<=85gIum zfR&BJ<;otURrcZ5T2ctNo~Bnc;}`+v%G8M)A$qnO(6a}x7<%p&Gx+nAly0O7R^)*~t(dn|xu zcETl4{HZW)&<1E0cp?G#8-b`HCbX3#gN2Y7JbT_Mp}B7*ltPnP2&foX-JdOQD3*J> zI$>zNY2+TTxXz*L&=K$H;$m$G2;pUC&izE^W5T zSOUs&S6taYYqP2(2t8?>=}F^^CzBv}&X>w|A;sm4BKTate$&DBo?!Kxj=YpUWSuw6 zG`ig1UFc0Sk&8EZv7}8UQ6#&Lf ziwC$oBTH$w_WPVi3mH$nT>FrH$aa1!`hVM^HLx2sc#07efU~(Y7_k(udm(TZUu)p@ zSNn$AtbL)JMhjY7`o?H|X^SQ@J2y&15!!Fo=51e?$G=3i}T{GGczaOa_-07oG> zkf|x`QVq=fE#B&2rrHE%lxOfpQRr3xM2PTG==QJhmk&fL>~)MR=t)3?o&-ejWFUg) z{7Ts_NFB~24~SrS(?GcnU|?#8>nOlQux%j6;V6h zmP9L~4&WAN)E_Ou(-jRwop_cmz(K*N3-Rt~RkRdO4-7)wc$&&n^R0<`5MR!8w-ceBa&r-DOCvaRC0h75f_Bf21_3m7TIz#Rb+Z84)Ls6zE9F3D3b?Z2Eqjh z$PEJ+wCl@m(x5h)HrO z#(9w@5@Iz#W=N%3W~fda$oMdx5IbUC2x+LIXUOwJ?J=V6TUFs(T| zw+O0lu52n-PGf3J0I;=;slI|jl;?3%xjaF#DMV9eyrQ8x`wC@@lp+xnBcrW5-aYhB z6JTIH(@kw7wjVm{8IsMQ9W@Mez+AT=GfRSCY{MDhChQP?SPC6T=SFWB~xqWiw_7N9^XOqt5X3kb6 z5xLStN}xaFxhMjl*`;FQi2c$FmWU-TMJ%Tvj)5HwM)w@`V!?%Y8A=N^|8H5oQDeh5 z!?3rBV3a6yiBw>)CzkXnF2t89z)}HUU=4-!ay|x#MvKr4%QQ%%isMzuKBv>OqxAZ` z)<8CL(e%n}8%D4k%Yf3-k|Y>RW_;>GBj|FHg#9G%52e@`FLX&k{U9k0E6D*QSFL&U zk~G-aMn*$ioL(csE2O3pIyB^|BNRg%4_4v|`jSPs5aiIz$oUF0*A@rMc`13`;>h{C z^$()mYHw!hw8gUU9gRZj5V8R;-Xzs8dA04Owx7ElzEY4`71gsYsmwpRe=5*A8EBoZ zZk!HO&)TKn>N&|8EdNNdmX&{OrL^GIOMd~L-B+5jA+Hze*V^*n21WAx`WpoD;fBF1 z6q6BYvqS5~XQfLSirn!%&-jsd311t`x-;Twkg{yv`^XFJYU5uAmEFOT`$7 zlyAs-PG?0#TDt3ug>}fvs;v#?ncb-&R?H#+?*!K7Il?Faql9-Veim3&&<7&9F+UE3 zp?+2KU~`MJFcZV41Bp141`8&NsFXbsqM}`sC;)Nun@kgh`LE z6EWdj9pl1EK{YvnPMRqig5$l(KEiEGnwYBa%McfI9T(A=Ia@3-(4Vs%z@2T$gzPC$ ztcr{qa!##KP1AG*+3*_5d8kRTXtyBQXcE$@7Lt|`{K{0T3&Uu-+P$XG%e^PsA<-XC2*Peax*?;N(;K{$PAMwn%%SRp>Pfab^I=N))#FBl}^-WXtnR}pKn16 zFJeWQVYr$}>KtY-HMQUZ^tMJUX9#-3RBhO5*iM>OkiG`AwNPvYohvKI8b7r)FlPD2 zdK*~#VHl1VTPtUzv`_o`9BGgXE`#;|eM2^CPaG_s8(K>GqJj%5bG;qsh$JYuw7Q~4 z9BPlCMZ=CX32XQoa%k;Gd%TRj^q65g`n^s|X*`u6;*FnrC~D);azr6^co{orz5^H$ zE#a_}!YWR*UeZDiU`JPH&H=I+Hf;CgA&3bH*yo&@CGkD^*?aQvJ^AT-@~PJHoc98m zuCwVTn`-7fsk6yTDlSS1G~!WLsxa^*1sgA}oE>aU4CcV+lJP{&Mmp1+lPpmP^D0-V zREJnRVi`I~p$u!(C?uUsqOGBvbX1h{ishr(^QCfb5s{+J4mmlVB54n;L0n=PlF%zt zsV+m4&EIBPfy50%-sbjAxkHog(DjFJyW4I(F}3B;?Jb994j%azeed`E+-g};KD}e_ z?|NQ2d41)}UaPx;nO`qoYPwoLlA{Lr^`mQsuyo0af@D4@RS)s~l9CYJAi{p1^` zUORQ`q3nu%_?4|YaP^Uyx<-0Ay>!LY((RK=x4#>iSh_vCbnn#C!;?!7XO}+mBj3^S zJ-_Q8ef-;1w|z(Ftd{!nkw<_jJrytP`r5AX*i_S|$)-)WGPj%dOb4sS-J`?T_fGg% zO?XzJ-!^9+@}xck zkdWtIM8J$Z4a4bTWEhqV$tw#kESX{wH}jIw5vuX2wq&$r@jYEa2ZU$O7`76R3QM#Q z&AJ9xoUTe(gll7{DzP^_=kNv0yUn+IuX9>8Z{H9V59 zVxpog>s>qL-8|{tJRPXNc699M^}r$%C>uBm<6GaBImtp~lf_jk7!xzV*2nwbql~ZG znH)!vg%{L1R)fs`njIr+MkQ);q_JqDMiN&;4m)CpD%1MBbI4X~Mrb3xUkB}4E(;{D zEs%v4S3F6@PzBdpH`XVk=1)Ah5kjZEN3o)4=OSYQwp9NY*A09t9?Qgu^aNECj z-Y!+teq_aqUvNoZ2~@*CO~{uCC4imL(L*N?22orBr`S-9bk5F}dbzR_>Es2NpwNzr zWP-HN^EpYW~A`VLH2 z)f0DBB9&GBoIaYhBKf^k1!{F;>7G*Q{nGY54_e>vka5pL4?4{_gQVC8T+}2rt%HWN z4r&%f1vs5 zJY$}$r*W>t<}3ZwDY+}It{fe>{`9+RMpkA^56-%5u0!%dp&U(4OjUVqGXgZ3%^S;# zu{+u{0%>g_a=H};4JBfAx7?FkVeD%om@c}iK=~_lF@_{jD1t9i`|G*=(-k$Z1jeJU z_Px}Xtz9!w({^LegtvXd*}f2I)1Yw0XAtj6L`U(lsnRuqtTf7F^n@ z0+%5b1+Yy_*dx<&G>knLhy*n0KkWqg z&&~9eICF(SS|0AF>CL$RAOa@bUqg5w{3ANG@Vcr)_N5Ptorw z+9d*usXs(EIXR+`<)=#B3MxSPBgAA{RQ#83Z(D>6xJrwm0iOsm;C9uK@yBmfywmV@ z!@K8xxZ&{cJ~LT$#lXC-)sgFK?o=JQdT6G0`BZJ|WNj;8;mDDnR2`@I<0FS=)@=NdchmLoSo!!fKlClV z?cFrt-1G}P*qq?~TJN4UwpRHh2sc7)nMSsAW`d9Gs&o^RC&}{sl#uD!Xje0vSSe@l zjj*sM8Fi|#)}8Q;fVo5l!(!YFz_ zWx|6(9AIH~R0MJ|QWh)j8T#-rr4%ODCNtNg^onhU48t*!!U)=V1BD1N3X3Ab=ZwOX z-4r@Yw>WNQd$Q*!l5T%TFKEWWp6nw_07lhqk|zL5BZt%}AD3sXHv8J?>doSL@CZL= zTxD~1#DC(k!St+LTG2Xd_1R0O{Y&{d<61pu$M3o3N_)dxi_gAnuGwiXoefmk%T@E~ za@BmgoXrjpKjW$<^Xc-r^&a~nd9FdWubXuQ?VH&mnx144O;56j#uFCNpW5+cgv$+y zpbh#3j7yh1NARVXg+kO7Emf@&+=f*`)B{@t8*CBEge}4X+k}BA6_+7;Gkgh|wT2F#I2PLW z!1@hpd72r+=ZMQqU5G{Ey@RBOUad81wU)4rwb&5r(5`qkP!J{$nzA~inllLYm-liB z`vt6V?5F9ALxZpGn@=i;p#7EFzOB@NazP>crZcK(gI28~9YbBBn;}A(K_B(#J=>1; z4zVe^xX0M8qjm}`1!!wU8nzW;M;vWpyHCfnr3h^u>oss+R+9RGtY%(QkWtR;_qy?TY@jE268Y zk4?W%u6Vd(#iJc7PSA^A_HoFRSWFp(Xrtmb@+7V%WeE0Mf|x!7()#zw8XmFY8iYEz zv(=h$YE{crXd%5)MwDJOZN67K7-9=XxqpQWQx@Db6w0;+720ahtqpDv`Xpcv>GYb7 z5RSB!;QXC=k_A-8(JFBc5m3qQ$@iw8fF9(v6uxxP_Mt)hTtuo zuqiZ#&$l5k8N*XUw2Ov|8?_q&!R{0zU%|#ue->PCs@(&w&B|Pxty8&%Gzy8hm2uoK zA}pxob}f|A5-Q9D^meh7RyZg4V`|nCyvEQ}RE}=CwryLpqT{ZVo9(?fSV2w2HYPSA?+-%LW_I2SUkM?ooZPq~)wf2Pp z$O)1O>pg<6)oZ_W3d*x#8{U>^amEvUs!N8!0^6_1->4a~lZxzwWFyfQ)uZ4t#7=2y z0$;_;)<#R4eZvltTlv1(Igc0BuZi`-;ArC6;KPZrFFkT@>EDopBmN$O*GF03~{s8oq;b$ zC}c${mLBsb@6gYkbR!9e@-E%zYfS;j)0XLBnsEFo=ThftEShtx0G9NB3zdH@_$1@g z=d$|Qe@oMs7QbOv{P{cXm29*-Q&o4ZZ>$f%*0prx;4~z)H5(>tHr!hAZdJBs_sHQH zZ_T8)dAd4u8+-jAV_FI?C}t7XTWvSnvZ1Zns%<0gX{YA}*HzbDPhi^j(6n#QT^~$c zNPCm@Err;stQ=x1Z}4jG_?8>>@17s&&6XdT_Eb!Jyq|cTn%+hodsJ3jyhdo+t2+>s z4-meOA+Q;>2=UV$YCS3LXrn=`pnF&0CjcVB!c6VLZKB%7)jd?!AxdjzL3pg0%_Q`b z@e%+b5=r}NAALnUNvo%D_rg(nRltrg6T*{(Mo;h`MOhDSIo-ynMUNuDtVbnSj}};q zU)euzv)fBQDaW>!IqYOuJLj<3Yi2z`Fc`G=lAdI0X$$WU5VmvS{BsX3NFKT!=wT(0vZ8Mi%Jop5>%0LJ|Hsh z3?o76Dg(TzVr{mYhI@f$)n-#?0IUeDPtI+M#T@(*s?9h~0Fdv}Po9lDrCRZ`KtkPu zs7yn#5zzJ={DkJxKSDB~HirxP6o2)Uf5oJK#r0U$-!|cC6Ns%u*RgH3>!dC2;p@yxf1PfeIHSP1m?2Xhe`1W3kj8u5j*dA$5mG%9g zjJw$H(`qkVB{a1KN4}0iq%>2O)9r`aZJKJm7QcQ6J5pC;6Z5Riqw>h~q-jo1n&x<7 zn$O$u6gg2Q#b@{z@y5*a8uL0@Vua11s1y5G?byfay3an=QhsNJ^}apoj@rN{J9K{8 zBlum z*svpY6IA6(-TZpf4)qAER-hxfoO2@7OJNAh7U%)(EhR>9r((P~P_OIzS zL${ydwqVZ-ll--X&C*H#|Bc3S6+UEDVW1jD#?EPf6+dc1c$7`6k6<0x0ad?`kMv6i z41bKMU&;;?b%lpzZSH0@Ys5PBQuIJ^5P(>nTGEpe%~nKJ2P*>=2+^)5!GPSwN05n~ z!b54sE$0}xK$vCGeqM|@X)Lmc zr^ZfAtB=a6Ypcgr&$=a7?L672LWt$7ExNZ?ACxPYvot8s-$;ERR1N#ot4{1|29zbj z-|*zdnwlbm5tRnOuqs=~Q*rTuflaHen2smmMtrfsA)Qc>k~CP(vP0!7F^s%OA8|L^G!7hMlVHi@Q@t~ z%+bld8rhNO-~9(%nP!2Cb^Wqxuc8px4L!>b0}tEzcWVV~Ltxz(PQ%YBFI2*>Vu2D2 z%vt(k_)80@IKwe#5OXHYfHCJFqNe{s0rH0958-<3bgWwx;}QV2fD`z4RMvLN)X@hA ze*n~uGkAtCD?Nzh-MpGm>WV;@jQwjCfCH5)u%Wk7*b`5X09%V)H6NB$Uj5wFZfy2G zaAQ-pY~6@u8kGL!vCG%f6V+=zmMk?j)1K|)n_u1edpk!Se&La?J#yphTjy_{pDy)` zL|-_2_3WJ8vY}?g0rX$f@E`M?Q-U!^$JaW3>pwomzXPx^LG*EL+qe0F98bj>%F&6Xao67 zUb_Ls!1NUMKM>|po%8~zBK_F5WEfScUlXc_(ipqku+Iy7^6`;a3Od&ey)(J~;#XW1 zBM;?q^u$fo1D2g%=By|)XQONjXwv^3X*1OeW~Epg@Eb3sN|6Grs!#GRgIy<6ZFx#i z!ks)4n=vMA&4-A>>&jAq8x$(0bc1V| zf|36}@I@xHaOCsBAKdCosc$3c6$v`i7q(s9HsP*^VB*!X-zyvQ-`G5|cRE-#s*FYf ziMGA8ZG82Ff5n7n#jll?sF@c}U5(tZjaZqq(SLxzX&Gx7tsCDrzU8`nefjm^_4FH; zU%Pz$+=O?-gmc5f*+}EavoV9fVzbda>kQK|lWXT9^9&=oh*VEQc^GBP;HdpOETp7LA4`rl(hVV)_e+$P}a5g-iv<07$8w zNCTNl)3R63{oc8;(i^8o><|$D?&fjZw>qZ$D<}OcCn{H6f96JDs&(gN>&|TJLlZ5# z-wVE1@*XU<4^KD`b6?)HcXl2;c<`o&XVpDVZ(kCd%2X#|%wE8rQ{^%K5+w>ogG1aP z4(+8EXXv(vZu@Y%=~O<(BWLFfl{tj(!Oizk-UHnC^oBNo$y3TaB{#C5O^-r!Q(DNB z8v0{K`UJ%k>caa7Y;yFPZvQK?O??vq2yB2I8<)a8&U9@P{jaK@E%VsxXUb}4!J^jV zZF${n35A>zB*`ucl~Tw}Ay`KTI6dg@((*Yk;y!6`L6=<-IwIFZ%` zJGl~QWn*Uw{F+m~ygX=zYi>lWhdWQ+M~D|RXA4GAOuGFv=?M0dATPj^r&A2p0xvNv z4|xJfg~)_vbKx$3I}J76c2l+$NI|YbpumUG=#@w3ZL&ScMR1zp9n^-8@;itQcQDHb zC>#~iFgr`boM_o`A~C}sqUhsrC8%JF2yClE9bopt*y7EO%+WnP0?mtUU;-7>u?p%& zOC}xIL^9PL;$7#wL7da2dQ>t3*u19)gklf$;|c}C>{K5LUy_20odcv=RVfP6m}>b8 zIwdUjA?VZtc5ACyvOvI;ulAd?$v9#II%>Gw1!m!T^05G^3rXQbRVQOq37AS(=%f+1 z!Q29-pimal>sf2=2He{vB!*P{0W`{>b+jrNg{s>|(U;_S69kf4lwp}_-Zx=5 zd>6YX2w8HU>J0`>$33e>Kot$|?%>;1mjIbEbyWy@DvzY3rN(Q6SVEuLW6+Kg%!6x35RorzVvB7FqcJ^>-+_Xb8&SD%H6wOBR~G!Sr9 zc!>~bB>kFH2o32iq&>uo6*Z}07+4^pE2Vts_Au3B6Fw#L7!PFB4U(sx9Pv2p_0z$c zSt~u4tP{_z5At)y<(ao5UMxJyrTD`86(CdjLBba0*iyJ_(Ce@|(jwAYd5IXfnaDd=SPx`Tqkk zFlh`kf7?Jz-&#+gUEc~1NA#f>3|M2r_k{rk$uWRJ9$I>Oh(+tsP{wZ!3c8sLQ(>Tk zC#ksUazdK11;SgXlfQ{YCFFyUVB7Nqrvxo+O8JmoaU1sF@Z_hi@-NGSqzHzV;VyE% zw|XBj;=~ZEc*p1@;(MqU1dE;`r7lZ%h~&$;$Sd0c+8Lo~Xqo#dBKTH%klv(|Ri!X2 z{`MQIMi+b!(c<2PZyzR&`qZDoQoqC zrnMQnoTcP(+{`wZI3N$E;J6Wwoa)0{R)m%T$uvoxS|Azc0vfvv1TPcW6i}&b4hk}x zgM!ZHpdhq4NR;*`UJ8ENRAN7@lFd|n9^_Reqw*X9bb~6?4*kLo9+*BX_`|-QSIXcQ z(2k?N96Ru(ONu$5C4wg>jmhg;pnHP$h&((wVQonsom@DUt2|e>7Z$!czRQ)6!7m)k zpF9YilDr`&ug_TrW2u}ak%Wsu$augQkvAYP?9limdwX+oIw#{kn3EICK0>jrdwdpW zBDv`S!vkm~QhB(PunyzWSGeuQL>n@B>(r#P3WOVQL8oFW45Aqt;Cs4?nWaGF;TCVr zcdeK1w^u`h2*6)*m<;gusa_Vss(Gj&veh2b#A{5tl8g=To5((`$E0zN25*H4Zb-UUFxmo&W%19JD1H-mfK(41%D> zsTujcwe&oxj_2WAiNF|F3yq_Zf^}zXt#)LADt_$QizNDFX{Qg=amArw!tz_s7YDkK zyiotGd6p4}7!ZardPf%)3KJV*Es9jh5F7%eUc_h$4!nkz^-A1wR+yi`Z7p>R8}MVp zJ_*Aa2B>CSY_|`^7F9h{o8KIKNCsZ?>Q4wlkkA|;wo16vJ9)>wjA7JWZ^M+gWzyR+ z&|Bb65c^qZc&J#x7Uod-$0mx|$ZE7<#r0$OQ9;T-9lOK*JqIa{!rf@-2Brfi;BcU#^50d~jIA6W5M~U2eDU4r)UM9SU7gvj$Fgf5 z8(o>Le0$c_mOl|9Pun z=lhJO;-f}yv*XGmqf6!_uiY^l60YhUl^=tFgl!4jjaSZcaJIptdSaxfF!vCi81b8T zS>QR3%@BeWQ>a!ER7k&2L%IsBnutp>^FkDc{AQu=-ijxlIJ}<$D6#rPBIaxZ1Mz4c z9Y%Avh z5&Nu-cASf|?DCy!Tl4LsHfDXBrhFZfzK-{T6TXhD@6ZRqiN{XBw+b0VA@RKwb?QIy zJtTd9%bu;)_aBgP&!egET1*}jI8nAyhp@!u1YVo)Axoh%bqm|6L6rX!4OKSK=PU4~ zu)JacMNt>1FtU)y(gG@N6te+e=zf&Fs+|=*Y2ne61kHG|IU%0&%k+gudE_lTb?(b^ zKR~8t0fj~=ciCe0VyLW`c?3vcYs@81(lwTCwA2WjLm;ckVQOjA!vYQm46@uHp~-2g z$DEGBaJ@<-_Cn{VUM9KASs+5rQGGcZogy4w18BWJ26hO(L_?;-Z;7-dcb3?>f+J6f z)1;$Zi48^Y63!1L38Xk{mQpa41yV9RfHQ0f^0Gh7NFUs?2(P6~YysPelf7ctM1zxb zK=ZYPdjW{lx9oe5vx38pIQFE%O1mJ>Wvv3NEMn@X%(}b#-I|t1sF1(;7+)$E@i~U)kwAo!K zol|#ObeH-O3O^0ktk92BK~xcHP<|I{{+l8ER;9FBAJA4IZV<)`-$iK1#Qv>#1Y&1# z(zHqipClo0AI^`46V|>kodlFfE6F~1Mi8_xx5n5Bq}2~jN~3X5t_~4x zX*zt2U8Csx&(IlS=Fp}yDvMhKY@CRO6_S zeg=n5(;h_8PpKG9DC|$<67dnSL@DS2&}(qs2mh8}hIE0=fP(uerivt{y7|??H z700IcV#}R6oelL7)n+Z6qNNI)71QDAhIL9Y8KK!@u31wey0WLoSa5>*gHtq=Mdu*h zGasvr8GMS5JoM3`|m-S*IJFWvUx zR@~~f>>ygH3;g_6q+12N$Cj?aTG+O_G~3py2K;jK=`e_-U*B{uf!xYgO^6034bgO35WkYTkc5X&jKgAP`_=6e`jN{M zo`$>bGKw30BI~XnPu*te$8?zwTXPPN9iA*}1VO9m)AQIDT{1rdA~1i8+M8$zU4lWm zf4@cHheKm%m|0n5qnef{wzAoj^Clmfi|n~T&liz+Ahw8&Dq?-2#^yVAZKw+bHpkX4 z)x#UD-~3*LhwSG_JzH>TwS_O5`_3B75Zx{nGpt(62CQGvoh1J$TgBeb9K$7O+fmrx zt}Na@vzN|demJ$3YEOUym>c~Hc{ONlHJ(}?+Ee6(qd=OpG}!9-z%cB>&n?wrj3<0x zE!Tb-tC0+#hMeaJ2o+o~wX+p6r%`Fx`9JCe)yQWQR$w%Dq~&M5o+~g$5K4iP1vlP_T($eXp!4ME~J%55LC)g zDHB{|tU=l3%80h_P9~tX2X&wG^8-HGKp^(Tv9llh>J{38q|kiMVebW{f;H1X>TIs$ z*a=oi3BH(?0dIn3#06JZ%jh$f1FfM@8{Gu(!J+cYsc`aWijC@O*>Wt;eAp~!^DxL! z#cS&b&}_x`kf!1=^xE;@9rqGul0WoSTyu;$CaTxn3TAy< zfBne*pLzXvgK&CX3){WzFKxdbeWU-i{#z&CdHU_AKM4G*`v0MR-fF3;9JN4!6Q~}2 z2&OnwffbX171vKst=uuWa>t#(&JU|=u3a3vINpEbc(!`|gm3+aO|6j6ji#>+jtyRK zeq-%xYhe#u|91U*C;!FM?>~(Umy};UHnL}AXtq-F`A44nv44r4@9Sr8JoDD2o0qaH zx84bCqf&z-50gXg8c}Zk`x$>=(wbn+g%6NTK{5e z+kqDAf8Sz9I1dopFn;KHd5-3Iz9TGlE@h2cu@e?RnyxdnpTr)HV3dTUJ`@&=445|) z@yY66;uq;aXv{d_yTM3MO7d2^#KAX2jDmg?bS<46N)g&YuQd5V;HBY9Tc>U{j}w%I zYGVAK<%z7+>+F^a#|5Grps65G1}f>#=|v@lRmnE5LSBy#X|D=}RlD&bqh!X0Lf$qd4|*Brpla^N9p}uiY3vpDrC+y7l)Ssp(aSVxD>jRGCfTB z-N$Xu!$_2~(d5dN&>ftXI1`n`(|mFT@0JpBU*P73z&2qyp*oF-9$Q8%aK+ z15xXtH}2pKw9{?vZymaM=&fTnkKz2&@pW0-gMbOan&~CWryEz0(Pni6nO`=}lvj|s zW+Noq{zfv_Y(%<>dNS8+gt?}-j?6V1VXj#b5xxNulm$vX^s}c&cs&A_juSHCVBXzhHzeHaP>VPk3%A;kt;A1UPG;;BeW0zx*fd!w_c~V;ZFUvvWLkTQ*;6QUYq%uY`d>YK_ z03>20`BGCNY#i#88tjMl?Rl6mDfoaclhp)hEu4cHi+bS3Nx7^sStH6+f-4Xx_2s;(&rH< zC{6GRlm{7WsD%!e(&n33T=8^kd*}(%u^t_C>PJV<7llbTEkI@yuy~UpoSPLz4ODTa zXeKX%yt-4*@&N-Kp?#{!3bm9yfRd1w-s`JJWI{`omW^Kb9$}{e*HY(Wu?wNc2GeJg ziB-@Vh_k))MyD_eXdEm7sSzY_H7!V${->I2aEQw8i=Spz>HgM(8}z!EC4sw2av9;7D!tXMj%?r_FCTFN4_M z-(P#Tv1$C`%TJB$|K?#>R=jZPYp1Y{cGvIkx?58_zUJj}ivJM7l`p#}C@3&gT)${4 z@pS$x1b+KMW=tb}vgvi8N%xyd_vAO#1!D-W9g7brjU_}U1p@6w$DfifD&*#YHVe7m zqQR!^6pISE$*SPNu?MH&jwvyg(A+KLJ72(_=;y|sn=NCj;1W1rz*c3$`GR>ZQp0%& z*Q1(?3c{}%qDvM>8ojTFH1a={!hqz1@`p;AoD3jwr5OSted>Z7%iw%&j=w{&KF3xt3!0U_92kvVGSOr5?3?s1xm#V!`{uV~t6L|0t!Dd`eY^GD1KEvxaWcb%Z{LhJFrs>0G@}d>M)IWk0j3wBId(c%*sJ&r zFCCRwb4Aeu-ofSMkS!`-l*l37s3rABNNFxd%~4sBR@X1=fpzp|6j#g|*f{Nqk7m(P zWKme6dz)ZCA4bM{DAjM6L!Arh(R$!H$2me01=pMER8u<9u3*7S{4{g`CVv-Xc2{uW zYoas?u5*nA5%{U8{L)LbcgECS)Z$Y**Q}*Bo_Q@^X~b)x-?+v4G2n}MEcxxWxJ7G+ z@l+HO&{}Q8YoQ^VX%>xQ68Z&eMR2rkxo>_UHybLC&y|rGj zG)%)XBSW+d^H(acY;X`yXnr^j&~n>gn6o*?Q;|){wZhsJc8O@JA%lj?Etis z18xRVEYHbVAZjEOpT}wujL&#faw-8ks`Lj$L;V5BW6)3@soXzenSqy&_ObTcl}E0h ze53odZUo+~eD~Rj?MFsR{%3C$2&yS>=(aa>H&}h`b7P-VcM+V<23w(;w7JVF)feN( zzx6a?YNSBTwOudndOi9^-)nuhHonvGcE_ESyU2r@Ys_^w5S*xbARE{^71%Wy*oAYi z$h!E|?w7i=jqQK7+A z@5%-qnhG483>;jv<~S~4VUJVIr@SjCy(_OPS?}rz=jzYqu|xv(A4@LmR3NuhFFre6 zzhtU@<7EBDTN|_W58MtsU@rOBACOOZtV@vpy%a&0_bY>sG)sToyz%a8uDaYe~ zY?N^?t`+_QKFQY#0|*x*sLK(rlhpV~y;3k^g5M(Buw7`AvCURDSz&FmBRqnI`S+zw zCg5XMB(s`;4oJ|&h#K^a!wwVr1=?jsA8D!x0tf$}P&uP zIK&u=LyWLE1Udz$+>)?cHONu-KQRhnCAG2#Wh~HN-)j;&>(hg@GqQ-SkGg+~72d3@ zKR{LG&O1n$D>C|zi-y+t=M>*!PoQ%YiLcsrFzU(MK*aFw!~ZfEIMsITs&kLi{q z4CX|D_0>(#S3{k@c3lx&bxq@R9sCM4vUj1z=|&ur9hz=w!+kbbZ(lY8_d#|%fn@?U zvn3P~`=2Q!wm(zILk@?^C`9Mg&3Y;1lY+Ig6%?wJ0xM_z6bc}2j`IDaqZCevYTWkV zba2hA70(&hsyREJvvr4L`2-$LN%%Ca0FcI8x%UAlV_2dwr!}^{IDwTPhzJ4S)${-m)PY<>;fN! zA}r&3u=RlqxA9R*Xi13-y{ztPh`{-v*e?Ln@7|aKohKKM#QKRPue_gM^q_2wj%K0`?DmL{a9K+QX&N6cS*=Ve)(7v3Xd4uT;*goJss;;*vtRmz zPcWehq+HZeRU|?&P-%zN6JxN)gCfu&Fr6yWd(<@|U(tJ_(=avk%YhK4syufD0TQI) z$UsX(93>BP!C{NZ1Vd++^n=s$^htx-A?KD$!dVa)l$Vh!h;pi=b{59huu^iC^k53b zND4Q zBFI<2NLj_r`o{0Dzjr&GiqpK#Jb;5A{7av=tru{g32l@Y(HMUlKw0R{EPPs`@^ut%7JQ475*tN-64P7cc*GslwPbG1ha}&w-+sd~ zzW3Eb-#Ii{zg--P{N<_|S$H&554552RZid&vk?-J(V0@Cs1C|^1tQ^8Qai*3S%P*s zck|qx`t4c&_KC`lcX5=>jtQq~m1OR9S~onmi4w@SpDH>()o)b~URadx;ya#s3#v+H zeHpi?DlBu-gS)7%B!amjedw(m-F&TMtmEs?O*or`st#}U%ZU(I%Yg#FYN2CNO|YY= z0A8CEaKx=Q&=41sNPft6ek&@w9^8Vif7VNs3~?zOdRt)JPF;hmn0o?t*`vE0xQau# zjT2jB$#7mUVjZ#cT7Y()hQJ-An_;FX1j{?>_+E)t=EA8CbnNWRbUX_I4jWDk3)Bjk z43LL{^UC097nUVty}@bN=3+Q>305HNlD9qc;II8Hu&Men4##zBCFfie;{26=g4*Y; zH^KnC-nJp|ZHvC*DvTq`0s7zu?yBvoRco!f=KOhUj&}pY#Pko55wAGqQwr(Seu{<= z5dVq_)BXrQGa)hCRI836%kTtE!R1J*9ae4$mi~Czs_VmRS z>QUv@b@D-21a@OiL3U%z_MKE4YOe76@MVN@)=XSKMW8^yGluV#7jaYB64+pvtI@OjBuRz4q)d_nkN3YOko(|#lUR_12r z-7Oyk@2oin%kp3;BnZCJS#r@NSREU$@yyEnGi3-D8+;184LFIYQ-Yh&t!>4A?32`4 z`*3S@7iBKkIZ`tvdXjD|jwBc&PRuCEND%w3(5;zztOtqNVUpYKoU4=V$K_8PFiHje z?i?}6v+>%*`Up-NVp0ov=p0ouVPiz7I)Q+bh(5cA06XJkZfL+mhjx^y4zDWF@CYy7u=ip$#gIQhW_Ld#MCZl}BevTvsxqXC|sz@4~Ofnb+;F zr^koyxYv?n((38zrtxRSGNU&5iVHN2M_+&9d;70HddI)kW>SslV@fD@RHx{KS`@#kV6K3ZMK^=C0R2;{I8QUrpUv5heLKf#Gd2^OW>{3dkm+yCKC_Co%?~yX(gQTrb$%sG^Ai{q4xIn zQ@N5@;^IZPkr4A}oF>A*MUKjAxRK8xvMErAQB@wH8*jT{4(F_%#xG9Z&0R9b9tRNHH0SScX%1%D@*n#eS4YhZT}AN(wi()%I3%+ZZKVwY0W0eFRjMRuITj$n9832*CQiKKu(y!4awEyEuM zJq&dS;IOkA;XX6E9~+W*h6v|1`qa7S+yN!wKwoT^atoi}KhD&~9sA{E`KQvdzm=MP zBDsGex%giC6RGm2QsYmh&`+g?zm-;Tpc%=2D%IovjMetlhrayKPi)Kn*4Fq_+mfH! z*3VSbjaHBa)I|N7Y(?9Z@=qOmEpp{Y65Z#I*z9uiI6Q3PG4Bh?t)qwMB|PTWyX404 z?eh{I^EK6S&1igH!ef4O%pz|dd1_pK)%B9=dOOZZoR=tS{xP_xlIunW=Oud1cUru1 zIc0#yeDeueJ|g1;m3fIn^L-Y(9Gvj4qH=@tzG}IC!n@>p0~Jv}zu7KVPXt@(UG==L zMLsM~R4l!|YhI$@{FCxtS+1gL(sTZZB`8;oyQtDt^Xo6l^7auFO3yh3J-Bn;2~4n+ z;^;Yl&b~n|o$xfxOL)$omAA`{A9Y$J@A7PU^A$IIjF#4BU3FI+2$lH1lDJ|;z*U`f r)?Be8h0fa4n6nPs(Bn6Iyc&+=*Ip7 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 PxoDzF?i3-!T>Sq5@tW0+ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/click/_compat.py b/venv/lib/python3.12/site-packages/click/_compat.py new file mode 100644 index 0000000..f2726b9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/_compat.py @@ -0,0 +1,622 @@ +from __future__ import annotations + +import codecs +import collections.abc as cabc +import io +import os +import re +import sys +import typing as t +from types import TracebackType +from weakref import WeakKeyDictionary + +CYGWIN = sys.platform.startswith("cygwin") +WIN = sys.platform.startswith("win") +auto_wrap_for_ansi: t.Callable[[t.TextIO], t.TextIO] | None = None +_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") + + +def _make_text_stream( + stream: t.BinaryIO, + encoding: str | None, + errors: str | None, + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if encoding is None: + encoding = get_best_encoding(stream) + if errors is None: + errors = "replace" + return _NonClosingTextIOWrapper( + stream, + encoding, + errors, + line_buffering=True, + force_readable=force_readable, + force_writable=force_writable, + ) + + +def is_ascii_encoding(encoding: str) -> bool: + """Checks if a given encoding is ascii.""" + try: + return codecs.lookup(encoding).name == "ascii" + except LookupError: + return False + + +def get_best_encoding(stream: t.IO[t.Any]) -> str: + """Returns the default stream encoding if not found.""" + rv = getattr(stream, "encoding", None) or sys.getdefaultencoding() + if is_ascii_encoding(rv): + return "utf-8" + return rv + + +class _NonClosingTextIOWrapper(io.TextIOWrapper): + def __init__( + self, + stream: t.BinaryIO, + encoding: str | None, + errors: str | None, + force_readable: bool = False, + force_writable: bool = False, + **extra: t.Any, + ) -> None: + self._stream = stream = t.cast( + t.BinaryIO, _FixupStream(stream, force_readable, force_writable) + ) + super().__init__(stream, encoding, errors, **extra) + + def __del__(self) -> None: + try: + self.detach() + except Exception: + pass + + def isatty(self) -> bool: + # https://bitbucket.org/pypy/pypy/issue/1803 + return self._stream.isatty() + + +class _FixupStream: + """The new io interface needs more from streams than streams + traditionally implement. As such, this fix-up code is necessary in + some circumstances. + + The forcing of readable and writable flags are there because some tools + put badly patched objects on sys (one such offender are certain version + of jupyter notebook). + """ + + def __init__( + self, + stream: t.BinaryIO, + force_readable: bool = False, + force_writable: bool = False, + ): + self._stream = stream + self._force_readable = force_readable + self._force_writable = force_writable + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._stream, name) + + def read1(self, size: int) -> bytes: + f = getattr(self._stream, "read1", None) + + if f is not None: + return t.cast(bytes, f(size)) + + return self._stream.read(size) + + def readable(self) -> bool: + if self._force_readable: + return True + x = getattr(self._stream, "readable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.read(0) + except Exception: + return False + return True + + def writable(self) -> bool: + if self._force_writable: + return True + x = getattr(self._stream, "writable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.write(b"") + except Exception: + try: + self._stream.write(b"") + except Exception: + return False + return True + + def seekable(self) -> bool: + x = getattr(self._stream, "seekable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.seek(self._stream.tell()) + except Exception: + return False + return True + + +def _is_binary_reader(stream: t.IO[t.Any], default: bool = False) -> bool: + try: + return isinstance(stream.read(0), bytes) + except Exception: + return default + # This happens in some cases where the stream was already + # closed. In this case, we assume the default. + + +def _is_binary_writer(stream: t.IO[t.Any], default: bool = False) -> bool: + try: + stream.write(b"") + except Exception: + try: + stream.write("") + return False + except Exception: + pass + return default + return True + + +def _find_binary_reader(stream: t.IO[t.Any]) -> t.BinaryIO | None: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_reader(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_reader(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _find_binary_writer(stream: t.IO[t.Any]) -> t.BinaryIO | None: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_writer(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_writer(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _stream_is_misconfigured(stream: t.TextIO) -> bool: + """A stream is misconfigured if its encoding is ASCII.""" + # If the stream does not have an encoding set, we assume it's set + # to ASCII. This appears to happen in certain unittest + # environments. It's not quite clear what the correct behavior is + # but this at least will force Click to recover somehow. + return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii") + + +def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: str | None) -> bool: + """A stream attribute is compatible if it is equal to the + desired value or the desired value is unset and the attribute + has a value. + """ + stream_value = getattr(stream, attr, None) + return stream_value == value or (value is None and stream_value is not None) + + +def _is_compatible_text_stream( + stream: t.TextIO, encoding: str | None, errors: str | None +) -> bool: + """Check if a stream's encoding and errors attributes are + compatible with the desired values. + """ + return _is_compat_stream_attr( + stream, "encoding", encoding + ) and _is_compat_stream_attr(stream, "errors", errors) + + +def _force_correct_text_stream( + text_stream: t.IO[t.Any], + encoding: str | None, + errors: str | None, + is_binary: t.Callable[[t.IO[t.Any], bool], bool], + find_binary: t.Callable[[t.IO[t.Any]], t.BinaryIO | None], + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if is_binary(text_stream, False): + binary_reader = t.cast(t.BinaryIO, text_stream) + else: + text_stream = t.cast(t.TextIO, text_stream) + # If the stream looks compatible, and won't default to a + # misconfigured ascii encoding, return it as-is. + if _is_compatible_text_stream(text_stream, encoding, errors) and not ( + encoding is None and _stream_is_misconfigured(text_stream) + ): + return text_stream + + # Otherwise, get the underlying binary reader. + possible_binary_reader = find_binary(text_stream) + + # If that's not possible, silently use the original reader + # and get mojibake instead of exceptions. + if possible_binary_reader is None: + return text_stream + + binary_reader = possible_binary_reader + + # Default errors to replace instead of strict in order to get + # something that works. + if errors is None: + errors = "replace" + + # Wrap the binary stream in a text stream with the correct + # encoding parameters. + return _make_text_stream( + binary_reader, + encoding, + errors, + force_readable=force_readable, + force_writable=force_writable, + ) + + +def _force_correct_text_reader( + text_reader: t.IO[t.Any], + encoding: str | None, + errors: str | None, + force_readable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_reader, + encoding, + errors, + _is_binary_reader, + _find_binary_reader, + force_readable=force_readable, + ) + + +def _force_correct_text_writer( + text_writer: t.IO[t.Any], + encoding: str | None, + errors: str | None, + force_writable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_writer, + encoding, + errors, + _is_binary_writer, + _find_binary_writer, + force_writable=force_writable, + ) + + +def get_binary_stdin() -> t.BinaryIO: + reader = _find_binary_reader(sys.stdin) + if reader is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdin.") + return reader + + +def get_binary_stdout() -> t.BinaryIO: + writer = _find_binary_writer(sys.stdout) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdout.") + return writer + + +def get_binary_stderr() -> t.BinaryIO: + writer = _find_binary_writer(sys.stderr) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stderr.") + return writer + + +def get_text_stdin(encoding: str | None = None, errors: str | None = None) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True) + + +def get_text_stdout(encoding: str | None = None, errors: str | None = None) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True) + + +def get_text_stderr(encoding: str | None = None, errors: str | None = None) -> t.TextIO: + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True) + + +def _wrap_io_open( + file: str | os.PathLike[str] | int, + mode: str, + encoding: str | None, + errors: str | None, +) -> t.IO[t.Any]: + """Handles not passing ``encoding`` and ``errors`` in binary mode.""" + if "b" in mode: + return open(file, mode) + + return open(file, mode, encoding=encoding, errors=errors) + + +def open_stream( + filename: str | os.PathLike[str], + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + atomic: bool = False, +) -> tuple[t.IO[t.Any], bool]: + binary = "b" in mode + filename = os.fspath(filename) + + # Standard streams first. These are simple because they ignore the + # atomic flag. Use fsdecode to handle Path("-"). + if os.fsdecode(filename) == "-": + if any(m in mode for m in ["w", "a", "x"]): + if binary: + return get_binary_stdout(), False + return get_text_stdout(encoding=encoding, errors=errors), False + if binary: + return get_binary_stdin(), False + return get_text_stdin(encoding=encoding, errors=errors), False + + # Non-atomic writes directly go out through the regular open functions. + if not atomic: + return _wrap_io_open(filename, mode, encoding, errors), True + + # Some usability stuff for atomic writes + if "a" in mode: + raise ValueError( + "Appending to an existing file is not supported, because that" + " would involve an expensive `copy`-operation to a temporary" + " file. Open the file in normal `w`-mode and copy explicitly" + " if that's what you're after." + ) + if "x" in mode: + raise ValueError("Use the `overwrite`-parameter instead.") + if "w" not in mode: + raise ValueError("Atomic writes only make sense with `w`-mode.") + + # Atomic writes are more complicated. They work by opening a file + # as a proxy in the same folder and then using the fdopen + # functionality to wrap it in a Python file. Then we wrap it in an + # atomic file that moves the file over on close. + import errno + import random + + try: + perm: int | None = os.stat(filename).st_mode + except OSError: + perm = None + + flags = os.O_RDWR | os.O_CREAT | os.O_EXCL + + if binary: + flags |= getattr(os, "O_BINARY", 0) + + while True: + tmp_filename = os.path.join( + os.path.dirname(filename), + f".__atomic-write{random.randrange(1 << 32):08x}", + ) + try: + fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm) + break + except OSError as e: + if e.errno == errno.EEXIST or ( + os.name == "nt" + and e.errno == errno.EACCES + and os.path.isdir(e.filename) + and os.access(e.filename, os.W_OK) + ): + continue + raise + + if perm is not None: + os.chmod(tmp_filename, perm) # in case perm includes bits in umask + + f = _wrap_io_open(fd, mode, encoding, errors) + af = _AtomicFile(f, tmp_filename, os.path.realpath(filename)) + return t.cast(t.IO[t.Any], af), True + + +class _AtomicFile: + def __init__(self, f: t.IO[t.Any], tmp_filename: str, real_filename: str) -> None: + self._f = f + self._tmp_filename = tmp_filename + self._real_filename = real_filename + self.closed = False + + @property + def name(self) -> str: + return self._real_filename + + def close(self, delete: bool = False) -> None: + if self.closed: + return + self._f.close() + os.replace(self._tmp_filename, self._real_filename) + self.closed = True + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._f, name) + + def __enter__(self) -> _AtomicFile: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.close(delete=exc_type is not None) + + def __repr__(self) -> str: + return repr(self._f) + + +def strip_ansi(value: str) -> str: + return _ansi_re.sub("", value) + + +def _is_jupyter_kernel_output(stream: t.IO[t.Any]) -> bool: + while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)): + stream = stream._stream + + return stream.__class__.__module__.startswith("ipykernel.") + + +def should_strip_ansi( + stream: t.IO[t.Any] | None = None, color: bool | None = None +) -> bool: + if color is None: + if stream is None: + stream = sys.stdin + return not isatty(stream) and not _is_jupyter_kernel_output(stream) + return not color + + +# On Windows, wrap the output streams with colorama to support ANSI +# color codes. +# NOTE: double check is needed so mypy does not analyze this on Linux +if sys.platform.startswith("win") and WIN: + from ._winconsole import _get_windows_console_stream + + def _get_argv_encoding() -> str: + import locale + + return locale.getpreferredencoding() + + _ansi_stream_wrappers: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def auto_wrap_for_ansi(stream: t.TextIO, color: bool | None = None) -> t.TextIO: + """Support ANSI color and style codes on Windows by wrapping a + stream with colorama. + """ + try: + cached = _ansi_stream_wrappers.get(stream) + except Exception: + cached = None + + if cached is not None: + return cached + + import colorama + + strip = should_strip_ansi(stream, color) + ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip) + rv = t.cast(t.TextIO, ansi_wrapper.stream) + _write = rv.write + + def _safe_write(s: str) -> int: + try: + return _write(s) + except BaseException: + ansi_wrapper.reset_all() + raise + + rv.write = _safe_write # type: ignore[method-assign] + + try: + _ansi_stream_wrappers[stream] = rv + except Exception: + pass + + return rv + +else: + + def _get_argv_encoding() -> str: + return getattr(sys.stdin, "encoding", None) or sys.getfilesystemencoding() + + def _get_windows_console_stream( + f: t.TextIO, encoding: str | None, errors: str | None + ) -> t.TextIO | None: + return None + + +def term_len(x: str) -> int: + return len(strip_ansi(x)) + + +def isatty(stream: t.IO[t.Any]) -> bool: + try: + return stream.isatty() + except Exception: + return False + + +def _make_cached_stream_func( + src_func: t.Callable[[], t.TextIO | None], + wrapper_func: t.Callable[[], t.TextIO], +) -> t.Callable[[], t.TextIO | None]: + cache: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def func() -> t.TextIO | None: + stream = src_func() + + if stream is None: + return None + + try: + rv = cache.get(stream) + except Exception: + rv = None + if rv is not None: + return rv + rv = wrapper_func() + try: + cache[stream] = rv + except Exception: + pass + return rv + + return func + + +_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin) +_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout) +_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr) + + +binary_streams: cabc.Mapping[str, t.Callable[[], t.BinaryIO]] = { + "stdin": get_binary_stdin, + "stdout": get_binary_stdout, + "stderr": get_binary_stderr, +} + +text_streams: cabc.Mapping[str, t.Callable[[str | None, str | None], t.TextIO]] = { + "stdin": get_text_stdin, + "stdout": get_text_stdout, + "stderr": get_text_stderr, +} diff --git a/venv/lib/python3.12/site-packages/click/_termui_impl.py b/venv/lib/python3.12/site-packages/click/_termui_impl.py new file mode 100644 index 0000000..47f87b8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/_termui_impl.py @@ -0,0 +1,847 @@ +""" +This module contains implementations for the termui module. To keep the +import time of Click down, some infrequently used functionality is +placed in this module and only imported as needed. +""" + +from __future__ import annotations + +import collections.abc as cabc +import contextlib +import math +import os +import shlex +import sys +import time +import typing as t +from gettext import gettext as _ +from io import StringIO +from pathlib import Path +from types import TracebackType + +from ._compat import _default_text_stdout +from ._compat import CYGWIN +from ._compat import get_best_encoding +from ._compat import isatty +from ._compat import open_stream +from ._compat import strip_ansi +from ._compat import term_len +from ._compat import WIN +from .exceptions import ClickException +from .utils import echo + +V = t.TypeVar("V") + +if os.name == "nt": + BEFORE_BAR = "\r" + AFTER_BAR = "\n" +else: + BEFORE_BAR = "\r\033[?25l" + AFTER_BAR = "\033[?25h\n" + + +class ProgressBar(t.Generic[V]): + def __init__( + self, + iterable: cabc.Iterable[V] | None, + length: int | None = None, + fill_char: str = "#", + empty_char: str = " ", + bar_template: str = "%(bar)s", + info_sep: str = " ", + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + item_show_func: t.Callable[[V | None], str | None] | None = None, + label: str | None = None, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, + width: int = 30, + ) -> None: + self.fill_char = fill_char + self.empty_char = empty_char + self.bar_template = bar_template + self.info_sep = info_sep + self.hidden = hidden + self.show_eta = show_eta + self.show_percent = show_percent + self.show_pos = show_pos + self.item_show_func = item_show_func + self.label: str = label or "" + + if file is None: + file = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if file is None: + file = StringIO() + + self.file = file + self.color = color + self.update_min_steps = update_min_steps + self._completed_intervals = 0 + self.width: int = width + self.autowidth: bool = width == 0 + + if length is None: + from operator import length_hint + + length = length_hint(iterable, -1) + + if length == -1: + length = None + if iterable is None: + if length is None: + raise TypeError("iterable or length is required") + iterable = t.cast("cabc.Iterable[V]", range(length)) + self.iter: cabc.Iterable[V] = iter(iterable) + self.length = length + self.pos: int = 0 + self.avg: list[float] = [] + self.last_eta: float + self.start: float + self.start = self.last_eta = time.time() + self.eta_known: bool = False + self.finished: bool = False + self.max_width: int | None = None + self.entered: bool = False + self.current_item: V | None = None + self._is_atty = isatty(self.file) + self._last_line: str | None = None + + def __enter__(self) -> ProgressBar[V]: + self.entered = True + self.render_progress() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.render_finish() + + def __iter__(self) -> cabc.Iterator[V]: + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + self.render_progress() + return self.generator() + + def __next__(self) -> V: + # Iteration is defined in terms of a generator function, + # returned by iter(self); use that to define next(). This works + # because `self.iter` is an iterable consumed by that generator, + # so it is re-entry safe. Calling `next(self.generator())` + # twice works and does "what you want". + return next(iter(self)) + + def render_finish(self) -> None: + if self.hidden or not self._is_atty: + return + self.file.write(AFTER_BAR) + self.file.flush() + + @property + def pct(self) -> float: + if self.finished: + return 1.0 + return min(self.pos / (float(self.length or 1) or 1), 1.0) + + @property + def time_per_iteration(self) -> float: + if not self.avg: + return 0.0 + return sum(self.avg) / float(len(self.avg)) + + @property + def eta(self) -> float: + if self.length is not None and not self.finished: + return self.time_per_iteration * (self.length - self.pos) + return 0.0 + + def format_eta(self) -> str: + if self.eta_known: + t = int(self.eta) + seconds = t % 60 + t //= 60 + minutes = t % 60 + t //= 60 + hours = t % 24 + t //= 24 + if t > 0: + return f"{t}d {hours:02}:{minutes:02}:{seconds:02}" + else: + return f"{hours:02}:{minutes:02}:{seconds:02}" + return "" + + def format_pos(self) -> str: + pos = str(self.pos) + if self.length is not None: + pos += f"/{self.length}" + return pos + + def format_pct(self) -> str: + return f"{int(self.pct * 100): 4}%"[1:] + + def format_bar(self) -> str: + if self.length is not None: + bar_length = int(self.pct * self.width) + bar = self.fill_char * bar_length + bar += self.empty_char * (self.width - bar_length) + elif self.finished: + bar = self.fill_char * self.width + else: + chars = list(self.empty_char * (self.width or 1)) + if self.time_per_iteration != 0: + chars[ + int( + (math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5) + * self.width + ) + ] = self.fill_char + bar = "".join(chars) + return bar + + def format_progress_line(self) -> str: + show_percent = self.show_percent + + info_bits = [] + if self.length is not None and show_percent is None: + show_percent = not self.show_pos + + if self.show_pos: + info_bits.append(self.format_pos()) + if show_percent: + info_bits.append(self.format_pct()) + if self.show_eta and self.eta_known and not self.finished: + info_bits.append(self.format_eta()) + if self.item_show_func is not None: + item_info = self.item_show_func(self.current_item) + if item_info is not None: + info_bits.append(item_info) + + return ( + self.bar_template + % { + "label": self.label, + "bar": self.format_bar(), + "info": self.info_sep.join(info_bits), + } + ).rstrip() + + def render_progress(self) -> None: + if self.hidden: + return + + if not self._is_atty: + # Only output the label once if the output is not a TTY. + if self._last_line != self.label: + self._last_line = self.label + echo(self.label, file=self.file, color=self.color) + return + + buf = [] + # Update width in case the terminal has been resized + if self.autowidth: + import shutil + + old_width = self.width + self.width = 0 + clutter_length = term_len(self.format_progress_line()) + new_width = max(0, shutil.get_terminal_size().columns - clutter_length) + if new_width < old_width and self.max_width is not None: + buf.append(BEFORE_BAR) + buf.append(" " * self.max_width) + self.max_width = new_width + self.width = new_width + + clear_width = self.width + if self.max_width is not None: + clear_width = self.max_width + + buf.append(BEFORE_BAR) + line = self.format_progress_line() + line_len = term_len(line) + if self.max_width is None or self.max_width < line_len: + self.max_width = line_len + + buf.append(line) + buf.append(" " * (clear_width - line_len)) + line = "".join(buf) + # Render the line only if it changed. + + if line != self._last_line: + self._last_line = line + echo(line, file=self.file, color=self.color, nl=False) + self.file.flush() + + def make_step(self, n_steps: int) -> None: + self.pos += n_steps + if self.length is not None and self.pos >= self.length: + self.finished = True + + if (time.time() - self.last_eta) < 1.0: + return + + self.last_eta = time.time() + + # self.avg is a rolling list of length <= 7 of steps where steps are + # defined as time elapsed divided by the total progress through + # self.length. + if self.pos: + step = (time.time() - self.start) / self.pos + else: + step = time.time() - self.start + + self.avg = self.avg[-6:] + [step] + + self.eta_known = self.length is not None + + def update(self, n_steps: int, current_item: V | None = None) -> None: + """Update the progress bar by advancing a specified number of + steps, and optionally set the ``current_item`` for this new + position. + + :param n_steps: Number of steps to advance. + :param current_item: Optional item to set as ``current_item`` + for the updated position. + + .. versionchanged:: 8.0 + Added the ``current_item`` optional parameter. + + .. versionchanged:: 8.0 + Only render when the number of steps meets the + ``update_min_steps`` threshold. + """ + if current_item is not None: + self.current_item = current_item + + self._completed_intervals += n_steps + + if self._completed_intervals >= self.update_min_steps: + self.make_step(self._completed_intervals) + self.render_progress() + self._completed_intervals = 0 + + def finish(self) -> None: + self.eta_known = False + self.current_item = None + self.finished = True + + def generator(self) -> cabc.Iterator[V]: + """Return a generator which yields the items added to the bar + during construction, and updates the progress bar *after* the + yielded block returns. + """ + # WARNING: the iterator interface for `ProgressBar` relies on + # this and only works because this is a simple generator which + # doesn't create or manage additional state. If this function + # changes, the impact should be evaluated both against + # `iter(bar)` and `next(bar)`. `next()` in particular may call + # `self.generator()` repeatedly, and this must remain safe in + # order for that interface to work. + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + + if not self._is_atty: + yield from self.iter + else: + for rv in self.iter: + self.current_item = rv + + # This allows show_item_func to be updated before the + # item is processed. Only trigger at the beginning of + # the update interval. + if self._completed_intervals == 0: + self.render_progress() + + yield rv + self.update(1) + + self.finish() + self.render_progress() + + +def pager(generator: cabc.Iterable[str], color: bool | None = None) -> None: + """Decide what method to use for paging through text.""" + stdout = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if stdout is None: + stdout = StringIO() + + if not isatty(sys.stdin) or not isatty(stdout): + return _nullpager(stdout, generator, color) + + # Split and normalize the pager command into parts. + pager_cmd_parts = shlex.split(os.environ.get("PAGER", ""), posix=False) + if pager_cmd_parts: + if WIN: + if _tempfilepager(generator, pager_cmd_parts, color): + return + elif _pipepager(generator, pager_cmd_parts, color): + return + + if os.environ.get("TERM") in ("dumb", "emacs"): + return _nullpager(stdout, generator, color) + if (WIN or sys.platform.startswith("os2")) and _tempfilepager( + generator, ["more"], color + ): + return + if _pipepager(generator, ["less"], color): + return + + import tempfile + + fd, filename = tempfile.mkstemp() + os.close(fd) + try: + if _pipepager(generator, ["more"], color): + return + return _nullpager(stdout, generator, color) + finally: + os.unlink(filename) + + +def _pipepager( + generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None +) -> bool: + """Page through text by feeding it to another program. Invoking a + pager through this might support colors. + + Returns `True` if the command was found, `False` otherwise and thus another + pager should be attempted. + """ + # Split the command into the invoked CLI and its parameters. + if not cmd_parts: + return False + + import shutil + + cmd = cmd_parts[0] + cmd_params = cmd_parts[1:] + + cmd_filepath = shutil.which(cmd) + if not cmd_filepath: + return False + # Resolves symlinks and produces a normalized absolute path string. + cmd_path = Path(cmd_filepath).resolve() + cmd_name = cmd_path.name + + import subprocess + + # Make a local copy of the environment to not affect the global one. + env = dict(os.environ) + + # If we're piping to less and the user hasn't decided on colors, we enable + # them by default we find the -R flag in the command line arguments. + if color is None and cmd_name == "less": + less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_params)}" + if not less_flags: + env["LESS"] = "-R" + color = True + elif "r" in less_flags or "R" in less_flags: + color = True + + c = subprocess.Popen( + [str(cmd_path)] + cmd_params, + shell=True, + stdin=subprocess.PIPE, + env=env, + errors="replace", + text=True, + ) + assert c.stdin is not None + try: + for text in generator: + if not color: + text = strip_ansi(text) + + c.stdin.write(text) + except BrokenPipeError: + # In case the pager exited unexpectedly, ignore the broken pipe error. + pass + except Exception as e: + # In case there is an exception we want to close the pager immediately + # and let the caller handle it. + # Otherwise the pager will keep running, and the user may not notice + # the error message, or worse yet it may leave the terminal in a broken state. + c.terminate() + raise e + finally: + # We must close stdin and wait for the pager to exit before we continue + try: + c.stdin.close() + # Close implies flush, so it might throw a BrokenPipeError if the pager + # process exited already. + except BrokenPipeError: + pass + + # Less doesn't respect ^C, but catches it for its own UI purposes (aborting + # search or other commands inside less). + # + # That means when the user hits ^C, the parent process (click) terminates, + # but less is still alive, paging the output and messing up the terminal. + # + # If the user wants to make the pager exit on ^C, they should set + # `LESS='-K'`. It's not our decision to make. + while True: + try: + c.wait() + except KeyboardInterrupt: + pass + else: + break + + return True + + +def _tempfilepager( + generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None +) -> bool: + """Page through text by invoking a program on a temporary file. + + Returns `True` if the command was found, `False` otherwise and thus another + pager should be attempted. + """ + # Split the command into the invoked CLI and its parameters. + if not cmd_parts: + return False + + import shutil + + cmd = cmd_parts[0] + + cmd_filepath = shutil.which(cmd) + if not cmd_filepath: + return False + # Resolves symlinks and produces a normalized absolute path string. + cmd_path = Path(cmd_filepath).resolve() + + import subprocess + import tempfile + + fd, filename = tempfile.mkstemp() + # TODO: This never terminates if the passed generator never terminates. + text = "".join(generator) + if not color: + text = strip_ansi(text) + encoding = get_best_encoding(sys.stdout) + with open_stream(filename, "wb")[0] as f: + f.write(text.encode(encoding)) + try: + subprocess.call([str(cmd_path), filename]) + except OSError: + # Command not found + pass + finally: + os.close(fd) + os.unlink(filename) + + return True + + +def _nullpager( + stream: t.TextIO, generator: cabc.Iterable[str], color: bool | None +) -> None: + """Simply print unformatted text. This is the ultimate fallback.""" + for text in generator: + if not color: + text = strip_ansi(text) + stream.write(text) + + +class Editor: + def __init__( + self, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", + ) -> None: + self.editor = editor + self.env = env + self.require_save = require_save + self.extension = extension + + def get_editor(self) -> str: + if self.editor is not None: + return self.editor + for key in "VISUAL", "EDITOR": + rv = os.environ.get(key) + if rv: + return rv + if WIN: + return "notepad" + + from shutil import which + + for editor in "sensible-editor", "vim", "nano": + if which(editor) is not None: + return editor + return "vi" + + def edit_files(self, filenames: cabc.Iterable[str]) -> None: + import subprocess + + editor = self.get_editor() + environ: dict[str, str] | None = None + + if self.env: + environ = os.environ.copy() + environ.update(self.env) + + exc_filename = " ".join(f'"{filename}"' for filename in filenames) + + try: + c = subprocess.Popen( + args=f"{editor} {exc_filename}", env=environ, shell=True + ) + exit_code = c.wait() + if exit_code != 0: + raise ClickException( + _("{editor}: Editing failed").format(editor=editor) + ) + except OSError as e: + raise ClickException( + _("{editor}: Editing failed: {e}").format(editor=editor, e=e) + ) from e + + @t.overload + def edit(self, text: bytes | bytearray) -> bytes | None: ... + + # We cannot know whether or not the type expected is str or bytes when None + # is passed, so str is returned as that was what was done before. + @t.overload + def edit(self, text: str | None) -> str | None: ... + + def edit(self, text: str | bytes | bytearray | None) -> str | bytes | None: + import tempfile + + if text is None: + data: bytes | bytearray = b"" + elif isinstance(text, (bytes, bytearray)): + data = text + else: + if text and not text.endswith("\n"): + text += "\n" + + if WIN: + data = text.replace("\n", "\r\n").encode("utf-8-sig") + else: + data = text.encode("utf-8") + + fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension) + f: t.BinaryIO + + try: + with os.fdopen(fd, "wb") as f: + f.write(data) + + # If the filesystem resolution is 1 second, like Mac OS + # 10.12 Extended, or 2 seconds, like FAT32, and the editor + # closes very fast, require_save can fail. Set the modified + # time to be 2 seconds in the past to work around this. + os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2)) + # Depending on the resolution, the exact value might not be + # recorded, so get the new recorded value. + timestamp = os.path.getmtime(name) + + self.edit_files((name,)) + + if self.require_save and os.path.getmtime(name) == timestamp: + return None + + with open(name, "rb") as f: + rv = f.read() + + if isinstance(text, (bytes, bytearray)): + return rv + + return rv.decode("utf-8-sig").replace("\r\n", "\n") + finally: + os.unlink(name) + + +def open_url(url: str, wait: bool = False, locate: bool = False) -> int: + import subprocess + + def _unquote_file(url: str) -> str: + from urllib.parse import unquote + + if url.startswith("file://"): + url = unquote(url[7:]) + + return url + + if sys.platform == "darwin": + args = ["open"] + if wait: + args.append("-W") + if locate: + args.append("-R") + args.append(_unquote_file(url)) + null = open("/dev/null", "w") + try: + return subprocess.Popen(args, stderr=null).wait() + finally: + null.close() + elif WIN: + if locate: + url = _unquote_file(url) + args = ["explorer", f"/select,{url}"] + else: + args = ["start"] + if wait: + args.append("/WAIT") + args.append("") + args.append(url) + try: + return subprocess.call(args) + except OSError: + # Command not found + return 127 + elif CYGWIN: + if locate: + url = _unquote_file(url) + args = ["cygstart", os.path.dirname(url)] + else: + args = ["cygstart"] + if wait: + args.append("-w") + args.append(url) + try: + return subprocess.call(args) + except OSError: + # Command not found + return 127 + + try: + if locate: + url = os.path.dirname(_unquote_file(url)) or "." + else: + url = _unquote_file(url) + c = subprocess.Popen(["xdg-open", url]) + if wait: + return c.wait() + return 0 + except OSError: + if url.startswith(("http://", "https://")) and not locate and not wait: + import webbrowser + + webbrowser.open(url) + return 0 + return 1 + + +def _translate_ch_to_exc(ch: str) -> None: + if ch == "\x03": + raise KeyboardInterrupt() + + if ch == "\x04" and not WIN: # Unix-like, Ctrl+D + raise EOFError() + + if ch == "\x1a" and WIN: # Windows, Ctrl+Z + raise EOFError() + + return None + + +if sys.platform == "win32": + import msvcrt + + @contextlib.contextmanager + def raw_terminal() -> cabc.Iterator[int]: + yield -1 + + def getchar(echo: bool) -> str: + # The function `getch` will return a bytes object corresponding to + # the pressed character. Since Windows 10 build 1803, it will also + # return \x00 when called a second time after pressing a regular key. + # + # `getwch` does not share this probably-bugged behavior. Moreover, it + # returns a Unicode object by default, which is what we want. + # + # Either of these functions will return \x00 or \xe0 to indicate + # a special key, and you need to call the same function again to get + # the "rest" of the code. The fun part is that \u00e0 is + # "latin small letter a with grave", so if you type that on a French + # keyboard, you _also_ get a \xe0. + # E.g., consider the Up arrow. This returns \xe0 and then \x48. The + # resulting Unicode string reads as "a with grave" + "capital H". + # This is indistinguishable from when the user actually types + # "a with grave" and then "capital H". + # + # When \xe0 is returned, we assume it's part of a special-key sequence + # and call `getwch` again, but that means that when the user types + # the \u00e0 character, `getchar` doesn't return until a second + # character is typed. + # The alternative is returning immediately, but that would mess up + # cross-platform handling of arrow keys and others that start with + # \xe0. Another option is using `getch`, but then we can't reliably + # read non-ASCII characters, because return values of `getch` are + # limited to the current 8-bit codepage. + # + # Anyway, Click doesn't claim to do this Right(tm), and using `getwch` + # is doing the right thing in more situations than with `getch`. + + if echo: + func = t.cast(t.Callable[[], str], msvcrt.getwche) + else: + func = t.cast(t.Callable[[], str], msvcrt.getwch) + + rv = func() + + if rv in ("\x00", "\xe0"): + # \x00 and \xe0 are control characters that indicate special key, + # see above. + rv += func() + + _translate_ch_to_exc(rv) + return rv + +else: + import termios + import tty + + @contextlib.contextmanager + def raw_terminal() -> cabc.Iterator[int]: + f: t.TextIO | None + fd: int + + if not isatty(sys.stdin): + f = open("/dev/tty") + fd = f.fileno() + else: + fd = sys.stdin.fileno() + f = None + + try: + old_settings = termios.tcgetattr(fd) + + try: + tty.setraw(fd) + yield fd + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + sys.stdout.flush() + + if f is not None: + f.close() + except termios.error: + pass + + def getchar(echo: bool) -> str: + with raw_terminal() as fd: + ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace") + + if echo and isatty(sys.stdout): + sys.stdout.write(ch) + + _translate_ch_to_exc(ch) + return ch diff --git a/venv/lib/python3.12/site-packages/click/_textwrap.py b/venv/lib/python3.12/site-packages/click/_textwrap.py new file mode 100644 index 0000000..97fbee3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/_textwrap.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import collections.abc as cabc +import textwrap +from contextlib import contextmanager + + +class TextWrapper(textwrap.TextWrapper): + def _handle_long_word( + self, + reversed_chunks: list[str], + cur_line: list[str], + cur_len: int, + width: int, + ) -> None: + space_left = max(width - cur_len, 1) + + if self.break_long_words: + last = reversed_chunks[-1] + cut = last[:space_left] + res = last[space_left:] + cur_line.append(cut) + reversed_chunks[-1] = res + elif not cur_line: + cur_line.append(reversed_chunks.pop()) + + @contextmanager + def extra_indent(self, indent: str) -> cabc.Iterator[None]: + old_initial_indent = self.initial_indent + old_subsequent_indent = self.subsequent_indent + self.initial_indent += indent + self.subsequent_indent += indent + + try: + yield + finally: + self.initial_indent = old_initial_indent + self.subsequent_indent = old_subsequent_indent + + def indent_only(self, text: str) -> str: + rv = [] + + for idx, line in enumerate(text.splitlines()): + indent = self.initial_indent + + if idx > 0: + indent = self.subsequent_indent + + rv.append(f"{indent}{line}") + + return "\n".join(rv) diff --git a/venv/lib/python3.12/site-packages/click/_utils.py b/venv/lib/python3.12/site-packages/click/_utils.py new file mode 100644 index 0000000..09fb008 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/_utils.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +import enum +import typing as t + + +class Sentinel(enum.Enum): + """Enum used to define sentinel values. + + .. seealso:: + + `PEP 661 - Sentinel Values `_. + """ + + UNSET = object() + FLAG_NEEDS_VALUE = object() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}.{self.name}" + + +UNSET = Sentinel.UNSET +"""Sentinel used to indicate that a value is not set.""" + +FLAG_NEEDS_VALUE = Sentinel.FLAG_NEEDS_VALUE +"""Sentinel used to indicate an option was passed as a flag without a +value but is not a flag option. + +``Option.consume_value`` uses this to prompt or use the ``flag_value``. +""" + +T_UNSET = t.Literal[UNSET] # type: ignore[valid-type] +"""Type hint for the :data:`UNSET` sentinel value.""" + +T_FLAG_NEEDS_VALUE = t.Literal[FLAG_NEEDS_VALUE] # type: ignore[valid-type] +"""Type hint for the :data:`FLAG_NEEDS_VALUE` sentinel value.""" diff --git a/venv/lib/python3.12/site-packages/click/_winconsole.py b/venv/lib/python3.12/site-packages/click/_winconsole.py new file mode 100644 index 0000000..e56c7c6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/_winconsole.py @@ -0,0 +1,296 @@ +# This module is based on the excellent work by Adam Bartoš who +# provided a lot of what went into the implementation here in +# the discussion to issue1602 in the Python bug tracker. +# +# There are some general differences in regards to how this works +# compared to the original patches as we do not need to patch +# the entire interpreter but just work in our little world of +# echo and prompt. +from __future__ import annotations + +import collections.abc as cabc +import io +import sys +import time +import typing as t +from ctypes import Array +from ctypes import byref +from ctypes import c_char +from ctypes import c_char_p +from ctypes import c_int +from ctypes import c_ssize_t +from ctypes import c_ulong +from ctypes import c_void_p +from ctypes import POINTER +from ctypes import py_object +from ctypes import Structure +from ctypes.wintypes import DWORD +from ctypes.wintypes import HANDLE +from ctypes.wintypes import LPCWSTR +from ctypes.wintypes import LPWSTR + +from ._compat import _NonClosingTextIOWrapper + +assert sys.platform == "win32" +import msvcrt # noqa: E402 +from ctypes import windll # noqa: E402 +from ctypes import WINFUNCTYPE # noqa: E402 + +c_ssize_p = POINTER(c_ssize_t) + +kernel32 = windll.kernel32 +GetStdHandle = kernel32.GetStdHandle +ReadConsoleW = kernel32.ReadConsoleW +WriteConsoleW = kernel32.WriteConsoleW +GetConsoleMode = kernel32.GetConsoleMode +GetLastError = kernel32.GetLastError +GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32)) +CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( + ("CommandLineToArgvW", windll.shell32) +) +LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32)) + +STDIN_HANDLE = GetStdHandle(-10) +STDOUT_HANDLE = GetStdHandle(-11) +STDERR_HANDLE = GetStdHandle(-12) + +PyBUF_SIMPLE = 0 +PyBUF_WRITABLE = 1 + +ERROR_SUCCESS = 0 +ERROR_NOT_ENOUGH_MEMORY = 8 +ERROR_OPERATION_ABORTED = 995 + +STDIN_FILENO = 0 +STDOUT_FILENO = 1 +STDERR_FILENO = 2 + +EOF = b"\x1a" +MAX_BYTES_WRITTEN = 32767 + +if t.TYPE_CHECKING: + try: + # Using `typing_extensions.Buffer` instead of `collections.abc` + # on Windows for some reason does not have `Sized` implemented. + from collections.abc import Buffer # type: ignore + except ImportError: + from typing_extensions import Buffer + +try: + from ctypes import pythonapi +except ImportError: + # On PyPy we cannot get buffers so our ability to operate here is + # severely limited. + get_buffer = None +else: + + class Py_buffer(Structure): + _fields_ = [ # noqa: RUF012 + ("buf", c_void_p), + ("obj", py_object), + ("len", c_ssize_t), + ("itemsize", c_ssize_t), + ("readonly", c_int), + ("ndim", c_int), + ("format", c_char_p), + ("shape", c_ssize_p), + ("strides", c_ssize_p), + ("suboffsets", c_ssize_p), + ("internal", c_void_p), + ] + + PyObject_GetBuffer = pythonapi.PyObject_GetBuffer + PyBuffer_Release = pythonapi.PyBuffer_Release + + def get_buffer(obj: Buffer, writable: bool = False) -> Array[c_char]: + buf = Py_buffer() + flags: int = PyBUF_WRITABLE if writable else PyBUF_SIMPLE + PyObject_GetBuffer(py_object(obj), byref(buf), flags) + + try: + buffer_type = c_char * buf.len + out: Array[c_char] = buffer_type.from_address(buf.buf) + return out + finally: + PyBuffer_Release(byref(buf)) + + +class _WindowsConsoleRawIOBase(io.RawIOBase): + def __init__(self, handle: int | None) -> None: + self.handle = handle + + def isatty(self) -> t.Literal[True]: + super().isatty() + return True + + +class _WindowsConsoleReader(_WindowsConsoleRawIOBase): + def readable(self) -> t.Literal[True]: + return True + + def readinto(self, b: Buffer) -> int: + bytes_to_be_read = len(b) + if not bytes_to_be_read: + return 0 + elif bytes_to_be_read % 2: + raise ValueError( + "cannot read odd number of bytes from UTF-16-LE encoded console" + ) + + buffer = get_buffer(b, writable=True) + code_units_to_be_read = bytes_to_be_read // 2 + code_units_read = c_ulong() + + rv = ReadConsoleW( + HANDLE(self.handle), + buffer, + code_units_to_be_read, + byref(code_units_read), + None, + ) + if GetLastError() == ERROR_OPERATION_ABORTED: + # wait for KeyboardInterrupt + time.sleep(0.1) + if not rv: + raise OSError(f"Windows error: {GetLastError()}") + + if buffer[0] == EOF: + return 0 + return 2 * code_units_read.value + + +class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): + def writable(self) -> t.Literal[True]: + return True + + @staticmethod + def _get_error_message(errno: int) -> str: + if errno == ERROR_SUCCESS: + return "ERROR_SUCCESS" + elif errno == ERROR_NOT_ENOUGH_MEMORY: + return "ERROR_NOT_ENOUGH_MEMORY" + return f"Windows error {errno}" + + def write(self, b: Buffer) -> int: + bytes_to_be_written = len(b) + buf = get_buffer(b) + code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2 + code_units_written = c_ulong() + + WriteConsoleW( + HANDLE(self.handle), + buf, + code_units_to_be_written, + byref(code_units_written), + None, + ) + bytes_written = 2 * code_units_written.value + + if bytes_written == 0 and bytes_to_be_written > 0: + raise OSError(self._get_error_message(GetLastError())) + return bytes_written + + +class ConsoleStream: + def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None: + self._text_stream = text_stream + self.buffer = byte_stream + + @property + def name(self) -> str: + return self.buffer.name + + def write(self, x: t.AnyStr) -> int: + if isinstance(x, str): + return self._text_stream.write(x) + try: + self.flush() + except Exception: + pass + return self.buffer.write(x) + + def writelines(self, lines: cabc.Iterable[t.AnyStr]) -> None: + for line in lines: + self.write(line) + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._text_stream, name) + + def isatty(self) -> bool: + return self.buffer.isatty() + + def __repr__(self) -> str: + return f"" + + +def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +_stream_factories: cabc.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = { + 0: _get_text_stdin, + 1: _get_text_stdout, + 2: _get_text_stderr, +} + + +def _is_console(f: t.TextIO) -> bool: + if not hasattr(f, "fileno"): + return False + + try: + fileno = f.fileno() + except (OSError, io.UnsupportedOperation): + return False + + handle = msvcrt.get_osfhandle(fileno) + return bool(GetConsoleMode(handle, byref(DWORD()))) + + +def _get_windows_console_stream( + f: t.TextIO, encoding: str | None, errors: str | None +) -> t.TextIO | None: + if ( + get_buffer is None + or encoding not in {"utf-16-le", None} + or errors not in {"strict", None} + or not _is_console(f) + ): + return None + + func = _stream_factories.get(f.fileno()) + if func is None: + return None + + b = getattr(f, "buffer", None) + + if b is None: + return None + + return func(b) diff --git a/venv/lib/python3.12/site-packages/click/core.py b/venv/lib/python3.12/site-packages/click/core.py new file mode 100644 index 0000000..ff2f74a --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/core.py @@ -0,0 +1,3347 @@ +from __future__ import annotations + +import collections.abc as cabc +import enum +import errno +import inspect +import os +import sys +import typing as t +from collections import abc +from collections import Counter +from contextlib import AbstractContextManager +from contextlib import contextmanager +from contextlib import ExitStack +from functools import update_wrapper +from gettext import gettext as _ +from gettext import ngettext +from itertools import repeat +from types import TracebackType + +from . import types +from ._utils import FLAG_NEEDS_VALUE +from ._utils import UNSET +from .exceptions import Abort +from .exceptions import BadParameter +from .exceptions import ClickException +from .exceptions import Exit +from .exceptions import MissingParameter +from .exceptions import NoArgsIsHelpError +from .exceptions import UsageError +from .formatting import HelpFormatter +from .formatting import join_options +from .globals import pop_context +from .globals import push_context +from .parser import _OptionParser +from .parser import _split_opt +from .termui import confirm +from .termui import prompt +from .termui import style +from .utils import _detect_program_name +from .utils import _expand_args +from .utils import echo +from .utils import make_default_short_help +from .utils import make_str +from .utils import PacifyFlushWrapper + +if t.TYPE_CHECKING: + from .shell_completion import CompletionItem + +F = t.TypeVar("F", bound="t.Callable[..., t.Any]") +V = t.TypeVar("V") + + +def _complete_visible_commands( + ctx: Context, incomplete: str +) -> cabc.Iterator[tuple[str, Command]]: + """List all the subcommands of a group that start with the + incomplete value and aren't hidden. + + :param ctx: Invocation context for the group. + :param incomplete: Value being completed. May be empty. + """ + multi = t.cast(Group, ctx.command) + + for name in multi.list_commands(ctx): + if name.startswith(incomplete): + command = multi.get_command(ctx, name) + + if command is not None and not command.hidden: + yield name, command + + +def _check_nested_chain( + base_command: Group, cmd_name: str, cmd: Command, register: bool = False +) -> None: + if not base_command.chain or not isinstance(cmd, Group): + return + + if register: + message = ( + f"It is not possible to add the group {cmd_name!r} to another" + f" group {base_command.name!r} that is in chain mode." + ) + else: + message = ( + f"Found the group {cmd_name!r} as subcommand to another group " + f" {base_command.name!r} that is in chain mode. This is not supported." + ) + + raise RuntimeError(message) + + +def batch(iterable: cabc.Iterable[V], batch_size: int) -> list[tuple[V, ...]]: + return list(zip(*repeat(iter(iterable), batch_size), strict=False)) + + +@contextmanager +def augment_usage_errors( + ctx: Context, param: Parameter | None = None +) -> cabc.Iterator[None]: + """Context manager that attaches extra information to exceptions.""" + try: + yield + except BadParameter as e: + if e.ctx is None: + e.ctx = ctx + if param is not None and e.param is None: + e.param = param + raise + except UsageError as e: + if e.ctx is None: + e.ctx = ctx + raise + + +def iter_params_for_processing( + invocation_order: cabc.Sequence[Parameter], + declaration_order: cabc.Sequence[Parameter], +) -> list[Parameter]: + """Returns all declared parameters in the order they should be processed. + + The declared parameters are re-shuffled depending on the order in which + they were invoked, as well as the eagerness of each parameters. + + The invocation order takes precedence over the declaration order. I.e. the + order in which the user provided them to the CLI is respected. + + This behavior and its effect on callback evaluation is detailed at: + https://click.palletsprojects.com/en/stable/advanced/#callback-evaluation-order + """ + + def sort_key(item: Parameter) -> tuple[bool, float]: + try: + idx: float = invocation_order.index(item) + except ValueError: + idx = float("inf") + + return not item.is_eager, idx + + return sorted(declaration_order, key=sort_key) + + +class ParameterSource(enum.Enum): + """This is an :class:`~enum.Enum` that indicates the source of a + parameter's value. + + Use :meth:`click.Context.get_parameter_source` to get the + source for a parameter by name. + + .. versionchanged:: 8.0 + Use :class:`~enum.Enum` and drop the ``validate`` method. + + .. versionchanged:: 8.0 + Added the ``PROMPT`` value. + """ + + COMMANDLINE = enum.auto() + """The value was provided by the command line args.""" + ENVIRONMENT = enum.auto() + """The value was provided with an environment variable.""" + DEFAULT = enum.auto() + """Used the default specified by the parameter.""" + DEFAULT_MAP = enum.auto() + """Used a default provided by :attr:`Context.default_map`.""" + PROMPT = enum.auto() + """Used a prompt to confirm a default or provide a value.""" + + +class Context: + """The context is a special internal object that holds state relevant + for the script execution at every single level. It's normally invisible + to commands unless they opt-in to getting access to it. + + The context is useful as it can pass internal objects around and can + control special execution features such as reading data from + environment variables. + + A context can be used as context manager in which case it will call + :meth:`close` on teardown. + + :param command: the command class for this context. + :param parent: the parent context. + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it is usually + the name of the script, for commands below it it's + the name of the script. + :param obj: an arbitrary object of user data. + :param auto_envvar_prefix: the prefix to use for automatic environment + variables. If this is `None` then reading + from environment variables is disabled. This + does not affect manually set environment + variables which are always read. + :param default_map: a dictionary (like object) with default values + for parameters. + :param terminal_width: the width of the terminal. The default is + inherit from parent context. If no context + defines the terminal width then auto + detection will be applied. + :param max_content_width: the maximum width for content rendered by + Click (this currently only affects help + pages). This defaults to 80 characters if + not overridden. In other words: even if the + terminal is larger than that, Click will not + format things wider than 80 characters by + default. In addition to that, formatters might + add some safety mapping on the right. + :param resilient_parsing: if this flag is enabled then Click will + parse without any interactivity or callback + invocation. Default values will also be + ignored. This is useful for implementing + things such as completion support. + :param allow_extra_args: if this is set to `True` then extra arguments + at the end will not raise an error and will be + kept on the context. The default is to inherit + from the command. + :param allow_interspersed_args: if this is set to `False` then options + and arguments cannot be mixed. The + default is to inherit from the command. + :param ignore_unknown_options: instructs click to ignore options it does + not know and keeps them for later + processing. + :param help_option_names: optionally a list of strings that define how + the default help parameter is named. The + default is ``['--help']``. + :param token_normalize_func: an optional function that is used to + normalize tokens (options, choices, + etc.). This for instance can be used to + implement case insensitive behavior. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are used in texts that Click prints which is by + default not the case. This for instance would affect + help output. + :param show_default: Show the default value for commands. If this + value is not set, it defaults to the value from the parent + context. ``Command.show_default`` overrides this default for the + specific command. + + .. versionchanged:: 8.2 + The ``protected_args`` attribute is deprecated and will be removed in + Click 9.0. ``args`` will contain remaining unparsed tokens. + + .. versionchanged:: 8.1 + The ``show_default`` parameter is overridden by + ``Command.show_default``, instead of the other way around. + + .. versionchanged:: 8.0 + The ``show_default`` parameter defaults to the value from the + parent context. + + .. versionchanged:: 7.1 + Added the ``show_default`` parameter. + + .. versionchanged:: 4.0 + Added the ``color``, ``ignore_unknown_options``, and + ``max_content_width`` parameters. + + .. versionchanged:: 3.0 + Added the ``allow_extra_args`` and ``allow_interspersed_args`` + parameters. + + .. versionchanged:: 2.0 + Added the ``resilient_parsing``, ``help_option_names``, and + ``token_normalize_func`` parameters. + """ + + #: The formatter class to create with :meth:`make_formatter`. + #: + #: .. versionadded:: 8.0 + formatter_class: type[HelpFormatter] = HelpFormatter + + def __init__( + self, + command: Command, + parent: Context | None = None, + info_name: str | None = None, + obj: t.Any | None = None, + auto_envvar_prefix: str | None = None, + default_map: cabc.MutableMapping[str, t.Any] | None = None, + terminal_width: int | None = None, + max_content_width: int | None = None, + resilient_parsing: bool = False, + allow_extra_args: bool | None = None, + allow_interspersed_args: bool | None = None, + ignore_unknown_options: bool | None = None, + help_option_names: list[str] | None = None, + token_normalize_func: t.Callable[[str], str] | None = None, + color: bool | None = None, + show_default: bool | None = None, + ) -> None: + #: the parent context or `None` if none exists. + self.parent = parent + #: the :class:`Command` for this context. + self.command = command + #: the descriptive information name + self.info_name = info_name + #: Map of parameter names to their parsed values. Parameters + #: with ``expose_value=False`` are not stored. + self.params: dict[str, t.Any] = {} + #: the leftover arguments. + self.args: list[str] = [] + #: protected arguments. These are arguments that are prepended + #: to `args` when certain parsing scenarios are encountered but + #: must be never propagated to another arguments. This is used + #: to implement nested parsing. + self._protected_args: list[str] = [] + #: the collected prefixes of the command's options. + self._opt_prefixes: set[str] = set(parent._opt_prefixes) if parent else set() + + if obj is None and parent is not None: + obj = parent.obj + + #: the user object stored. + self.obj: t.Any = obj + self._meta: dict[str, t.Any] = getattr(parent, "meta", {}) + + #: A dictionary (-like object) with defaults for parameters. + if ( + default_map is None + and info_name is not None + and parent is not None + and parent.default_map is not None + ): + default_map = parent.default_map.get(info_name) + + self.default_map: cabc.MutableMapping[str, t.Any] | None = default_map + + #: This flag indicates if a subcommand is going to be executed. A + #: group callback can use this information to figure out if it's + #: being executed directly or because the execution flow passes + #: onwards to a subcommand. By default it's None, but it can be + #: the name of the subcommand to execute. + #: + #: If chaining is enabled this will be set to ``'*'`` in case + #: any commands are executed. It is however not possible to + #: figure out which ones. If you require this knowledge you + #: should use a :func:`result_callback`. + self.invoked_subcommand: str | None = None + + if terminal_width is None and parent is not None: + terminal_width = parent.terminal_width + + #: The width of the terminal (None is autodetection). + self.terminal_width: int | None = terminal_width + + if max_content_width is None and parent is not None: + max_content_width = parent.max_content_width + + #: The maximum width of formatted content (None implies a sensible + #: default which is 80 for most things). + self.max_content_width: int | None = max_content_width + + if allow_extra_args is None: + allow_extra_args = command.allow_extra_args + + #: Indicates if the context allows extra args or if it should + #: fail on parsing. + #: + #: .. versionadded:: 3.0 + self.allow_extra_args = allow_extra_args + + if allow_interspersed_args is None: + allow_interspersed_args = command.allow_interspersed_args + + #: Indicates if the context allows mixing of arguments and + #: options or not. + #: + #: .. versionadded:: 3.0 + self.allow_interspersed_args: bool = allow_interspersed_args + + if ignore_unknown_options is None: + ignore_unknown_options = command.ignore_unknown_options + + #: Instructs click to ignore options that a command does not + #: understand and will store it on the context for later + #: processing. This is primarily useful for situations where you + #: want to call into external programs. Generally this pattern is + #: strongly discouraged because it's not possibly to losslessly + #: forward all arguments. + #: + #: .. versionadded:: 4.0 + self.ignore_unknown_options: bool = ignore_unknown_options + + if help_option_names is None: + if parent is not None: + help_option_names = parent.help_option_names + else: + help_option_names = ["--help"] + + #: The names for the help options. + self.help_option_names: list[str] = help_option_names + + if token_normalize_func is None and parent is not None: + token_normalize_func = parent.token_normalize_func + + #: An optional normalization function for tokens. This is + #: options, choices, commands etc. + self.token_normalize_func: t.Callable[[str], str] | None = token_normalize_func + + #: Indicates if resilient parsing is enabled. In that case Click + #: will do its best to not cause any failures and default values + #: will be ignored. Useful for completion. + self.resilient_parsing: bool = resilient_parsing + + # If there is no envvar prefix yet, but the parent has one and + # the command on this level has a name, we can expand the envvar + # prefix automatically. + if auto_envvar_prefix is None: + if ( + parent is not None + and parent.auto_envvar_prefix is not None + and self.info_name is not None + ): + auto_envvar_prefix = ( + f"{parent.auto_envvar_prefix}_{self.info_name.upper()}" + ) + else: + auto_envvar_prefix = auto_envvar_prefix.upper() + + if auto_envvar_prefix is not None: + auto_envvar_prefix = auto_envvar_prefix.replace("-", "_") + + self.auto_envvar_prefix: str | None = auto_envvar_prefix + + if color is None and parent is not None: + color = parent.color + + #: Controls if styling output is wanted or not. + self.color: bool | None = color + + if show_default is None and parent is not None: + show_default = parent.show_default + + #: Show option default values when formatting help text. + self.show_default: bool | None = show_default + + self._close_callbacks: list[t.Callable[[], t.Any]] = [] + self._depth = 0 + self._parameter_source: dict[str, ParameterSource] = {} + self._exit_stack = ExitStack() + + @property + def protected_args(self) -> list[str]: + import warnings + + warnings.warn( + "'protected_args' is deprecated and will be removed in Click 9.0." + " 'args' will contain remaining unparsed tokens.", + DeprecationWarning, + stacklevel=2, + ) + return self._protected_args + + def to_info_dict(self) -> dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. This traverses the entire CLI + structure. + + .. code-block:: python + + with Context(cli) as ctx: + info = ctx.to_info_dict() + + .. versionadded:: 8.0 + """ + return { + "command": self.command.to_info_dict(self), + "info_name": self.info_name, + "allow_extra_args": self.allow_extra_args, + "allow_interspersed_args": self.allow_interspersed_args, + "ignore_unknown_options": self.ignore_unknown_options, + "auto_envvar_prefix": self.auto_envvar_prefix, + } + + def __enter__(self) -> Context: + self._depth += 1 + push_context(self) + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> bool | None: + self._depth -= 1 + exit_result: bool | None = None + if self._depth == 0: + exit_result = self._close_with_exception_info(exc_type, exc_value, tb) + pop_context() + + return exit_result + + @contextmanager + def scope(self, cleanup: bool = True) -> cabc.Iterator[Context]: + """This helper method can be used with the context object to promote + it to the current thread local (see :func:`get_current_context`). + The default behavior of this is to invoke the cleanup functions which + can be disabled by setting `cleanup` to `False`. The cleanup + functions are typically used for things such as closing file handles. + + If the cleanup is intended the context object can also be directly + used as a context manager. + + Example usage:: + + with ctx.scope(): + assert get_current_context() is ctx + + This is equivalent:: + + with ctx: + assert get_current_context() is ctx + + .. versionadded:: 5.0 + + :param cleanup: controls if the cleanup functions should be run or + not. The default is to run these functions. In + some situations the context only wants to be + temporarily pushed in which case this can be disabled. + Nested pushes automatically defer the cleanup. + """ + if not cleanup: + self._depth += 1 + try: + with self as rv: + yield rv + finally: + if not cleanup: + self._depth -= 1 + + @property + def meta(self) -> dict[str, t.Any]: + """This is a dictionary which is shared with all the contexts + that are nested. It exists so that click utilities can store some + state here if they need to. It is however the responsibility of + that code to manage this dictionary well. + + The keys are supposed to be unique dotted strings. For instance + module paths are a good choice for it. What is stored in there is + irrelevant for the operation of click. However what is important is + that code that places data here adheres to the general semantics of + the system. + + Example usage:: + + LANG_KEY = f'{__name__}.lang' + + def set_language(value): + ctx = get_current_context() + ctx.meta[LANG_KEY] = value + + def get_language(): + return get_current_context().meta.get(LANG_KEY, 'en_US') + + .. versionadded:: 5.0 + """ + return self._meta + + def make_formatter(self) -> HelpFormatter: + """Creates the :class:`~click.HelpFormatter` for the help and + usage output. + + To quickly customize the formatter class used without overriding + this method, set the :attr:`formatter_class` attribute. + + .. versionchanged:: 8.0 + Added the :attr:`formatter_class` attribute. + """ + return self.formatter_class( + width=self.terminal_width, max_width=self.max_content_width + ) + + def with_resource(self, context_manager: AbstractContextManager[V]) -> V: + """Register a resource as if it were used in a ``with`` + statement. The resource will be cleaned up when the context is + popped. + + Uses :meth:`contextlib.ExitStack.enter_context`. It calls the + resource's ``__enter__()`` method and returns the result. When + the context is popped, it closes the stack, which calls the + resource's ``__exit__()`` method. + + To register a cleanup function for something that isn't a + context manager, use :meth:`call_on_close`. Or use something + from :mod:`contextlib` to turn it into a context manager first. + + .. code-block:: python + + @click.group() + @click.option("--name") + @click.pass_context + def cli(ctx): + ctx.obj = ctx.with_resource(connect_db(name)) + + :param context_manager: The context manager to enter. + :return: Whatever ``context_manager.__enter__()`` returns. + + .. versionadded:: 8.0 + """ + return self._exit_stack.enter_context(context_manager) + + def call_on_close(self, f: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: + """Register a function to be called when the context tears down. + + This can be used to close resources opened during the script + execution. Resources that support Python's context manager + protocol which would be used in a ``with`` statement should be + registered with :meth:`with_resource` instead. + + :param f: The function to execute on teardown. + """ + return self._exit_stack.callback(f) + + def close(self) -> None: + """Invoke all close callbacks registered with + :meth:`call_on_close`, and exit all context managers entered + with :meth:`with_resource`. + """ + self._close_with_exception_info(None, None, None) + + def _close_with_exception_info( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> bool | None: + """Unwind the exit stack by calling its :meth:`__exit__` providing the exception + information to allow for exception handling by the various resources registered + using :meth;`with_resource` + + :return: Whatever ``exit_stack.__exit__()`` returns. + """ + exit_result = self._exit_stack.__exit__(exc_type, exc_value, tb) + # In case the context is reused, create a new exit stack. + self._exit_stack = ExitStack() + + return exit_result + + @property + def command_path(self) -> str: + """The computed command path. This is used for the ``usage`` + information on the help page. It's automatically created by + combining the info names of the chain of contexts to the root. + """ + rv = "" + if self.info_name is not None: + rv = self.info_name + if self.parent is not None: + parent_command_path = [self.parent.command_path] + + if isinstance(self.parent.command, Command): + for param in self.parent.command.get_params(self): + parent_command_path.extend(param.get_usage_pieces(self)) + + rv = f"{' '.join(parent_command_path)} {rv}" + return rv.lstrip() + + def find_root(self) -> Context: + """Finds the outermost context.""" + node = self + while node.parent is not None: + node = node.parent + return node + + def find_object(self, object_type: type[V]) -> V | None: + """Finds the closest object of a given type.""" + node: Context | None = self + + while node is not None: + if isinstance(node.obj, object_type): + return node.obj + + node = node.parent + + return None + + def ensure_object(self, object_type: type[V]) -> V: + """Like :meth:`find_object` but sets the innermost object to a + new instance of `object_type` if it does not exist. + """ + rv = self.find_object(object_type) + if rv is None: + self.obj = rv = object_type() + return rv + + @t.overload + def lookup_default( + self, name: str, call: t.Literal[True] = True + ) -> t.Any | None: ... + + @t.overload + def lookup_default( + self, name: str, call: t.Literal[False] = ... + ) -> t.Any | t.Callable[[], t.Any] | None: ... + + def lookup_default(self, name: str, call: bool = True) -> t.Any | None: + """Get the default for a parameter from :attr:`default_map`. + + :param name: Name of the parameter. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + if self.default_map is not None: + value = self.default_map.get(name, UNSET) + + if call and callable(value): + return value() + + return value + + return UNSET + + def fail(self, message: str) -> t.NoReturn: + """Aborts the execution of the program with a specific error + message. + + :param message: the error message to fail with. + """ + raise UsageError(message, self) + + def abort(self) -> t.NoReturn: + """Aborts the script.""" + raise Abort() + + def exit(self, code: int = 0) -> t.NoReturn: + """Exits the application with a given exit code. + + .. versionchanged:: 8.2 + Callbacks and context managers registered with :meth:`call_on_close` + and :meth:`with_resource` are closed before exiting. + """ + self.close() + raise Exit(code) + + def get_usage(self) -> str: + """Helper method to get formatted usage string for the current + context and command. + """ + return self.command.get_usage(self) + + def get_help(self) -> str: + """Helper method to get formatted help page for the current + context and command. + """ + return self.command.get_help(self) + + def _make_sub_context(self, command: Command) -> Context: + """Create a new context of the same type as this context, but + for a new command. + + :meta private: + """ + return type(self)(command, info_name=command.name, parent=self) + + @t.overload + def invoke( + self, callback: t.Callable[..., V], /, *args: t.Any, **kwargs: t.Any + ) -> V: ... + + @t.overload + def invoke(self, callback: Command, /, *args: t.Any, **kwargs: t.Any) -> t.Any: ... + + def invoke( + self, callback: Command | t.Callable[..., V], /, *args: t.Any, **kwargs: t.Any + ) -> t.Any | V: + """Invokes a command callback in exactly the way it expects. There + are two ways to invoke this method: + + 1. the first argument can be a callback and all other arguments and + keyword arguments are forwarded directly to the function. + 2. the first argument is a click command object. In that case all + arguments are forwarded as well but proper click parameters + (options and click arguments) must be keyword arguments and Click + will fill in defaults. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if :meth:`forward` is called at multiple levels. + + .. versionchanged:: 3.2 + A new context is created, and missing arguments use default values. + """ + if isinstance(callback, Command): + other_cmd = callback + + if other_cmd.callback is None: + raise TypeError( + "The given command does not have a callback that can be invoked." + ) + else: + callback = t.cast("t.Callable[..., V]", other_cmd.callback) + + ctx = self._make_sub_context(other_cmd) + + for param in other_cmd.params: + if param.name not in kwargs and param.expose_value: + kwargs[param.name] = param.type_cast_value( # type: ignore + ctx, param.get_default(ctx) + ) + + # Track all kwargs as params, so that forward() will pass + # them on in subsequent calls. + ctx.params.update(kwargs) + else: + ctx = self + + with augment_usage_errors(self): + with ctx: + return callback(*args, **kwargs) + + def forward(self, cmd: Command, /, *args: t.Any, **kwargs: t.Any) -> t.Any: + """Similar to :meth:`invoke` but fills in default keyword + arguments from the current context if the other command expects + it. This cannot invoke callbacks directly, only other commands. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if ``forward`` is called at multiple levels. + """ + # Can only forward to other commands, not direct callbacks. + if not isinstance(cmd, Command): + raise TypeError("Callback is not a command.") + + for param in self.params: + if param not in kwargs: + kwargs[param] = self.params[param] + + return self.invoke(cmd, *args, **kwargs) + + def set_parameter_source(self, name: str, source: ParameterSource) -> None: + """Set the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + :param name: The name of the parameter. + :param source: A member of :class:`~click.core.ParameterSource`. + """ + self._parameter_source[name] = source + + def get_parameter_source(self, name: str) -> ParameterSource | None: + """Get the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + This can be useful for determining when a user specified a value + on the command line that is the same as the default value. It + will be :attr:`~click.core.ParameterSource.DEFAULT` only if the + value was actually taken from the default. + + :param name: The name of the parameter. + :rtype: ParameterSource + + .. versionchanged:: 8.0 + Returns ``None`` if the parameter was not provided from any + source. + """ + return self._parameter_source.get(name) + + +class Command: + """Commands are the basic building block of command line interfaces in + Click. A basic command handles command line parsing and might dispatch + more parsing to commands nested below it. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + :param callback: the callback to invoke. This is optional. + :param params: the parameters to register with this command. This can + be either :class:`Option` or :class:`Argument` objects. + :param help: the help string to use for this command. + :param epilog: like the help string but it's printed at the end of the + help page after everything else. + :param short_help: the short help to use for this command. This is + shown on the command listing of the parent command. + :param add_help_option: by default each command registers a ``--help`` + option. This can be disabled by this parameter. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is disabled by default. + If enabled this will add ``--help`` as argument + if no arguments are passed + :param hidden: hide this command from help outputs. + :param deprecated: If ``True`` or non-empty string, issues a message + indicating that the command is deprecated and highlights + its deprecation in --help. The message can be customized + by using a string as the value. + + .. versionchanged:: 8.2 + This is the base class for all commands, not ``BaseCommand``. + ``deprecated`` can be set to a string as well to customize the + deprecation message. + + .. versionchanged:: 8.1 + ``help``, ``epilog``, and ``short_help`` are stored unprocessed, + all formatting is done when outputting help text, not at init, + and is done even if not using the ``@command`` decorator. + + .. versionchanged:: 8.0 + Added a ``repr`` showing the command name. + + .. versionchanged:: 7.1 + Added the ``no_args_is_help`` parameter. + + .. versionchanged:: 2.0 + Added the ``context_settings`` parameter. + """ + + #: The context class to create with :meth:`make_context`. + #: + #: .. versionadded:: 8.0 + context_class: type[Context] = Context + + #: the default for the :attr:`Context.allow_extra_args` flag. + allow_extra_args = False + + #: the default for the :attr:`Context.allow_interspersed_args` flag. + allow_interspersed_args = True + + #: the default for the :attr:`Context.ignore_unknown_options` flag. + ignore_unknown_options = False + + def __init__( + self, + name: str | None, + context_settings: cabc.MutableMapping[str, t.Any] | None = None, + callback: t.Callable[..., t.Any] | None = None, + params: list[Parameter] | None = None, + help: str | None = None, + epilog: str | None = None, + short_help: str | None = None, + options_metavar: str | None = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool | str = False, + ) -> None: + #: the name the command thinks it has. Upon registering a command + #: on a :class:`Group` the group will default the command name + #: with this information. You should instead use the + #: :class:`Context`\'s :attr:`~Context.info_name` attribute. + self.name = name + + if context_settings is None: + context_settings = {} + + #: an optional dictionary with defaults passed to the context. + self.context_settings: cabc.MutableMapping[str, t.Any] = context_settings + + #: the callback to execute when the command fires. This might be + #: `None` in which case nothing happens. + self.callback = callback + #: the list of parameters for this command in the order they + #: should show up in the help page and execute. Eager parameters + #: will automatically be handled before non eager ones. + self.params: list[Parameter] = params or [] + self.help = help + self.epilog = epilog + self.options_metavar = options_metavar + self.short_help = short_help + self.add_help_option = add_help_option + self._help_option = None + self.no_args_is_help = no_args_is_help + self.hidden = hidden + self.deprecated = deprecated + + def to_info_dict(self, ctx: Context) -> dict[str, t.Any]: + return { + "name": self.name, + "params": [param.to_info_dict() for param in self.get_params(ctx)], + "help": self.help, + "epilog": self.epilog, + "short_help": self.short_help, + "hidden": self.hidden, + "deprecated": self.deprecated, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def get_usage(self, ctx: Context) -> str: + """Formats the usage line into a string and returns it. + + Calls :meth:`format_usage` internally. + """ + formatter = ctx.make_formatter() + self.format_usage(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_params(self, ctx: Context) -> list[Parameter]: + params = self.params + help_option = self.get_help_option(ctx) + + if help_option is not None: + params = [*params, help_option] + + if __debug__: + import warnings + + opts = [opt for param in params for opt in param.opts] + opts_counter = Counter(opts) + duplicate_opts = (opt for opt, count in opts_counter.items() if count > 1) + + for duplicate_opt in duplicate_opts: + warnings.warn( + ( + f"The parameter {duplicate_opt} is used more than once. " + "Remove its duplicate as parameters should be unique." + ), + stacklevel=3, + ) + + return params + + def format_usage(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the usage line into the formatter. + + This is a low-level method called by :meth:`get_usage`. + """ + pieces = self.collect_usage_pieces(ctx) + formatter.write_usage(ctx.command_path, " ".join(pieces)) + + def collect_usage_pieces(self, ctx: Context) -> list[str]: + """Returns all the pieces that go into the usage line and returns + it as a list of strings. + """ + rv = [self.options_metavar] if self.options_metavar else [] + + for param in self.get_params(ctx): + rv.extend(param.get_usage_pieces(ctx)) + + return rv + + def get_help_option_names(self, ctx: Context) -> list[str]: + """Returns the names for the help option.""" + all_names = set(ctx.help_option_names) + for param in self.params: + all_names.difference_update(param.opts) + all_names.difference_update(param.secondary_opts) + return list(all_names) + + def get_help_option(self, ctx: Context) -> Option | None: + """Returns the help option object. + + Skipped if :attr:`add_help_option` is ``False``. + + .. versionchanged:: 8.1.8 + The help option is now cached to avoid creating it multiple times. + """ + help_option_names = self.get_help_option_names(ctx) + + if not help_option_names or not self.add_help_option: + return None + + # Cache the help option object in private _help_option attribute to + # avoid creating it multiple times. Not doing this will break the + # callback odering by iter_params_for_processing(), which relies on + # object comparison. + if self._help_option is None: + # Avoid circular import. + from .decorators import help_option + + # Apply help_option decorator and pop resulting option + help_option(*help_option_names)(self) + self._help_option = self.params.pop() # type: ignore[assignment] + + return self._help_option + + def make_parser(self, ctx: Context) -> _OptionParser: + """Creates the underlying option parser for this command.""" + parser = _OptionParser(ctx) + for param in self.get_params(ctx): + param.add_to_parser(parser, ctx) + return parser + + def get_help(self, ctx: Context) -> str: + """Formats the help into a string and returns it. + + Calls :meth:`format_help` internally. + """ + formatter = ctx.make_formatter() + self.format_help(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_short_help_str(self, limit: int = 45) -> str: + """Gets short help for the command or makes it by shortening the + long help string. + """ + if self.short_help: + text = inspect.cleandoc(self.short_help) + elif self.help: + text = make_default_short_help(self.help, limit) + else: + text = "" + + if self.deprecated: + deprecated_message = ( + f"(DEPRECATED: {self.deprecated})" + if isinstance(self.deprecated, str) + else "(DEPRECATED)" + ) + text = _("{text} {deprecated_message}").format( + text=text, deprecated_message=deprecated_message + ) + + return text.strip() + + def format_help(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the help into the formatter if it exists. + + This is a low-level method called by :meth:`get_help`. + + This calls the following methods: + + - :meth:`format_usage` + - :meth:`format_help_text` + - :meth:`format_options` + - :meth:`format_epilog` + """ + self.format_usage(ctx, formatter) + self.format_help_text(ctx, formatter) + self.format_options(ctx, formatter) + self.format_epilog(ctx, formatter) + + def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the help text to the formatter if it exists.""" + if self.help is not None: + # truncate the help text to the first form feed + text = inspect.cleandoc(self.help).partition("\f")[0] + else: + text = "" + + if self.deprecated: + deprecated_message = ( + f"(DEPRECATED: {self.deprecated})" + if isinstance(self.deprecated, str) + else "(DEPRECATED)" + ) + text = _("{text} {deprecated_message}").format( + text=text, deprecated_message=deprecated_message + ) + + if text: + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(text) + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes all the options into the formatter if they exist.""" + opts = [] + for param in self.get_params(ctx): + rv = param.get_help_record(ctx) + if rv is not None: + opts.append(rv) + + if opts: + with formatter.section(_("Options")): + formatter.write_dl(opts) + + def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the epilog into the formatter if it exists.""" + if self.epilog: + epilog = inspect.cleandoc(self.epilog) + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(epilog) + + def make_context( + self, + info_name: str | None, + args: list[str], + parent: Context | None = None, + **extra: t.Any, + ) -> Context: + """This function when given an info name and arguments will kick + off the parsing and create a new :class:`Context`. It does not + invoke the actual command callback though. + + To quickly customize the context class used without overriding + this method, set the :attr:`context_class` attribute. + + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it's usually + the name of the script, for commands below it's + the name of the command. + :param args: the arguments to parse as list of strings. + :param parent: the parent context if available. + :param extra: extra keyword arguments forwarded to the context + constructor. + + .. versionchanged:: 8.0 + Added the :attr:`context_class` attribute. + """ + for key, value in self.context_settings.items(): + if key not in extra: + extra[key] = value + + ctx = self.context_class(self, info_name=info_name, parent=parent, **extra) + + with ctx.scope(cleanup=False): + self.parse_args(ctx, args) + return ctx + + def parse_args(self, ctx: Context, args: list[str]) -> list[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + raise NoArgsIsHelpError(ctx) + + parser = self.make_parser(ctx) + opts, args, param_order = parser.parse_args(args=args) + + for param in iter_params_for_processing(param_order, self.get_params(ctx)): + _, args = param.handle_parse_result(ctx, opts, args) + + if args and not ctx.allow_extra_args and not ctx.resilient_parsing: + ctx.fail( + ngettext( + "Got unexpected extra argument ({args})", + "Got unexpected extra arguments ({args})", + len(args), + ).format(args=" ".join(map(str, args))) + ) + + ctx.args = args + ctx._opt_prefixes.update(parser._opt_prefixes) + return args + + def invoke(self, ctx: Context) -> t.Any: + """Given a context, this invokes the attached callback (if it exists) + in the right way. + """ + if self.deprecated: + extra_message = ( + f" {self.deprecated}" if isinstance(self.deprecated, str) else "" + ) + message = _( + "DeprecationWarning: The command {name!r} is deprecated.{extra_message}" + ).format(name=self.name, extra_message=extra_message) + echo(style(message, fg="red"), err=True) + + if self.callback is not None: + return ctx.invoke(self.callback, **ctx.params) + + def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]: + """Return a list of completions for the incomplete value. Looks + at the names of options and chained multi-commands. + + Any command could be part of a chained multi-command, so sibling + commands are valid at any point during command completion. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results: list[CompletionItem] = [] + + if incomplete and not incomplete[0].isalnum(): + for param in self.get_params(ctx): + if ( + not isinstance(param, Option) + or param.hidden + or ( + not param.multiple + and ctx.get_parameter_source(param.name) # type: ignore + is ParameterSource.COMMANDLINE + ) + ): + continue + + results.extend( + CompletionItem(name, help=param.help) + for name in [*param.opts, *param.secondary_opts] + if name.startswith(incomplete) + ) + + while ctx.parent is not None: + ctx = ctx.parent + + if isinstance(ctx.command, Group) and ctx.command.chain: + results.extend( + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + if name not in ctx._protected_args + ) + + return results + + @t.overload + def main( + self, + args: cabc.Sequence[str] | None = None, + prog_name: str | None = None, + complete_var: str | None = None, + standalone_mode: t.Literal[True] = True, + **extra: t.Any, + ) -> t.NoReturn: ... + + @t.overload + def main( + self, + args: cabc.Sequence[str] | None = None, + prog_name: str | None = None, + complete_var: str | None = None, + standalone_mode: bool = ..., + **extra: t.Any, + ) -> t.Any: ... + + def main( + self, + args: cabc.Sequence[str] | None = None, + prog_name: str | None = None, + complete_var: str | None = None, + standalone_mode: bool = True, + windows_expand_args: bool = True, + **extra: t.Any, + ) -> t.Any: + """This is the way to invoke a script with all the bells and + whistles as a command line application. This will always terminate + the application after a call. If this is not wanted, ``SystemExit`` + needs to be caught. + + This method is also available by directly calling the instance of + a :class:`Command`. + + :param args: the arguments that should be used for parsing. If not + provided, ``sys.argv[1:]`` is used. + :param prog_name: the program name that should be used. By default + the program name is constructed by taking the file + name from ``sys.argv[0]``. + :param complete_var: the environment variable that controls the + bash completion support. The default is + ``"__COMPLETE"`` with prog_name in + uppercase. + :param standalone_mode: the default behavior is to invoke the script + in standalone mode. Click will then + handle exceptions and convert them into + error messages and the function will never + return but shut down the interpreter. If + this is set to `False` they will be + propagated to the caller and the return + value of this function is the return value + of :meth:`invoke`. + :param windows_expand_args: Expand glob patterns, user dir, and + env vars in command line args on Windows. + :param extra: extra keyword arguments are forwarded to the context + constructor. See :class:`Context` for more information. + + .. versionchanged:: 8.0.1 + Added the ``windows_expand_args`` parameter to allow + disabling command line arg expansion on Windows. + + .. versionchanged:: 8.0 + When taking arguments from ``sys.argv`` on Windows, glob + patterns, user dir, and env vars are expanded. + + .. versionchanged:: 3.0 + Added the ``standalone_mode`` parameter. + """ + if args is None: + args = sys.argv[1:] + + if os.name == "nt" and windows_expand_args: + args = _expand_args(args) + else: + args = list(args) + + if prog_name is None: + prog_name = _detect_program_name() + + # Process shell completion requests and exit early. + self._main_shell_completion(extra, prog_name, complete_var) + + try: + try: + with self.make_context(prog_name, args, **extra) as ctx: + rv = self.invoke(ctx) + if not standalone_mode: + return rv + # it's not safe to `ctx.exit(rv)` here! + # note that `rv` may actually contain data like "1" which + # has obvious effects + # more subtle case: `rv=[None, None]` can come out of + # chained commands which all returned `None` -- so it's not + # even always obvious that `rv` indicates success/failure + # by its truthiness/falsiness + ctx.exit() + except (EOFError, KeyboardInterrupt) as e: + echo(file=sys.stderr) + raise Abort() from e + except ClickException as e: + if not standalone_mode: + raise + e.show() + sys.exit(e.exit_code) + except OSError as e: + if e.errno == errno.EPIPE: + sys.stdout = t.cast(t.TextIO, PacifyFlushWrapper(sys.stdout)) + sys.stderr = t.cast(t.TextIO, PacifyFlushWrapper(sys.stderr)) + sys.exit(1) + else: + raise + except Exit as e: + if standalone_mode: + sys.exit(e.exit_code) + else: + # in non-standalone mode, return the exit code + # note that this is only reached if `self.invoke` above raises + # an Exit explicitly -- thus bypassing the check there which + # would return its result + # the results of non-standalone execution may therefore be + # somewhat ambiguous: if there are codepaths which lead to + # `ctx.exit(1)` and to `return 1`, the caller won't be able to + # tell the difference between the two + return e.exit_code + except Abort: + if not standalone_mode: + raise + echo(_("Aborted!"), file=sys.stderr) + sys.exit(1) + + def _main_shell_completion( + self, + ctx_args: cabc.MutableMapping[str, t.Any], + prog_name: str, + complete_var: str | None = None, + ) -> None: + """Check if the shell is asking for tab completion, process + that, then exit early. Called from :meth:`main` before the + program is invoked. + + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. Defaults to + ``_{PROG_NAME}_COMPLETE``. + + .. versionchanged:: 8.2.0 + Dots (``.``) in ``prog_name`` are replaced with underscores (``_``). + """ + if complete_var is None: + complete_name = prog_name.replace("-", "_").replace(".", "_") + complete_var = f"_{complete_name}_COMPLETE".upper() + + instruction = os.environ.get(complete_var) + + if not instruction: + return + + from .shell_completion import shell_complete + + rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) + sys.exit(rv) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: + """Alias for :meth:`main`.""" + return self.main(*args, **kwargs) + + +class _FakeSubclassCheck(type): + def __subclasscheck__(cls, subclass: type) -> bool: + return issubclass(subclass, cls.__bases__[0]) + + def __instancecheck__(cls, instance: t.Any) -> bool: + return isinstance(instance, cls.__bases__[0]) + + +class _BaseCommand(Command, metaclass=_FakeSubclassCheck): + """ + .. deprecated:: 8.2 + Will be removed in Click 9.0. Use ``Command`` instead. + """ + + +class Group(Command): + """A group is a command that nests other commands (or more groups). + + :param name: The name of the group command. + :param commands: Map names to :class:`Command` objects. Can be a list, which + will use :attr:`Command.name` as the keys. + :param invoke_without_command: Invoke the group's callback even if a + subcommand is not given. + :param no_args_is_help: If no arguments are given, show the group's help and + exit. Defaults to the opposite of ``invoke_without_command``. + :param subcommand_metavar: How to represent the subcommand argument in help. + The default will represent whether ``chain`` is set or not. + :param chain: Allow passing more than one subcommand argument. After parsing + a command's arguments, if any arguments remain another command will be + matched, and so on. + :param result_callback: A function to call after the group's and + subcommand's callbacks. The value returned by the subcommand is passed. + If ``chain`` is enabled, the value will be a list of values returned by + all the commands. If ``invoke_without_command`` is enabled, the value + will be the value returned by the group's callback, or an empty list if + ``chain`` is enabled. + :param kwargs: Other arguments passed to :class:`Command`. + + .. versionchanged:: 8.0 + The ``commands`` argument can be a list of command objects. + + .. versionchanged:: 8.2 + Merged with and replaces the ``MultiCommand`` base class. + """ + + allow_extra_args = True + allow_interspersed_args = False + + #: If set, this is used by the group's :meth:`command` decorator + #: as the default :class:`Command` class. This is useful to make all + #: subcommands use a custom command class. + #: + #: .. versionadded:: 8.0 + command_class: type[Command] | None = None + + #: If set, this is used by the group's :meth:`group` decorator + #: as the default :class:`Group` class. This is useful to make all + #: subgroups use a custom group class. + #: + #: If set to the special value :class:`type` (literally + #: ``group_class = type``), this group's class will be used as the + #: default class. This makes a custom group class continue to make + #: custom groups. + #: + #: .. versionadded:: 8.0 + group_class: type[Group] | type[type] | None = None + # Literal[type] isn't valid, so use Type[type] + + def __init__( + self, + name: str | None = None, + commands: cabc.MutableMapping[str, Command] + | cabc.Sequence[Command] + | None = None, + invoke_without_command: bool = False, + no_args_is_help: bool | None = None, + subcommand_metavar: str | None = None, + chain: bool = False, + result_callback: t.Callable[..., t.Any] | None = None, + **kwargs: t.Any, + ) -> None: + super().__init__(name, **kwargs) + + if commands is None: + commands = {} + elif isinstance(commands, abc.Sequence): + commands = {c.name: c for c in commands if c.name is not None} + + #: The registered subcommands by their exported names. + self.commands: cabc.MutableMapping[str, Command] = commands + + if no_args_is_help is None: + no_args_is_help = not invoke_without_command + + self.no_args_is_help = no_args_is_help + self.invoke_without_command = invoke_without_command + + if subcommand_metavar is None: + if chain: + subcommand_metavar = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." + else: + subcommand_metavar = "COMMAND [ARGS]..." + + self.subcommand_metavar = subcommand_metavar + self.chain = chain + # The result callback that is stored. This can be set or + # overridden with the :func:`result_callback` decorator. + self._result_callback = result_callback + + if self.chain: + for param in self.params: + if isinstance(param, Argument) and not param.required: + raise RuntimeError( + "A group in chain mode cannot have optional arguments." + ) + + def to_info_dict(self, ctx: Context) -> dict[str, t.Any]: + info_dict = super().to_info_dict(ctx) + commands = {} + + for name in self.list_commands(ctx): + command = self.get_command(ctx, name) + + if command is None: + continue + + sub_ctx = ctx._make_sub_context(command) + + with sub_ctx.scope(cleanup=False): + commands[name] = command.to_info_dict(sub_ctx) + + info_dict.update(commands=commands, chain=self.chain) + return info_dict + + def add_command(self, cmd: Command, name: str | None = None) -> None: + """Registers another :class:`Command` with this group. If the name + is not provided, the name of the command is used. + """ + name = name or cmd.name + if name is None: + raise TypeError("Command has no name.") + _check_nested_chain(self, name, cmd, register=True) + self.commands[name] = cmd + + @t.overload + def command(self, __func: t.Callable[..., t.Any]) -> Command: ... + + @t.overload + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command]: ... + + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command] | Command: + """A shortcut decorator for declaring and attaching a command to + the group. This takes the same arguments as :func:`command` and + immediately registers the created command with this group by + calling :meth:`add_command`. + + To customize the command class used, set the + :attr:`command_class` attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`command_class` attribute. + """ + from .decorators import command + + func: t.Callable[..., t.Any] | None = None + + if args and callable(args[0]): + assert len(args) == 1 and not kwargs, ( + "Use 'command(**kwargs)(callable)' to provide arguments." + ) + (func,) = args + args = () + + if self.command_class and kwargs.get("cls") is None: + kwargs["cls"] = self.command_class + + def decorator(f: t.Callable[..., t.Any]) -> Command: + cmd: Command = command(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + @t.overload + def group(self, __func: t.Callable[..., t.Any]) -> Group: ... + + @t.overload + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Group]: ... + + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Group] | Group: + """A shortcut decorator for declaring and attaching a group to + the group. This takes the same arguments as :func:`group` and + immediately registers the created group with this group by + calling :meth:`add_command`. + + To customize the group class used, set the :attr:`group_class` + attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`group_class` attribute. + """ + from .decorators import group + + func: t.Callable[..., t.Any] | None = None + + if args and callable(args[0]): + assert len(args) == 1 and not kwargs, ( + "Use 'group(**kwargs)(callable)' to provide arguments." + ) + (func,) = args + args = () + + if self.group_class is not None and kwargs.get("cls") is None: + if self.group_class is type: + kwargs["cls"] = type(self) + else: + kwargs["cls"] = self.group_class + + def decorator(f: t.Callable[..., t.Any]) -> Group: + cmd: Group = group(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + def result_callback(self, replace: bool = False) -> t.Callable[[F], F]: + """Adds a result callback to the command. By default if a + result callback is already registered this will chain them but + this can be disabled with the `replace` parameter. The result + callback is invoked with the return value of the subcommand + (or the list of return values from all subcommands if chaining + is enabled) as well as the parameters as they would be passed + to the main callback. + + Example:: + + @click.group() + @click.option('-i', '--input', default=23) + def cli(input): + return 42 + + @cli.result_callback() + def process_result(result, input): + return result + input + + :param replace: if set to `True` an already existing result + callback will be removed. + + .. versionchanged:: 8.0 + Renamed from ``resultcallback``. + + .. versionadded:: 3.0 + """ + + def decorator(f: F) -> F: + old_callback = self._result_callback + + if old_callback is None or replace: + self._result_callback = f + return f + + def function(value: t.Any, /, *args: t.Any, **kwargs: t.Any) -> t.Any: + inner = old_callback(value, *args, **kwargs) + return f(inner, *args, **kwargs) + + self._result_callback = rv = update_wrapper(t.cast(F, function), f) + return rv # type: ignore[return-value] + + return decorator + + def get_command(self, ctx: Context, cmd_name: str) -> Command | None: + """Given a context and a command name, this returns a :class:`Command` + object if it exists or returns ``None``. + """ + return self.commands.get(cmd_name) + + def list_commands(self, ctx: Context) -> list[str]: + """Returns a list of subcommand names in the order they should appear.""" + return sorted(self.commands) + + def collect_usage_pieces(self, ctx: Context) -> list[str]: + rv = super().collect_usage_pieces(ctx) + rv.append(self.subcommand_metavar) + return rv + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + super().format_options(ctx, formatter) + self.format_commands(ctx, formatter) + + def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None: + """Extra format methods for multi methods that adds all the commands + after the options. + """ + commands = [] + for subcommand in self.list_commands(ctx): + cmd = self.get_command(ctx, subcommand) + # What is this, the tool lied about a command. Ignore it + if cmd is None: + continue + if cmd.hidden: + continue + + commands.append((subcommand, cmd)) + + # allow for 3 times the default spacing + if len(commands): + limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands) + + rows = [] + for subcommand, cmd in commands: + help = cmd.get_short_help_str(limit) + rows.append((subcommand, help)) + + if rows: + with formatter.section(_("Commands")): + formatter.write_dl(rows) + + def parse_args(self, ctx: Context, args: list[str]) -> list[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + raise NoArgsIsHelpError(ctx) + + rest = super().parse_args(ctx, args) + + if self.chain: + ctx._protected_args = rest + ctx.args = [] + elif rest: + ctx._protected_args, ctx.args = rest[:1], rest[1:] + + return ctx.args + + def invoke(self, ctx: Context) -> t.Any: + def _process_result(value: t.Any) -> t.Any: + if self._result_callback is not None: + value = ctx.invoke(self._result_callback, value, **ctx.params) + return value + + if not ctx._protected_args: + if self.invoke_without_command: + # No subcommand was invoked, so the result callback is + # invoked with the group return value for regular + # groups, or an empty list for chained groups. + with ctx: + rv = super().invoke(ctx) + return _process_result([] if self.chain else rv) + ctx.fail(_("Missing command.")) + + # Fetch args back out + args = [*ctx._protected_args, *ctx.args] + ctx.args = [] + ctx._protected_args = [] + + # If we're not in chain mode, we only allow the invocation of a + # single command but we also inform the current context about the + # name of the command to invoke. + if not self.chain: + # Make sure the context is entered so we do not clean up + # resources until the result processor has worked. + with ctx: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + ctx.invoked_subcommand = cmd_name + super().invoke(ctx) + sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) + with sub_ctx: + return _process_result(sub_ctx.command.invoke(sub_ctx)) + + # In chain mode we create the contexts step by step, but after the + # base command has been invoked. Because at that point we do not + # know the subcommands yet, the invoked subcommand attribute is + # set to ``*`` to inform the command that subcommands are executed + # but nothing else. + with ctx: + ctx.invoked_subcommand = "*" if args else None + super().invoke(ctx) + + # Otherwise we make every single context and invoke them in a + # chain. In that case the return value to the result processor + # is the list of all invoked subcommand's results. + contexts = [] + while args: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + sub_ctx = cmd.make_context( + cmd_name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + ) + contexts.append(sub_ctx) + args, sub_ctx.args = sub_ctx.args, [] + + rv = [] + for sub_ctx in contexts: + with sub_ctx: + rv.append(sub_ctx.command.invoke(sub_ctx)) + return _process_result(rv) + + def resolve_command( + self, ctx: Context, args: list[str] + ) -> tuple[str | None, Command | None, list[str]]: + cmd_name = make_str(args[0]) + original_cmd_name = cmd_name + + # Get the command + cmd = self.get_command(ctx, cmd_name) + + # If we can't find the command but there is a normalization + # function available, we try with that one. + if cmd is None and ctx.token_normalize_func is not None: + cmd_name = ctx.token_normalize_func(cmd_name) + cmd = self.get_command(ctx, cmd_name) + + # If we don't find the command we want to show an error message + # to the user that it was not provided. However, there is + # something else we should do: if the first argument looks like + # an option we want to kick off parsing again for arguments to + # resolve things like --help which now should go to the main + # place. + if cmd is None and not ctx.resilient_parsing: + if _split_opt(cmd_name)[0]: + self.parse_args(ctx, args) + ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name)) + return cmd_name if cmd else None, cmd, args[1:] + + def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]: + """Return a list of completions for the incomplete value. Looks + at the names of options, subcommands, and chained + multi-commands. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results = [ + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + ] + results.extend(super().shell_complete(ctx, incomplete)) + return results + + +class _MultiCommand(Group, metaclass=_FakeSubclassCheck): + """ + .. deprecated:: 8.2 + Will be removed in Click 9.0. Use ``Group`` instead. + """ + + +class CommandCollection(Group): + """A :class:`Group` that looks up subcommands on other groups. If a command + is not found on this group, each registered source is checked in order. + Parameters on a source are not added to this group, and a source's callback + is not invoked when invoking its commands. In other words, this "flattens" + commands in many groups into this one group. + + :param name: The name of the group command. + :param sources: A list of :class:`Group` objects to look up commands from. + :param kwargs: Other arguments passed to :class:`Group`. + + .. versionchanged:: 8.2 + This is a subclass of ``Group``. Commands are looked up first on this + group, then each of its sources. + """ + + def __init__( + self, + name: str | None = None, + sources: list[Group] | None = None, + **kwargs: t.Any, + ) -> None: + super().__init__(name, **kwargs) + #: The list of registered groups. + self.sources: list[Group] = sources or [] + + def add_source(self, group: Group) -> None: + """Add a group as a source of commands.""" + self.sources.append(group) + + def get_command(self, ctx: Context, cmd_name: str) -> Command | None: + rv = super().get_command(ctx, cmd_name) + + if rv is not None: + return rv + + for source in self.sources: + rv = source.get_command(ctx, cmd_name) + + if rv is not None: + if self.chain: + _check_nested_chain(self, cmd_name, rv) + + return rv + + return None + + def list_commands(self, ctx: Context) -> list[str]: + rv: set[str] = set(super().list_commands(ctx)) + + for source in self.sources: + rv.update(source.list_commands(ctx)) + + return sorted(rv) + + +def _check_iter(value: t.Any) -> cabc.Iterator[t.Any]: + """Check if the value is iterable but not a string. Raises a type + error, or return an iterator over the value. + """ + if isinstance(value, str): + raise TypeError + + return iter(value) + + +class Parameter: + r"""A parameter to a command comes in two versions: they are either + :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently + not supported by design as some of the internals for parsing are + intentionally not finalized. + + Some settings are supported by both options and arguments. + + :param param_decls: the parameter declarations for this option or + argument. This is a list of flags or argument + names. + :param type: the type that should be used. Either a :class:`ParamType` + or a Python type. The latter is converted into the former + automatically if supported. + :param required: controls if this is optional or not. + :param default: the default value if omitted. This can also be a callable, + in which case it's invoked when the default is needed + without any arguments. + :param callback: A function to further process or validate the value + after type conversion. It is called as ``f(ctx, param, value)`` + and must return the value. It is called for all sources, + including prompts. + :param nargs: the number of arguments to match. If not ``1`` the return + value is a tuple instead of single value. The default for + nargs is ``1`` (except if the type is a tuple, then it's + the arity of the tuple). If ``nargs=-1``, all remaining + parameters are collected. + :param metavar: how the value is represented in the help page. + :param expose_value: if this is `True` then the value is passed onwards + to the command callback and stored on the context, + otherwise it's skipped. + :param is_eager: eager values are processed before non eager ones. This + should not be set for arguments or it will inverse the + order of processing. + :param envvar: environment variable(s) that are used to provide a default value for + this parameter. This can be a string or a sequence of strings. If a sequence is + given, only the first non-empty environment variable is used for the parameter. + :param shell_complete: A function that returns custom shell + completions. Used instead of the param's type completion if + given. Takes ``ctx, param, incomplete`` and must return a list + of :class:`~click.shell_completion.CompletionItem` or a list of + strings. + :param deprecated: If ``True`` or non-empty string, issues a message + indicating that the argument is deprecated and highlights + its deprecation in --help. The message can be customized + by using a string as the value. A deprecated parameter + cannot be required, a ValueError will be raised otherwise. + + .. versionchanged:: 8.2.0 + Introduction of ``deprecated``. + + .. versionchanged:: 8.2 + Adding duplicate parameter names to a :class:`~click.core.Command` will + result in a ``UserWarning`` being shown. + + .. versionchanged:: 8.2 + Adding duplicate parameter names to a :class:`~click.core.Command` will + result in a ``UserWarning`` being shown. + + .. versionchanged:: 8.0 + ``process_value`` validates required parameters and bounded + ``nargs``, and invokes the parameter callback before returning + the value. This allows the callback to validate prompts. + ``full_process_value`` is removed. + + .. versionchanged:: 8.0 + ``autocompletion`` is renamed to ``shell_complete`` and has new + semantics described above. The old name is deprecated and will + be removed in 8.1, until then it will be wrapped to match the + new requirements. + + .. versionchanged:: 8.0 + For ``multiple=True, nargs>1``, the default must be a list of + tuples. + + .. versionchanged:: 8.0 + Setting a default is no longer required for ``nargs>1``, it will + default to ``None``. ``multiple=True`` or ``nargs=-1`` will + default to ``()``. + + .. versionchanged:: 7.1 + Empty environment variables are ignored rather than taking the + empty string value. This makes it possible for scripts to clear + variables if they can't unset them. + + .. versionchanged:: 2.0 + Changed signature for parameter callback to also be passed the + parameter. The old callback format will still work, but it will + raise a warning to give you a chance to migrate the code easier. + """ + + param_type_name = "parameter" + + def __init__( + self, + param_decls: cabc.Sequence[str] | None = None, + type: types.ParamType | t.Any | None = None, + required: bool = False, + # XXX The default historically embed two concepts: + # - the declaration of a Parameter object carrying the default (handy to + # arbitrage the default value of coupled Parameters sharing the same + # self.name, like flag options), + # - and the actual value of the default. + # It is confusing and is the source of many issues discussed in: + # https://github.com/pallets/click/pull/3030 + # In the future, we might think of splitting it in two, not unlike + # Option.is_flag and Option.flag_value: we could have something like + # Parameter.is_default and Parameter.default_value. + default: t.Any | t.Callable[[], t.Any] | None = UNSET, + callback: t.Callable[[Context, Parameter, t.Any], t.Any] | None = None, + nargs: int | None = None, + multiple: bool = False, + metavar: str | None = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: str | cabc.Sequence[str] | None = None, + shell_complete: t.Callable[ + [Context, Parameter, str], list[CompletionItem] | list[str] + ] + | None = None, + deprecated: bool | str = False, + ) -> None: + self.name: str | None + self.opts: list[str] + self.secondary_opts: list[str] + self.name, self.opts, self.secondary_opts = self._parse_decls( + param_decls or (), expose_value + ) + self.type: types.ParamType = types.convert_type(type, default) + + # Default nargs to what the type tells us if we have that + # information available. + if nargs is None: + if self.type.is_composite: + nargs = self.type.arity + else: + nargs = 1 + + self.required = required + self.callback = callback + self.nargs = nargs + self.multiple = multiple + self.expose_value = expose_value + self.default = default + self.is_eager = is_eager + self.metavar = metavar + self.envvar = envvar + self._custom_shell_complete = shell_complete + self.deprecated = deprecated + + if __debug__: + if self.type.is_composite and nargs != self.type.arity: + raise ValueError( + f"'nargs' must be {self.type.arity} (or None) for" + f" type {self.type!r}, but it was {nargs}." + ) + + if required and deprecated: + raise ValueError( + f"The {self.param_type_name} '{self.human_readable_name}' " + "is deprecated and still required. A deprecated " + f"{self.param_type_name} cannot be required." + ) + + def to_info_dict(self) -> dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionchanged:: 8.3.0 + Returns ``None`` for the :attr:`default` if it was not set. + + .. versionadded:: 8.0 + """ + return { + "name": self.name, + "param_type_name": self.param_type_name, + "opts": self.opts, + "secondary_opts": self.secondary_opts, + "type": self.type.to_info_dict(), + "required": self.required, + "nargs": self.nargs, + "multiple": self.multiple, + # We explicitly hide the :attr:`UNSET` value to the user, as we choose to + # make it an implementation detail. And because ``to_info_dict`` has been + # designed for documentation purposes, we return ``None`` instead. + "default": self.default if self.default is not UNSET else None, + "envvar": self.envvar, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def _parse_decls( + self, decls: cabc.Sequence[str], expose_value: bool + ) -> tuple[str | None, list[str], list[str]]: + raise NotImplementedError() + + @property + def human_readable_name(self) -> str: + """Returns the human readable name of this parameter. This is the + same as the name for options, but the metavar for arguments. + """ + return self.name # type: ignore + + def make_metavar(self, ctx: Context) -> str: + if self.metavar is not None: + return self.metavar + + metavar = self.type.get_metavar(param=self, ctx=ctx) + + if metavar is None: + metavar = self.type.name.upper() + + if self.nargs != 1: + metavar += "..." + + return metavar + + @t.overload + def get_default( + self, ctx: Context, call: t.Literal[True] = True + ) -> t.Any | None: ... + + @t.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Any | t.Callable[[], t.Any] | None: ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Any | t.Callable[[], t.Any] | None: + """Get the default for the parameter. Tries + :meth:`Context.lookup_default` first, then the local default. + + :param ctx: Current context. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0.2 + Type casting is no longer performed when getting a default. + + .. versionchanged:: 8.0.1 + Type casting can fail in resilient parsing mode. Invalid + defaults will not prevent showing help text. + + .. versionchanged:: 8.0 + Looks at ``ctx.default_map`` first. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + value = ctx.lookup_default(self.name, call=False) # type: ignore + + if value is UNSET: + value = self.default + + if call and callable(value): + value = value() + + return value + + def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: + raise NotImplementedError() + + def consume_value( + self, ctx: Context, opts: cabc.Mapping[str, t.Any] + ) -> tuple[t.Any, ParameterSource]: + """Returns the parameter value produced by the parser. + + If the parser did not produce a value from user input, the value is either + sourced from the environment variable, the default map, or the parameter's + default value. In that order of precedence. + + If no value is found, an internal sentinel value is returned. + + :meta private: + """ + # Collect from the parse the value passed by the user to the CLI. + value = opts.get(self.name, UNSET) # type: ignore + # If the value is set, it means it was sourced from the command line by the + # parser, otherwise it left unset by default. + source = ( + ParameterSource.COMMANDLINE + if value is not UNSET + else ParameterSource.DEFAULT + ) + + if value is UNSET: + envvar_value = self.value_from_envvar(ctx) + if envvar_value is not None: + value = envvar_value + source = ParameterSource.ENVIRONMENT + + if value is UNSET: + default_map_value = ctx.lookup_default(self.name) # type: ignore + if default_map_value is not UNSET: + value = default_map_value + source = ParameterSource.DEFAULT_MAP + + if value is UNSET: + default_value = self.get_default(ctx) + if default_value is not UNSET: + value = default_value + source = ParameterSource.DEFAULT + + return value, source + + def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any: + """Convert and validate a value against the parameter's + :attr:`type`, :attr:`multiple`, and :attr:`nargs`. + """ + if value in (None, UNSET): + if self.multiple or self.nargs == -1: + return () + else: + return value + + def check_iter(value: t.Any) -> cabc.Iterator[t.Any]: + try: + return _check_iter(value) + except TypeError: + # This should only happen when passing in args manually, + # the parser should construct an iterable when parsing + # the command line. + raise BadParameter( + _("Value must be an iterable."), ctx=ctx, param=self + ) from None + + # Define the conversion function based on nargs and type. + + if self.nargs == 1 or self.type.is_composite: + + def convert(value: t.Any) -> t.Any: + return self.type(value, param=self, ctx=ctx) + + elif self.nargs == -1: + + def convert(value: t.Any) -> t.Any: # tuple[t.Any, ...] + return tuple(self.type(x, self, ctx) for x in check_iter(value)) + + else: # nargs > 1 + + def convert(value: t.Any) -> t.Any: # tuple[t.Any, ...] + value = tuple(check_iter(value)) + + if len(value) != self.nargs: + raise BadParameter( + ngettext( + "Takes {nargs} values but 1 was given.", + "Takes {nargs} values but {len} were given.", + len(value), + ).format(nargs=self.nargs, len=len(value)), + ctx=ctx, + param=self, + ) + + return tuple(self.type(x, self, ctx) for x in value) + + if self.multiple: + return tuple(convert(x) for x in check_iter(value)) + + return convert(value) + + def value_is_missing(self, value: t.Any) -> bool: + """A value is considered missing if: + + - it is :attr:`UNSET`, + - or if it is an empty sequence while the parameter is suppose to have + non-single value (i.e. :attr:`nargs` is not ``1`` or :attr:`multiple` is + set). + + :meta private: + """ + if value is UNSET: + return True + + if (self.nargs != 1 or self.multiple) and value == (): + return True + + return False + + def process_value(self, ctx: Context, value: t.Any) -> t.Any: + """Process the value of this parameter: + + 1. Type cast the value using :meth:`type_cast_value`. + 2. Check if the value is missing (see: :meth:`value_is_missing`), and raise + :exc:`MissingParameter` if it is required. + 3. If a :attr:`callback` is set, call it to have the value replaced by the + result of the callback. If the value was not set, the callback receive + ``None``. This keep the legacy behavior as it was before the introduction of + the :attr:`UNSET` sentinel. + + :meta private: + """ + value = self.type_cast_value(ctx, value) + + if self.required and self.value_is_missing(value): + raise MissingParameter(ctx=ctx, param=self) + + if self.callback is not None: + # Legacy case: UNSET is not exposed directly to the callback, but converted + # to None. + if value is UNSET: + value = None + value = self.callback(ctx, self, value) + + return value + + def resolve_envvar_value(self, ctx: Context) -> str | None: + """Returns the value found in the environment variable(s) attached to this + parameter. + + Environment variables values are `always returned as strings + `_. + + This method returns ``None`` if: + + - the :attr:`envvar` property is not set on the :class:`Parameter`, + - the environment variable is not found in the environment, + - the variable is found in the environment but its value is empty (i.e. the + environment variable is present but has an empty string). + + If :attr:`envvar` is setup with multiple environment variables, + then only the first non-empty value is returned. + + .. caution:: + + The raw value extracted from the environment is not normalized and is + returned as-is. Any normalization or reconciliation is performed later by + the :class:`Parameter`'s :attr:`type`. + + :meta private: + """ + if not self.envvar: + return None + + if isinstance(self.envvar, str): + rv = os.environ.get(self.envvar) + + if rv: + return rv + else: + for envvar in self.envvar: + rv = os.environ.get(envvar) + + # Return the first non-empty value of the list of environment variables. + if rv: + return rv + # Else, absence of value is interpreted as an environment variable that + # is not set, so proceed to the next one. + + return None + + def value_from_envvar(self, ctx: Context) -> str | cabc.Sequence[str] | None: + """Process the raw environment variable string for this parameter. + + Returns the string as-is or splits it into a sequence of strings if the + parameter is expecting multiple values (i.e. its :attr:`nargs` property is set + to a value other than ``1``). + + :meta private: + """ + rv = self.resolve_envvar_value(ctx) + + if rv is not None and self.nargs != 1: + return self.type.split_envvar_value(rv) + + return rv + + def handle_parse_result( + self, ctx: Context, opts: cabc.Mapping[str, t.Any], args: list[str] + ) -> tuple[t.Any, list[str]]: + """Process the value produced by the parser from user input. + + Always process the value through the Parameter's :attr:`type`, wherever it + comes from. + + If the parameter is deprecated, this method warn the user about it. But only if + the value has been explicitly set by the user (and as such, is not coming from + a default). + + :meta private: + """ + with augment_usage_errors(ctx, param=self): + value, source = self.consume_value(ctx, opts) + + ctx.set_parameter_source(self.name, source) # type: ignore + + # Display a deprecation warning if necessary. + if ( + self.deprecated + and value is not UNSET + and source not in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP) + ): + extra_message = ( + f" {self.deprecated}" if isinstance(self.deprecated, str) else "" + ) + message = _( + "DeprecationWarning: The {param_type} {name!r} is deprecated." + "{extra_message}" + ).format( + param_type=self.param_type_name, + name=self.human_readable_name, + extra_message=extra_message, + ) + echo(style(message, fg="red"), err=True) + + # Process the value through the parameter's type. + try: + value = self.process_value(ctx, value) + except Exception: + if not ctx.resilient_parsing: + raise + # In resilient parsing mode, we do not want to fail the command if the + # value is incompatible with the parameter type, so we reset the value + # to UNSET, which will be interpreted as a missing value. + value = UNSET + + # Add parameter's value to the context. + if ( + self.expose_value + # We skip adding the value if it was previously set by another parameter + # targeting the same variable name. This prevents parameters competing for + # the same name to override each other. + and self.name not in ctx.params + ): + # Click is logically enforcing that the name is None if the parameter is + # not to be exposed. We still assert it here to please the type checker. + assert self.name is not None, ( + f"{self!r} parameter's name should not be None when exposing value." + ) + # Normalize UNSET values to None, as we're about to pass them to the + # command function and move them to the pure-Python realm of user-written + # code. + ctx.params[self.name] = value if value is not UNSET else None + + return value, args + + def get_help_record(self, ctx: Context) -> tuple[str, str] | None: + pass + + def get_usage_pieces(self, ctx: Context) -> list[str]: + return [] + + def get_error_hint(self, ctx: Context) -> str: + """Get a stringified version of the param for use in error messages to + indicate which param caused the error. + """ + hint_list = self.opts or [self.human_readable_name] + return " / ".join(f"'{x}'" for x in hint_list) + + def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]: + """Return a list of completions for the incomplete value. If a + ``shell_complete`` function was given during init, it is used. + Otherwise, the :attr:`type` + :meth:`~click.types.ParamType.shell_complete` function is used. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + if self._custom_shell_complete is not None: + results = self._custom_shell_complete(ctx, self, incomplete) + + if results and isinstance(results[0], str): + from click.shell_completion import CompletionItem + + results = [CompletionItem(c) for c in results] + + return t.cast("list[CompletionItem]", results) + + return self.type.shell_complete(ctx, self, incomplete) + + +class Option(Parameter): + """Options are usually optional values on the command line and + have some extra features that arguments don't have. + + All other parameters are passed onwards to the parameter constructor. + + :param show_default: Show the default value for this option in its + help text. Values are not shown by default, unless + :attr:`Context.show_default` is ``True``. If this value is a + string, it shows that string in parentheses instead of the + actual value. This is particularly useful for dynamic options. + For single option boolean flags, the default remains hidden if + its value is ``False``. + :param show_envvar: Controls if an environment variable should be + shown on the help page and error messages. + Normally, environment variables are not shown. + :param prompt: If set to ``True`` or a non empty string then the + user will be prompted for input. If set to ``True`` the prompt + will be the option name capitalized. A deprecated option cannot be + prompted. + :param confirmation_prompt: Prompt a second time to confirm the + value if it was prompted for. Can be set to a string instead of + ``True`` to customize the message. + :param prompt_required: If set to ``False``, the user will be + prompted for input only when the option was specified as a flag + without a value. + :param hide_input: If this is ``True`` then the input on the prompt + will be hidden from the user. This is useful for password input. + :param is_flag: forces this option to act as a flag. The default is + auto detection. + :param flag_value: which value should be used for this flag if it's + enabled. This is set to a boolean automatically if + the option string contains a slash to mark two options. + :param multiple: if this is set to `True` then the argument is accepted + multiple times and recorded. This is similar to ``nargs`` + in how it works but supports arbitrary number of + arguments. + :param count: this flag makes an option increment an integer. + :param allow_from_autoenv: if this is enabled then the value of this + parameter will be pulled from an environment + variable in case a prefix is defined on the + context. + :param help: the help string. + :param hidden: hide this option from help outputs. + :param attrs: Other command arguments described in :class:`Parameter`. + + .. versionchanged:: 8.2 + ``envvar`` used with ``flag_value`` will always use the ``flag_value``, + previously it would use the value of the environment variable. + + .. versionchanged:: 8.1 + Help text indentation is cleaned here instead of only in the + ``@option`` decorator. + + .. versionchanged:: 8.1 + The ``show_default`` parameter overrides + ``Context.show_default``. + + .. versionchanged:: 8.1 + The default of a single option boolean flag is not shown if the + default value is ``False``. + + .. versionchanged:: 8.0.1 + ``type`` is detected from ``flag_value`` if given. + """ + + param_type_name = "option" + + def __init__( + self, + param_decls: cabc.Sequence[str] | None = None, + show_default: bool | str | None = None, + prompt: bool | str = False, + confirmation_prompt: bool | str = False, + prompt_required: bool = True, + hide_input: bool = False, + is_flag: bool | None = None, + flag_value: t.Any = UNSET, + multiple: bool = False, + count: bool = False, + allow_from_autoenv: bool = True, + type: types.ParamType | t.Any | None = None, + help: str | None = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = False, + deprecated: bool | str = False, + **attrs: t.Any, + ) -> None: + if help: + help = inspect.cleandoc(help) + + super().__init__( + param_decls, type=type, multiple=multiple, deprecated=deprecated, **attrs + ) + + if prompt is True: + if self.name is None: + raise TypeError("'name' is required with 'prompt=True'.") + + prompt_text: str | None = self.name.replace("_", " ").capitalize() + elif prompt is False: + prompt_text = None + else: + prompt_text = prompt + + if deprecated: + deprecated_message = ( + f"(DEPRECATED: {deprecated})" + if isinstance(deprecated, str) + else "(DEPRECATED)" + ) + help = help + deprecated_message if help is not None else deprecated_message + + self.prompt = prompt_text + self.confirmation_prompt = confirmation_prompt + self.prompt_required = prompt_required + self.hide_input = hide_input + self.hidden = hidden + + # The _flag_needs_value property tells the parser that this option is a flag + # that cannot be used standalone and needs a value. With this information, the + # parser can determine whether to consider the next user-provided argument in + # the CLI as a value for this flag or as a new option. + # If prompt is enabled but not required, then it opens the possibility for the + # option to gets its value from the user. + self._flag_needs_value = self.prompt is not None and not self.prompt_required + + # Auto-detect if this is a flag or not. + if is_flag is None: + # Implicitly a flag because flag_value was set. + if flag_value is not UNSET: + is_flag = True + # Not a flag, but when used as a flag it shows a prompt. + elif self._flag_needs_value: + is_flag = False + # Implicitly a flag because secondary options names were given. + elif self.secondary_opts: + is_flag = True + # The option is explicitly not a flag. But we do not know yet if it needs a + # value or not. So we look at the default value to determine it. + elif is_flag is False and not self._flag_needs_value: + self._flag_needs_value = self.default is UNSET + + if is_flag: + # Set missing default for flags if not explicitly required or prompted. + if self.default is UNSET and not self.required and not self.prompt: + if multiple: + self.default = () + + # Auto-detect the type of the flag based on the flag_value. + if type is None: + # A flag without a flag_value is a boolean flag. + if flag_value is UNSET: + self.type = types.BoolParamType() + # If the flag value is a boolean, use BoolParamType. + elif isinstance(flag_value, bool): + self.type = types.BoolParamType() + # Otherwise, guess the type from the flag value. + else: + self.type = types.convert_type(None, flag_value) + + self.is_flag: bool = bool(is_flag) + self.is_bool_flag: bool = bool( + is_flag and isinstance(self.type, types.BoolParamType) + ) + self.flag_value: t.Any = flag_value + + # Set boolean flag default to False if unset and not required. + if self.is_bool_flag: + if self.default is UNSET and not self.required: + self.default = False + + # Support the special case of aligning the default value with the flag_value + # for flags whose default is explicitly set to True. Note that as long as we + # have this condition, there is no way a flag can have a default set to True, + # and a flag_value set to something else. Refs: + # https://github.com/pallets/click/issues/3024#issuecomment-3146199461 + # https://github.com/pallets/click/pull/3030/commits/06847da + if self.default is True and self.flag_value is not UNSET: + self.default = self.flag_value + + # Set the default flag_value if it is not set. + if self.flag_value is UNSET: + if self.is_flag: + self.flag_value = True + else: + self.flag_value = None + + # Counting. + self.count = count + if count: + if type is None: + self.type = types.IntRange(min=0) + if self.default is UNSET: + self.default = 0 + + self.allow_from_autoenv = allow_from_autoenv + self.help = help + self.show_default = show_default + self.show_choices = show_choices + self.show_envvar = show_envvar + + if __debug__: + if deprecated and prompt: + raise ValueError("`deprecated` options cannot use `prompt`.") + + if self.nargs == -1: + raise TypeError("nargs=-1 is not supported for options.") + + if not self.is_bool_flag and self.secondary_opts: + raise TypeError("Secondary flag is not valid for non-boolean flag.") + + if self.is_bool_flag and self.hide_input and self.prompt is not None: + raise TypeError( + "'prompt' with 'hide_input' is not valid for boolean flag." + ) + + if self.count: + if self.multiple: + raise TypeError("'count' is not valid with 'multiple'.") + + if self.is_flag: + raise TypeError("'count' is not valid with 'is_flag'.") + + def to_info_dict(self) -> dict[str, t.Any]: + """ + .. versionchanged:: 8.3.0 + Returns ``None`` for the :attr:`flag_value` if it was not set. + """ + info_dict = super().to_info_dict() + info_dict.update( + help=self.help, + prompt=self.prompt, + is_flag=self.is_flag, + # We explicitly hide the :attr:`UNSET` value to the user, as we choose to + # make it an implementation detail. And because ``to_info_dict`` has been + # designed for documentation purposes, we return ``None`` instead. + flag_value=self.flag_value if self.flag_value is not UNSET else None, + count=self.count, + hidden=self.hidden, + ) + return info_dict + + def get_error_hint(self, ctx: Context) -> str: + result = super().get_error_hint(ctx) + if self.show_envvar and self.envvar is not None: + result += f" (env var: '{self.envvar}')" + return result + + def _parse_decls( + self, decls: cabc.Sequence[str], expose_value: bool + ) -> tuple[str | None, list[str], list[str]]: + opts = [] + secondary_opts = [] + name = None + possible_names = [] + + for decl in decls: + if decl.isidentifier(): + if name is not None: + raise TypeError(f"Name '{name}' defined twice") + name = decl + else: + split_char = ";" if decl[:1] == "/" else "/" + if split_char in decl: + first, second = decl.split(split_char, 1) + first = first.rstrip() + if first: + possible_names.append(_split_opt(first)) + opts.append(first) + second = second.lstrip() + if second: + secondary_opts.append(second.lstrip()) + if first == second: + raise ValueError( + f"Boolean option {decl!r} cannot use the" + " same flag for true/false." + ) + else: + possible_names.append(_split_opt(decl)) + opts.append(decl) + + if name is None and possible_names: + possible_names.sort(key=lambda x: -len(x[0])) # group long options first + name = possible_names[0][1].replace("-", "_").lower() + if not name.isidentifier(): + name = None + + if name is None: + if not expose_value: + return None, opts, secondary_opts + raise TypeError( + f"Could not determine name for option with declarations {decls!r}" + ) + + if not opts and not secondary_opts: + raise TypeError( + f"No options defined but a name was passed ({name})." + " Did you mean to declare an argument instead? Did" + f" you mean to pass '--{name}'?" + ) + + return name, opts, secondary_opts + + def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: + if self.multiple: + action = "append" + elif self.count: + action = "count" + else: + action = "store" + + if self.is_flag: + action = f"{action}_const" + + if self.is_bool_flag and self.secondary_opts: + parser.add_option( + obj=self, opts=self.opts, dest=self.name, action=action, const=True + ) + parser.add_option( + obj=self, + opts=self.secondary_opts, + dest=self.name, + action=action, + const=False, + ) + else: + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + const=self.flag_value, + ) + else: + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + nargs=self.nargs, + ) + + def get_help_record(self, ctx: Context) -> tuple[str, str] | None: + if self.hidden: + return None + + any_prefix_is_slash = False + + def _write_opts(opts: cabc.Sequence[str]) -> str: + nonlocal any_prefix_is_slash + + rv, any_slashes = join_options(opts) + + if any_slashes: + any_prefix_is_slash = True + + if not self.is_flag and not self.count: + rv += f" {self.make_metavar(ctx=ctx)}" + + return rv + + rv = [_write_opts(self.opts)] + + if self.secondary_opts: + rv.append(_write_opts(self.secondary_opts)) + + help = self.help or "" + + extra = self.get_help_extra(ctx) + extra_items = [] + if "envvars" in extra: + extra_items.append( + _("env var: {var}").format(var=", ".join(extra["envvars"])) + ) + if "default" in extra: + extra_items.append(_("default: {default}").format(default=extra["default"])) + if "range" in extra: + extra_items.append(extra["range"]) + if "required" in extra: + extra_items.append(_(extra["required"])) + + if extra_items: + extra_str = "; ".join(extra_items) + help = f"{help} [{extra_str}]" if help else f"[{extra_str}]" + + return ("; " if any_prefix_is_slash else " / ").join(rv), help + + def get_help_extra(self, ctx: Context) -> types.OptionHelpExtra: + extra: types.OptionHelpExtra = {} + + if self.show_envvar: + envvar = self.envvar + + if envvar is None: + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + + if envvar is not None: + if isinstance(envvar, str): + extra["envvars"] = (envvar,) + else: + extra["envvars"] = tuple(str(d) for d in envvar) + + # Temporarily enable resilient parsing to avoid type casting + # failing for the default. Might be possible to extend this to + # help formatting in general. + resilient = ctx.resilient_parsing + ctx.resilient_parsing = True + + try: + default_value = self.get_default(ctx, call=False) + finally: + ctx.resilient_parsing = resilient + + show_default = False + show_default_is_str = False + + if self.show_default is not None: + if isinstance(self.show_default, str): + show_default_is_str = show_default = True + else: + show_default = self.show_default + elif ctx.show_default is not None: + show_default = ctx.show_default + + if show_default_is_str or ( + show_default and (default_value not in (None, UNSET)) + ): + if show_default_is_str: + default_string = f"({self.show_default})" + elif isinstance(default_value, (list, tuple)): + default_string = ", ".join(str(d) for d in default_value) + elif isinstance(default_value, enum.Enum): + default_string = default_value.name + elif inspect.isfunction(default_value): + default_string = _("(dynamic)") + elif self.is_bool_flag and self.secondary_opts: + # For boolean flags that have distinct True/False opts, + # use the opt without prefix instead of the value. + default_string = _split_opt( + (self.opts if default_value else self.secondary_opts)[0] + )[1] + elif self.is_bool_flag and not self.secondary_opts and not default_value: + default_string = "" + elif default_value == "": + default_string = '""' + else: + default_string = str(default_value) + + if default_string: + extra["default"] = default_string + + if ( + isinstance(self.type, types._NumberRangeBase) + # skip count with default range type + and not (self.count and self.type.min == 0 and self.type.max is None) + ): + range_str = self.type._describe_range() + + if range_str: + extra["range"] = range_str + + if self.required: + extra["required"] = "required" + + return extra + + def prompt_for_value(self, ctx: Context) -> t.Any: + """This is an alternative flow that can be activated in the full + value processing if a value does not exist. It will prompt the + user until a valid value exists and then returns the processed + value as result. + """ + assert self.prompt is not None + + # Calculate the default before prompting anything to lock in the value before + # attempting any user interaction. + default = self.get_default(ctx) + + # A boolean flag can use a simplified [y/n] confirmation prompt. + if self.is_bool_flag: + # If we have no boolean default, we force the user to explicitly provide + # one. + if default in (UNSET, None): + default = None + # Nothing prevent you to declare an option that is simultaneously: + # 1) auto-detected as a boolean flag, + # 2) allowed to prompt, and + # 3) still declare a non-boolean default. + # This forced casting into a boolean is necessary to align any non-boolean + # default to the prompt, which is going to be a [y/n]-style confirmation + # because the option is still a boolean flag. That way, instead of [y/n], + # we get [Y/n] or [y/N] depending on the truthy value of the default. + # Refs: https://github.com/pallets/click/pull/3030#discussion_r2289180249 + else: + default = bool(default) + return confirm(self.prompt, default) + + # If show_default is set to True/False, provide this to `prompt` as well. For + # non-bool values of `show_default`, we use `prompt`'s default behavior + prompt_kwargs: t.Any = {} + if isinstance(self.show_default, bool): + prompt_kwargs["show_default"] = self.show_default + + return prompt( + self.prompt, + # Use ``None`` to inform the prompt() function to reiterate until a valid + # value is provided by the user if we have no default. + default=None if default is UNSET else default, + type=self.type, + hide_input=self.hide_input, + show_choices=self.show_choices, + confirmation_prompt=self.confirmation_prompt, + value_proc=lambda x: self.process_value(ctx, x), + **prompt_kwargs, + ) + + def resolve_envvar_value(self, ctx: Context) -> str | None: + """:class:`Option` resolves its environment variable the same way as + :func:`Parameter.resolve_envvar_value`, but it also supports + :attr:`Context.auto_envvar_prefix`. If we could not find an environment from + the :attr:`envvar` property, we fallback on :attr:`Context.auto_envvar_prefix` + to build dynamiccaly the environment variable name using the + :python:`{ctx.auto_envvar_prefix}_{self.name.upper()}` template. + + :meta private: + """ + rv = super().resolve_envvar_value(ctx) + + if rv is not None: + return rv + + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + rv = os.environ.get(envvar) + + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Any: + """For :class:`Option`, this method processes the raw environment variable + string the same way as :func:`Parameter.value_from_envvar` does. + + But in the case of non-boolean flags, the value is analyzed to determine if the + flag is activated or not, and returns a boolean of its activation, or the + :attr:`flag_value` if the latter is set. + + This method also takes care of repeated options (i.e. options with + :attr:`multiple` set to ``True``). + + :meta private: + """ + rv = self.resolve_envvar_value(ctx) + + # Absent environment variable or an empty string is interpreted as unset. + if rv is None: + return None + + # Non-boolean flags are more liberal in what they accept. But a flag being a + # flag, its envvar value still needs to be analyzed to determine if the flag is + # activated or not. + if self.is_flag and not self.is_bool_flag: + # If the flag_value is set and match the envvar value, return it + # directly. + if self.flag_value is not UNSET and rv == self.flag_value: + return self.flag_value + # Analyze the envvar value as a boolean to know if the flag is + # activated or not. + return types.BoolParamType.str_to_bool(rv) + + # Split the envvar value if it is allowed to be repeated. + value_depth = (self.nargs != 1) + bool(self.multiple) + if value_depth > 0: + multi_rv = self.type.split_envvar_value(rv) + if self.multiple and self.nargs != 1: + multi_rv = batch(multi_rv, self.nargs) # type: ignore[assignment] + + return multi_rv + + return rv + + def consume_value( + self, ctx: Context, opts: cabc.Mapping[str, Parameter] + ) -> tuple[t.Any, ParameterSource]: + """For :class:`Option`, the value can be collected from an interactive prompt + if the option is a flag that needs a value (and the :attr:`prompt` property is + set). + + Additionally, this method handles flag option that are activated without a + value, in which case the :attr:`flag_value` is returned. + + :meta private: + """ + value, source = super().consume_value(ctx, opts) + + # The parser will emit a sentinel value if the option is allowed to as a flag + # without a value. + if value is FLAG_NEEDS_VALUE: + # If the option allows for a prompt, we start an interaction with the user. + if self.prompt is not None and not ctx.resilient_parsing: + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + # Else the flag takes its flag_value as value. + else: + value = self.flag_value + source = ParameterSource.COMMANDLINE + + # A flag which is activated always returns the flag value, unless the value + # comes from the explicitly sets default. + elif ( + self.is_flag + and value is True + and not self.is_bool_flag + and source not in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP) + ): + value = self.flag_value + + # Re-interpret a multiple option which has been sent as-is by the parser. + # Here we replace each occurrence of value-less flags (marked by the + # FLAG_NEEDS_VALUE sentinel) with the flag_value. + elif ( + self.multiple + and value is not UNSET + and source not in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP) + and any(v is FLAG_NEEDS_VALUE for v in value) + ): + value = [self.flag_value if v is FLAG_NEEDS_VALUE else v for v in value] + source = ParameterSource.COMMANDLINE + + # The value wasn't set, or used the param's default, prompt for one to the user + # if prompting is enabled. + elif ( + ( + value is UNSET + or source in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP) + ) + and self.prompt is not None + and (self.required or self.prompt_required) + and not ctx.resilient_parsing + ): + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + + return value, source + + def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any: + if self.is_flag and not self.required: + if value is UNSET: + if self.is_bool_flag: + # If the flag is a boolean flag, we return False if it is not set. + value = False + return super().type_cast_value(ctx, value) + + +class Argument(Parameter): + """Arguments are positional parameters to a command. They generally + provide fewer features than options but can have infinite ``nargs`` + and are required by default. + + All parameters are passed onwards to the constructor of :class:`Parameter`. + """ + + param_type_name = "argument" + + def __init__( + self, + param_decls: cabc.Sequence[str], + required: bool | None = None, + **attrs: t.Any, + ) -> None: + # Auto-detect the requirement status of the argument if not explicitly set. + if required is None: + # The argument gets automatically required if it has no explicit default + # value set and is setup to match at least one value. + if attrs.get("default", UNSET) is UNSET: + required = attrs.get("nargs", 1) > 0 + # If the argument has a default value, it is not required. + else: + required = False + + if "multiple" in attrs: + raise TypeError("__init__() got an unexpected keyword argument 'multiple'.") + + super().__init__(param_decls, required=required, **attrs) + + @property + def human_readable_name(self) -> str: + if self.metavar is not None: + return self.metavar + return self.name.upper() # type: ignore + + def make_metavar(self, ctx: Context) -> str: + if self.metavar is not None: + return self.metavar + var = self.type.get_metavar(param=self, ctx=ctx) + if not var: + var = self.name.upper() # type: ignore + if self.deprecated: + var += "!" + if not self.required: + var = f"[{var}]" + if self.nargs != 1: + var += "..." + return var + + def _parse_decls( + self, decls: cabc.Sequence[str], expose_value: bool + ) -> tuple[str | None, list[str], list[str]]: + if not decls: + if not expose_value: + return None, [], [] + raise TypeError("Argument is marked as exposed, but does not have a name.") + if len(decls) == 1: + name = arg = decls[0] + name = name.replace("-", "_").lower() + else: + raise TypeError( + "Arguments take exactly one parameter declaration, got" + f" {len(decls)}: {decls}." + ) + return name, [arg], [] + + def get_usage_pieces(self, ctx: Context) -> list[str]: + return [self.make_metavar(ctx)] + + def get_error_hint(self, ctx: Context) -> str: + return f"'{self.make_metavar(ctx)}'" + + def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: + parser.add_argument(dest=self.name, nargs=self.nargs, obj=self) + + +def __getattr__(name: str) -> object: + import warnings + + if name == "BaseCommand": + warnings.warn( + "'BaseCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Command' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _BaseCommand + + if name == "MultiCommand": + warnings.warn( + "'MultiCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Group' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _MultiCommand + + raise AttributeError(name) diff --git a/venv/lib/python3.12/site-packages/click/decorators.py b/venv/lib/python3.12/site-packages/click/decorators.py new file mode 100644 index 0000000..21f4c34 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/decorators.py @@ -0,0 +1,551 @@ +from __future__ import annotations + +import inspect +import typing as t +from functools import update_wrapper +from gettext import gettext as _ + +from .core import Argument +from .core import Command +from .core import Context +from .core import Group +from .core import Option +from .core import Parameter +from .globals import get_current_context +from .utils import echo + +if t.TYPE_CHECKING: + import typing_extensions as te + + P = te.ParamSpec("P") + +R = t.TypeVar("R") +T = t.TypeVar("T") +_AnyCallable = t.Callable[..., t.Any] +FC = t.TypeVar("FC", bound="_AnyCallable | Command") + + +def pass_context(f: t.Callable[te.Concatenate[Context, P], R]) -> t.Callable[P, R]: + """Marks a callback as wanting to receive the current context + object as first argument. + """ + + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + return f(get_current_context(), *args, **kwargs) + + return update_wrapper(new_func, f) + + +def pass_obj(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: + """Similar to :func:`pass_context`, but only pass the object on the + context onwards (:attr:`Context.obj`). This is useful if that object + represents the state of a nested system. + """ + + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + return f(get_current_context().obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + +def make_pass_decorator( + object_type: type[T], ensure: bool = False +) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]: + """Given an object type this creates a decorator that will work + similar to :func:`pass_obj` but instead of passing the object of the + current context, it will find the innermost context of type + :func:`object_type`. + + This generates a decorator that works roughly like this:: + + from functools import update_wrapper + + def decorator(f): + @pass_context + def new_func(ctx, *args, **kwargs): + obj = ctx.find_object(object_type) + return ctx.invoke(f, obj, *args, **kwargs) + return update_wrapper(new_func, f) + return decorator + + :param object_type: the type of the object to pass. + :param ensure: if set to `True`, a new object will be created and + remembered on the context if it's not there yet. + """ + + def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + ctx = get_current_context() + + obj: T | None + if ensure: + obj = ctx.ensure_object(object_type) + else: + obj = ctx.find_object(object_type) + + if obj is None: + raise RuntimeError( + "Managed to invoke callback without a context" + f" object of type {object_type.__name__!r}" + " existing." + ) + + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + return decorator + + +def pass_meta_key( + key: str, *, doc_description: str | None = None +) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]: + """Create a decorator that passes a key from + :attr:`click.Context.meta` as the first argument to the decorated + function. + + :param key: Key in ``Context.meta`` to pass. + :param doc_description: Description of the object being passed, + inserted into the decorator's docstring. Defaults to "the 'key' + key from Context.meta". + + .. versionadded:: 8.0 + """ + + def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + ctx = get_current_context() + obj = ctx.meta[key] + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + if doc_description is None: + doc_description = f"the {key!r} key from :attr:`click.Context.meta`" + + decorator.__doc__ = ( + f"Decorator that passes {doc_description} as the first argument" + " to the decorated function." + ) + return decorator + + +CmdType = t.TypeVar("CmdType", bound=Command) + + +# variant: no call, directly as decorator for a function. +@t.overload +def command(name: _AnyCallable) -> Command: ... + + +# variant: with positional name and with positional or keyword cls argument: +# @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...) +@t.overload +def command( + name: str | None, + cls: type[CmdType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], CmdType]: ... + + +# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...) +@t.overload +def command( + name: None = None, + *, + cls: type[CmdType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], CmdType]: ... + + +# variant: with optional string name, no cls argument provided. +@t.overload +def command( + name: str | None = ..., cls: None = None, **attrs: t.Any +) -> t.Callable[[_AnyCallable], Command]: ... + + +def command( + name: str | _AnyCallable | None = None, + cls: type[CmdType] | None = None, + **attrs: t.Any, +) -> Command | t.Callable[[_AnyCallable], Command | CmdType]: + r"""Creates a new :class:`Command` and uses the decorated function as + callback. This will also automatically attach all decorated + :func:`option`\s and :func:`argument`\s as parameters to the command. + + The name of the command defaults to the name of the function, converted to + lowercase, with underscores ``_`` replaced by dashes ``-``, and the suffixes + ``_command``, ``_cmd``, ``_group``, and ``_grp`` are removed. For example, + ``init_data_command`` becomes ``init-data``. + + All keyword arguments are forwarded to the underlying command class. + For the ``params`` argument, any decorated params are appended to + the end of the list. + + Once decorated the function turns into a :class:`Command` instance + that can be invoked as a command line utility or be attached to a + command :class:`Group`. + + :param name: The name of the command. Defaults to modifying the function's + name as described above. + :param cls: The command class to create. Defaults to :class:`Command`. + + .. versionchanged:: 8.2 + The suffixes ``_command``, ``_cmd``, ``_group``, and ``_grp`` are + removed when generating the name. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.1 + The ``params`` argument can be used. Decorated params are + appended to the end of the list. + """ + + func: t.Callable[[_AnyCallable], t.Any] | None = None + + if callable(name): + func = name + name = None + assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class." + assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments." + + if cls is None: + cls = t.cast("type[CmdType]", Command) + + def decorator(f: _AnyCallable) -> CmdType: + if isinstance(f, Command): + raise TypeError("Attempted to convert a callback into a command twice.") + + attr_params = attrs.pop("params", None) + params = attr_params if attr_params is not None else [] + + try: + decorator_params = f.__click_params__ # type: ignore + except AttributeError: + pass + else: + del f.__click_params__ # type: ignore + params.extend(reversed(decorator_params)) + + if attrs.get("help") is None: + attrs["help"] = f.__doc__ + + if t.TYPE_CHECKING: + assert cls is not None + assert not callable(name) + + if name is not None: + cmd_name = name + else: + cmd_name = f.__name__.lower().replace("_", "-") + cmd_left, sep, suffix = cmd_name.rpartition("-") + + if sep and suffix in {"command", "cmd", "group", "grp"}: + cmd_name = cmd_left + + cmd = cls(name=cmd_name, callback=f, params=params, **attrs) + cmd.__doc__ = f.__doc__ + return cmd + + if func is not None: + return decorator(func) + + return decorator + + +GrpType = t.TypeVar("GrpType", bound=Group) + + +# variant: no call, directly as decorator for a function. +@t.overload +def group(name: _AnyCallable) -> Group: ... + + +# variant: with positional name and with positional or keyword cls argument: +# @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...) +@t.overload +def group( + name: str | None, + cls: type[GrpType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], GrpType]: ... + + +# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...) +@t.overload +def group( + name: None = None, + *, + cls: type[GrpType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], GrpType]: ... + + +# variant: with optional string name, no cls argument provided. +@t.overload +def group( + name: str | None = ..., cls: None = None, **attrs: t.Any +) -> t.Callable[[_AnyCallable], Group]: ... + + +def group( + name: str | _AnyCallable | None = None, + cls: type[GrpType] | None = None, + **attrs: t.Any, +) -> Group | t.Callable[[_AnyCallable], Group | GrpType]: + """Creates a new :class:`Group` with a function as callback. This + works otherwise the same as :func:`command` just that the `cls` + parameter is set to :class:`Group`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + """ + if cls is None: + cls = t.cast("type[GrpType]", Group) + + if callable(name): + return command(cls=cls, **attrs)(name) + + return command(name, cls, **attrs) + + +def _param_memo(f: t.Callable[..., t.Any], param: Parameter) -> None: + if isinstance(f, Command): + f.params.append(param) + else: + if not hasattr(f, "__click_params__"): + f.__click_params__ = [] # type: ignore + + f.__click_params__.append(param) # type: ignore + + +def argument( + *param_decls: str, cls: type[Argument] | None = None, **attrs: t.Any +) -> t.Callable[[FC], FC]: + """Attaches an argument to the command. All positional arguments are + passed as parameter declarations to :class:`Argument`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Argument` instance manually + and attaching it to the :attr:`Command.params` list. + + For the default argument class, refer to :class:`Argument` and + :class:`Parameter` for descriptions of parameters. + + :param cls: the argument class to instantiate. This defaults to + :class:`Argument`. + :param param_decls: Passed as positional arguments to the constructor of + ``cls``. + :param attrs: Passed as keyword arguments to the constructor of ``cls``. + """ + if cls is None: + cls = Argument + + def decorator(f: FC) -> FC: + _param_memo(f, cls(param_decls, **attrs)) + return f + + return decorator + + +def option( + *param_decls: str, cls: type[Option] | None = None, **attrs: t.Any +) -> t.Callable[[FC], FC]: + """Attaches an option to the command. All positional arguments are + passed as parameter declarations to :class:`Option`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Option` instance manually + and attaching it to the :attr:`Command.params` list. + + For the default option class, refer to :class:`Option` and + :class:`Parameter` for descriptions of parameters. + + :param cls: the option class to instantiate. This defaults to + :class:`Option`. + :param param_decls: Passed as positional arguments to the constructor of + ``cls``. + :param attrs: Passed as keyword arguments to the constructor of ``cls``. + """ + if cls is None: + cls = Option + + def decorator(f: FC) -> FC: + _param_memo(f, cls(param_decls, **attrs)) + return f + + return decorator + + +def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--yes`` option which shows a prompt before continuing if + not passed. If the prompt is declined, the program will exit. + + :param param_decls: One or more option names. Defaults to the single + value ``"--yes"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value: + ctx.abort() + + if not param_decls: + param_decls = ("--yes",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("callback", callback) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("prompt", "Do you want to continue?") + kwargs.setdefault("help", "Confirm the action without prompting.") + return option(*param_decls, **kwargs) + + +def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--password`` option which prompts for a password, hiding + input and asking to enter the value again for confirmation. + + :param param_decls: One or more option names. Defaults to the single + value ``"--password"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + if not param_decls: + param_decls = ("--password",) + + kwargs.setdefault("prompt", True) + kwargs.setdefault("confirmation_prompt", True) + kwargs.setdefault("hide_input", True) + return option(*param_decls, **kwargs) + + +def version_option( + version: str | None = None, + *param_decls: str, + package_name: str | None = None, + prog_name: str | None = None, + message: str | None = None, + **kwargs: t.Any, +) -> t.Callable[[FC], FC]: + """Add a ``--version`` option which immediately prints the version + number and exits the program. + + If ``version`` is not provided, Click will try to detect it using + :func:`importlib.metadata.version` to get the version for the + ``package_name``. + + If ``package_name`` is not provided, Click will try to detect it by + inspecting the stack frames. This will be used to detect the + version, so it must match the name of the installed package. + + :param version: The version number to show. If not provided, Click + will try to detect it. + :param param_decls: One or more option names. Defaults to the single + value ``"--version"``. + :param package_name: The package name to detect the version from. If + not provided, Click will try to detect it. + :param prog_name: The name of the CLI to show in the message. If not + provided, it will be detected from the command. + :param message: The message to show. The values ``%(prog)s``, + ``%(package)s``, and ``%(version)s`` are available. Defaults to + ``"%(prog)s, version %(version)s"``. + :param kwargs: Extra arguments are passed to :func:`option`. + :raise RuntimeError: ``version`` could not be detected. + + .. versionchanged:: 8.0 + Add the ``package_name`` parameter, and the ``%(package)s`` + value for messages. + + .. versionchanged:: 8.0 + Use :mod:`importlib.metadata` instead of ``pkg_resources``. The + version is detected based on the package name, not the entry + point name. The Python package name must match the installed + package name, or be passed with ``package_name=``. + """ + if message is None: + message = _("%(prog)s, version %(version)s") + + if version is None and package_name is None: + frame = inspect.currentframe() + f_back = frame.f_back if frame is not None else None + f_globals = f_back.f_globals if f_back is not None else None + # break reference cycle + # https://docs.python.org/3/library/inspect.html#the-interpreter-stack + del frame + + if f_globals is not None: + package_name = f_globals.get("__name__") + + if package_name == "__main__": + package_name = f_globals.get("__package__") + + if package_name: + package_name = package_name.partition(".")[0] + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + + nonlocal prog_name + nonlocal version + + if prog_name is None: + prog_name = ctx.find_root().info_name + + if version is None and package_name is not None: + import importlib.metadata + + try: + version = importlib.metadata.version(package_name) + except importlib.metadata.PackageNotFoundError: + raise RuntimeError( + f"{package_name!r} is not installed. Try passing" + " 'package_name' instead." + ) from None + + if version is None: + raise RuntimeError( + f"Could not determine the version for {package_name!r} automatically." + ) + + echo( + message % {"prog": prog_name, "package": package_name, "version": version}, + color=ctx.color, + ) + ctx.exit() + + if not param_decls: + param_decls = ("--version",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show the version and exit.")) + kwargs["callback"] = callback + return option(*param_decls, **kwargs) + + +def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Pre-configured ``--help`` option which immediately prints the help page + and exits the program. + + :param param_decls: One or more option names. Defaults to the single + value ``"--help"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + + def show_help(ctx: Context, param: Parameter, value: bool) -> None: + """Callback that print the help page on ```` and exits.""" + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + if not param_decls: + param_decls = ("--help",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show this message and exit.")) + kwargs.setdefault("callback", show_help) + + return option(*param_decls, **kwargs) diff --git a/venv/lib/python3.12/site-packages/click/exceptions.py b/venv/lib/python3.12/site-packages/click/exceptions.py new file mode 100644 index 0000000..4d782ee --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/exceptions.py @@ -0,0 +1,308 @@ +from __future__ import annotations + +import collections.abc as cabc +import typing as t +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import get_text_stderr +from .globals import resolve_color_default +from .utils import echo +from .utils import format_filename + +if t.TYPE_CHECKING: + from .core import Command + from .core import Context + from .core import Parameter + + +def _join_param_hints(param_hint: cabc.Sequence[str] | str | None) -> str | None: + if param_hint is not None and not isinstance(param_hint, str): + return " / ".join(repr(x) for x in param_hint) + + return param_hint + + +class ClickException(Exception): + """An exception that Click can handle and show to the user.""" + + #: The exit code for this exception. + exit_code = 1 + + def __init__(self, message: str) -> None: + super().__init__(message) + # The context will be removed by the time we print the message, so cache + # the color settings here to be used later on (in `show`) + self.show_color: bool | None = resolve_color_default() + self.message = message + + def format_message(self) -> str: + return self.message + + def __str__(self) -> str: + return self.message + + def show(self, file: t.IO[t.Any] | None = None) -> None: + if file is None: + file = get_text_stderr() + + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=self.show_color, + ) + + +class UsageError(ClickException): + """An internal exception that signals a usage error. This typically + aborts any further handling. + + :param message: the error message to display. + :param ctx: optionally the context that caused this error. Click will + fill in the context automatically in some situations. + """ + + exit_code = 2 + + def __init__(self, message: str, ctx: Context | None = None) -> None: + super().__init__(message) + self.ctx = ctx + self.cmd: Command | None = self.ctx.command if self.ctx else None + + def show(self, file: t.IO[t.Any] | None = None) -> None: + if file is None: + file = get_text_stderr() + color = None + hint = "" + if ( + self.ctx is not None + and self.ctx.command.get_help_option(self.ctx) is not None + ): + hint = _("Try '{command} {option}' for help.").format( + command=self.ctx.command_path, option=self.ctx.help_option_names[0] + ) + hint = f"{hint}\n" + if self.ctx is not None: + color = self.ctx.color + echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color) + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=color, + ) + + +class BadParameter(UsageError): + """An exception that formats out a standardized error message for a + bad parameter. This is useful when thrown from a callback or type as + Click will attach contextual information to it (for instance, which + parameter it is). + + .. versionadded:: 2.0 + + :param param: the parameter object that caused this error. This can + be left out, and Click will attach this info itself + if possible. + :param param_hint: a string that shows up as parameter name. This + can be used as alternative to `param` in cases + where custom validation should happen. If it is + a string it's used as such, if it's a list then + each item is quoted and separated. + """ + + def __init__( + self, + message: str, + ctx: Context | None = None, + param: Parameter | None = None, + param_hint: cabc.Sequence[str] | str | None = None, + ) -> None: + super().__init__(message, ctx) + self.param = param + self.param_hint = param_hint + + def format_message(self) -> str: + if self.param_hint is not None: + param_hint = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) # type: ignore + else: + return _("Invalid value: {message}").format(message=self.message) + + return _("Invalid value for {param_hint}: {message}").format( + param_hint=_join_param_hints(param_hint), message=self.message + ) + + +class MissingParameter(BadParameter): + """Raised if click required an option or argument but it was not + provided when invoking the script. + + .. versionadded:: 4.0 + + :param param_type: a string that indicates the type of the parameter. + The default is to inherit the parameter type from + the given `param`. Valid values are ``'parameter'``, + ``'option'`` or ``'argument'``. + """ + + def __init__( + self, + message: str | None = None, + ctx: Context | None = None, + param: Parameter | None = None, + param_hint: cabc.Sequence[str] | str | None = None, + param_type: str | None = None, + ) -> None: + super().__init__(message or "", ctx, param, param_hint) + self.param_type = param_type + + def format_message(self) -> str: + if self.param_hint is not None: + param_hint: cabc.Sequence[str] | str | None = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) # type: ignore + else: + param_hint = None + + param_hint = _join_param_hints(param_hint) + param_hint = f" {param_hint}" if param_hint else "" + + param_type = self.param_type + if param_type is None and self.param is not None: + param_type = self.param.param_type_name + + msg = self.message + if self.param is not None: + msg_extra = self.param.type.get_missing_message( + param=self.param, ctx=self.ctx + ) + if msg_extra: + if msg: + msg += f". {msg_extra}" + else: + msg = msg_extra + + msg = f" {msg}" if msg else "" + + # Translate param_type for known types. + if param_type == "argument": + missing = _("Missing argument") + elif param_type == "option": + missing = _("Missing option") + elif param_type == "parameter": + missing = _("Missing parameter") + else: + missing = _("Missing {param_type}").format(param_type=param_type) + + return f"{missing}{param_hint}.{msg}" + + def __str__(self) -> str: + if not self.message: + param_name = self.param.name if self.param else None + return _("Missing parameter: {param_name}").format(param_name=param_name) + else: + return self.message + + +class NoSuchOption(UsageError): + """Raised if click attempted to handle an option that does not + exist. + + .. versionadded:: 4.0 + """ + + def __init__( + self, + option_name: str, + message: str | None = None, + possibilities: cabc.Sequence[str] | None = None, + ctx: Context | None = None, + ) -> None: + if message is None: + message = _("No such option: {name}").format(name=option_name) + + super().__init__(message, ctx) + self.option_name = option_name + self.possibilities = possibilities + + def format_message(self) -> str: + if not self.possibilities: + return self.message + + possibility_str = ", ".join(sorted(self.possibilities)) + suggest = ngettext( + "Did you mean {possibility}?", + "(Possible options: {possibilities})", + len(self.possibilities), + ).format(possibility=possibility_str, possibilities=possibility_str) + return f"{self.message} {suggest}" + + +class BadOptionUsage(UsageError): + """Raised if an option is generally supplied but the use of the option + was incorrect. This is for instance raised if the number of arguments + for an option is not correct. + + .. versionadded:: 4.0 + + :param option_name: the name of the option being used incorrectly. + """ + + def __init__( + self, option_name: str, message: str, ctx: Context | None = None + ) -> None: + super().__init__(message, ctx) + self.option_name = option_name + + +class BadArgumentUsage(UsageError): + """Raised if an argument is generally supplied but the use of the argument + was incorrect. This is for instance raised if the number of values + for an argument is not correct. + + .. versionadded:: 6.0 + """ + + +class NoArgsIsHelpError(UsageError): + def __init__(self, ctx: Context) -> None: + self.ctx: Context + super().__init__(ctx.get_help(), ctx=ctx) + + def show(self, file: t.IO[t.Any] | None = None) -> None: + echo(self.format_message(), file=file, err=True, color=self.ctx.color) + + +class FileError(ClickException): + """Raised if a file cannot be opened.""" + + def __init__(self, filename: str, hint: str | None = None) -> None: + if hint is None: + hint = _("unknown error") + + super().__init__(hint) + self.ui_filename: str = format_filename(filename) + self.filename = filename + + def format_message(self) -> str: + return _("Could not open file {filename!r}: {message}").format( + filename=self.ui_filename, message=self.message + ) + + +class Abort(RuntimeError): + """An internal signalling exception that signals Click to abort.""" + + +class Exit(RuntimeError): + """An exception that indicates that the application should exit with some + status code. + + :param code: the status code to exit with. + """ + + __slots__ = ("exit_code",) + + def __init__(self, code: int = 0) -> None: + self.exit_code: int = code diff --git a/venv/lib/python3.12/site-packages/click/formatting.py b/venv/lib/python3.12/site-packages/click/formatting.py new file mode 100644 index 0000000..0b64f83 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/formatting.py @@ -0,0 +1,301 @@ +from __future__ import annotations + +import collections.abc as cabc +from contextlib import contextmanager +from gettext import gettext as _ + +from ._compat import term_len +from .parser import _split_opt + +# Can force a width. This is used by the test system +FORCED_WIDTH: int | None = None + + +def measure_table(rows: cabc.Iterable[tuple[str, str]]) -> tuple[int, ...]: + widths: dict[int, int] = {} + + for row in rows: + for idx, col in enumerate(row): + widths[idx] = max(widths.get(idx, 0), term_len(col)) + + return tuple(y for x, y in sorted(widths.items())) + + +def iter_rows( + rows: cabc.Iterable[tuple[str, str]], col_count: int +) -> cabc.Iterator[tuple[str, ...]]: + for row in rows: + yield row + ("",) * (col_count - len(row)) + + +def wrap_text( + text: str, + width: int = 78, + initial_indent: str = "", + subsequent_indent: str = "", + preserve_paragraphs: bool = False, +) -> str: + """A helper function that intelligently wraps text. By default, it + assumes that it operates on a single paragraph of text but if the + `preserve_paragraphs` parameter is provided it will intelligently + handle paragraphs (defined by two empty lines). + + If paragraphs are handled, a paragraph can be prefixed with an empty + line containing the ``\\b`` character (``\\x08``) to indicate that + no rewrapping should happen in that block. + + :param text: the text that should be rewrapped. + :param width: the maximum width for the text. + :param initial_indent: the initial indent that should be placed on the + first line as a string. + :param subsequent_indent: the indent string that should be placed on + each consecutive line. + :param preserve_paragraphs: if this flag is set then the wrapping will + intelligently handle paragraphs. + """ + from ._textwrap import TextWrapper + + text = text.expandtabs() + wrapper = TextWrapper( + width, + initial_indent=initial_indent, + subsequent_indent=subsequent_indent, + replace_whitespace=False, + ) + if not preserve_paragraphs: + return wrapper.fill(text) + + p: list[tuple[int, bool, str]] = [] + buf: list[str] = [] + indent = None + + def _flush_par() -> None: + if not buf: + return + if buf[0].strip() == "\b": + p.append((indent or 0, True, "\n".join(buf[1:]))) + else: + p.append((indent or 0, False, " ".join(buf))) + del buf[:] + + for line in text.splitlines(): + if not line: + _flush_par() + indent = None + else: + if indent is None: + orig_len = term_len(line) + line = line.lstrip() + indent = orig_len - term_len(line) + buf.append(line) + _flush_par() + + rv = [] + for indent, raw, text in p: + with wrapper.extra_indent(" " * indent): + if raw: + rv.append(wrapper.indent_only(text)) + else: + rv.append(wrapper.fill(text)) + + return "\n\n".join(rv) + + +class HelpFormatter: + """This class helps with formatting text-based help pages. It's + usually just needed for very special internal cases, but it's also + exposed so that developers can write their own fancy outputs. + + At present, it always writes into memory. + + :param indent_increment: the additional increment for each level. + :param width: the width for the text. This defaults to the terminal + width clamped to a maximum of 78. + """ + + def __init__( + self, + indent_increment: int = 2, + width: int | None = None, + max_width: int | None = None, + ) -> None: + self.indent_increment = indent_increment + if max_width is None: + max_width = 80 + if width is None: + import shutil + + width = FORCED_WIDTH + if width is None: + width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50) + self.width = width + self.current_indent: int = 0 + self.buffer: list[str] = [] + + def write(self, string: str) -> None: + """Writes a unicode string into the internal buffer.""" + self.buffer.append(string) + + def indent(self) -> None: + """Increases the indentation.""" + self.current_indent += self.indent_increment + + def dedent(self) -> None: + """Decreases the indentation.""" + self.current_indent -= self.indent_increment + + def write_usage(self, prog: str, args: str = "", prefix: str | None = None) -> None: + """Writes a usage line into the buffer. + + :param prog: the program name. + :param args: whitespace separated list of arguments. + :param prefix: The prefix for the first line. Defaults to + ``"Usage: "``. + """ + if prefix is None: + prefix = f"{_('Usage:')} " + + usage_prefix = f"{prefix:>{self.current_indent}}{prog} " + text_width = self.width - self.current_indent + + if text_width >= (term_len(usage_prefix) + 20): + # The arguments will fit to the right of the prefix. + indent = " " * term_len(usage_prefix) + self.write( + wrap_text( + args, + text_width, + initial_indent=usage_prefix, + subsequent_indent=indent, + ) + ) + else: + # The prefix is too long, put the arguments on the next line. + self.write(usage_prefix) + self.write("\n") + indent = " " * (max(self.current_indent, term_len(prefix)) + 4) + self.write( + wrap_text( + args, text_width, initial_indent=indent, subsequent_indent=indent + ) + ) + + self.write("\n") + + def write_heading(self, heading: str) -> None: + """Writes a heading into the buffer.""" + self.write(f"{'':>{self.current_indent}}{heading}:\n") + + def write_paragraph(self) -> None: + """Writes a paragraph into the buffer.""" + if self.buffer: + self.write("\n") + + def write_text(self, text: str) -> None: + """Writes re-indented text into the buffer. This rewraps and + preserves paragraphs. + """ + indent = " " * self.current_indent + self.write( + wrap_text( + text, + self.width, + initial_indent=indent, + subsequent_indent=indent, + preserve_paragraphs=True, + ) + ) + self.write("\n") + + def write_dl( + self, + rows: cabc.Sequence[tuple[str, str]], + col_max: int = 30, + col_spacing: int = 2, + ) -> None: + """Writes a definition list into the buffer. This is how options + and commands are usually formatted. + + :param rows: a list of two item tuples for the terms and values. + :param col_max: the maximum width of the first column. + :param col_spacing: the number of spaces between the first and + second column. + """ + rows = list(rows) + widths = measure_table(rows) + if len(widths) != 2: + raise TypeError("Expected two columns for definition list") + + first_col = min(widths[0], col_max) + col_spacing + + for first, second in iter_rows(rows, len(widths)): + self.write(f"{'':>{self.current_indent}}{first}") + if not second: + self.write("\n") + continue + if term_len(first) <= first_col - col_spacing: + self.write(" " * (first_col - term_len(first))) + else: + self.write("\n") + self.write(" " * (first_col + self.current_indent)) + + text_width = max(self.width - first_col - 2, 10) + wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True) + lines = wrapped_text.splitlines() + + if lines: + self.write(f"{lines[0]}\n") + + for line in lines[1:]: + self.write(f"{'':>{first_col + self.current_indent}}{line}\n") + else: + self.write("\n") + + @contextmanager + def section(self, name: str) -> cabc.Iterator[None]: + """Helpful context manager that writes a paragraph, a heading, + and the indents. + + :param name: the section name that is written as heading. + """ + self.write_paragraph() + self.write_heading(name) + self.indent() + try: + yield + finally: + self.dedent() + + @contextmanager + def indentation(self) -> cabc.Iterator[None]: + """A context manager that increases the indentation.""" + self.indent() + try: + yield + finally: + self.dedent() + + def getvalue(self) -> str: + """Returns the buffer contents.""" + return "".join(self.buffer) + + +def join_options(options: cabc.Sequence[str]) -> tuple[str, bool]: + """Given a list of option strings this joins them in the most appropriate + way and returns them in the form ``(formatted_string, + any_prefix_is_slash)`` where the second item in the tuple is a flag that + indicates if any of the option prefixes was a slash. + """ + rv = [] + any_prefix_is_slash = False + + for opt in options: + prefix = _split_opt(opt)[0] + + if prefix == "/": + any_prefix_is_slash = True + + rv.append((len(prefix), opt)) + + rv.sort(key=lambda x: x[0]) + return ", ".join(x[1] for x in rv), any_prefix_is_slash diff --git a/venv/lib/python3.12/site-packages/click/globals.py b/venv/lib/python3.12/site-packages/click/globals.py new file mode 100644 index 0000000..a2f9172 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/globals.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import typing as t +from threading import local + +if t.TYPE_CHECKING: + from .core import Context + +_local = local() + + +@t.overload +def get_current_context(silent: t.Literal[False] = False) -> Context: ... + + +@t.overload +def get_current_context(silent: bool = ...) -> Context | None: ... + + +def get_current_context(silent: bool = False) -> Context | None: + """Returns the current click context. This can be used as a way to + access the current context object from anywhere. This is a more implicit + alternative to the :func:`pass_context` decorator. This function is + primarily useful for helpers such as :func:`echo` which might be + interested in changing its behavior based on the current context. + + To push the current context, :meth:`Context.scope` can be used. + + .. versionadded:: 5.0 + + :param silent: if set to `True` the return value is `None` if no context + is available. The default behavior is to raise a + :exc:`RuntimeError`. + """ + try: + return t.cast("Context", _local.stack[-1]) + except (AttributeError, IndexError) as e: + if not silent: + raise RuntimeError("There is no active click context.") from e + + return None + + +def push_context(ctx: Context) -> None: + """Pushes a new context to the current stack.""" + _local.__dict__.setdefault("stack", []).append(ctx) + + +def pop_context() -> None: + """Removes the top level from the stack.""" + _local.stack.pop() + + +def resolve_color_default(color: bool | None = None) -> bool | None: + """Internal helper to get the default value of the color flag. If a + value is passed it's returned unchanged, otherwise it's looked up from + the current context. + """ + if color is not None: + return color + + ctx = get_current_context(silent=True) + + if ctx is not None: + return ctx.color + + return None diff --git a/venv/lib/python3.12/site-packages/click/parser.py b/venv/lib/python3.12/site-packages/click/parser.py new file mode 100644 index 0000000..1ea1f71 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/parser.py @@ -0,0 +1,532 @@ +""" +This module started out as largely a copy paste from the stdlib's +optparse module with the features removed that we do not need from +optparse because we implement them in Click on a higher level (for +instance type handling, help formatting and a lot more). + +The plan is to remove more and more from here over time. + +The reason this is a different module and not optparse from the stdlib +is that there are differences in 2.x and 3.x about the error messages +generated and optparse in the stdlib uses gettext for no good reason +and might cause us issues. + +Click uses parts of optparse written by Gregory P. Ward and maintained +by the Python Software Foundation. This is limited to code in parser.py. + +Copyright 2001-2006 Gregory P. Ward. All rights reserved. +Copyright 2002-2006 Python Software Foundation. All rights reserved. +""" + +# This code uses parts of optparse written by Gregory P. Ward and +# maintained by the Python Software Foundation. +# Copyright 2001-2006 Gregory P. Ward +# Copyright 2002-2006 Python Software Foundation +from __future__ import annotations + +import collections.abc as cabc +import typing as t +from collections import deque +from gettext import gettext as _ +from gettext import ngettext + +from ._utils import FLAG_NEEDS_VALUE +from ._utils import UNSET +from .exceptions import BadArgumentUsage +from .exceptions import BadOptionUsage +from .exceptions import NoSuchOption +from .exceptions import UsageError + +if t.TYPE_CHECKING: + from ._utils import T_FLAG_NEEDS_VALUE + from ._utils import T_UNSET + from .core import Argument as CoreArgument + from .core import Context + from .core import Option as CoreOption + from .core import Parameter as CoreParameter + +V = t.TypeVar("V") + + +def _unpack_args( + args: cabc.Sequence[str], nargs_spec: cabc.Sequence[int] +) -> tuple[cabc.Sequence[str | cabc.Sequence[str | None] | None], list[str]]: + """Given an iterable of arguments and an iterable of nargs specifications, + it returns a tuple with all the unpacked arguments at the first index + and all remaining arguments as the second. + + The nargs specification is the number of arguments that should be consumed + or `-1` to indicate that this position should eat up all the remainders. + + Missing items are filled with ``UNSET``. + """ + args = deque(args) + nargs_spec = deque(nargs_spec) + rv: list[str | tuple[str | T_UNSET, ...] | T_UNSET] = [] + spos: int | None = None + + def _fetch(c: deque[V]) -> V | T_UNSET: + try: + if spos is None: + return c.popleft() + else: + return c.pop() + except IndexError: + return UNSET + + while nargs_spec: + nargs = _fetch(nargs_spec) + + if nargs is None: + continue + + if nargs == 1: + rv.append(_fetch(args)) # type: ignore[arg-type] + elif nargs > 1: + x = [_fetch(args) for _ in range(nargs)] + + # If we're reversed, we're pulling in the arguments in reverse, + # so we need to turn them around. + if spos is not None: + x.reverse() + + rv.append(tuple(x)) + elif nargs < 0: + if spos is not None: + raise TypeError("Cannot have two nargs < 0") + + spos = len(rv) + rv.append(UNSET) + + # spos is the position of the wildcard (star). If it's not `None`, + # we fill it with the remainder. + if spos is not None: + rv[spos] = tuple(args) + args = [] + rv[spos + 1 :] = reversed(rv[spos + 1 :]) + + return tuple(rv), list(args) + + +def _split_opt(opt: str) -> tuple[str, str]: + first = opt[:1] + if first.isalnum(): + return "", opt + if opt[1:2] == first: + return opt[:2], opt[2:] + return first, opt[1:] + + +def _normalize_opt(opt: str, ctx: Context | None) -> str: + if ctx is None or ctx.token_normalize_func is None: + return opt + prefix, opt = _split_opt(opt) + return f"{prefix}{ctx.token_normalize_func(opt)}" + + +class _Option: + def __init__( + self, + obj: CoreOption, + opts: cabc.Sequence[str], + dest: str | None, + action: str | None = None, + nargs: int = 1, + const: t.Any | None = None, + ): + self._short_opts = [] + self._long_opts = [] + self.prefixes: set[str] = set() + + for opt in opts: + prefix, value = _split_opt(opt) + if not prefix: + raise ValueError(f"Invalid start character for option ({opt})") + self.prefixes.add(prefix[0]) + if len(prefix) == 1 and len(value) == 1: + self._short_opts.append(opt) + else: + self._long_opts.append(opt) + self.prefixes.add(prefix) + + if action is None: + action = "store" + + self.dest = dest + self.action = action + self.nargs = nargs + self.const = const + self.obj = obj + + @property + def takes_value(self) -> bool: + return self.action in ("store", "append") + + def process(self, value: t.Any, state: _ParsingState) -> None: + if self.action == "store": + state.opts[self.dest] = value # type: ignore + elif self.action == "store_const": + state.opts[self.dest] = self.const # type: ignore + elif self.action == "append": + state.opts.setdefault(self.dest, []).append(value) # type: ignore + elif self.action == "append_const": + state.opts.setdefault(self.dest, []).append(self.const) # type: ignore + elif self.action == "count": + state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore + else: + raise ValueError(f"unknown action '{self.action}'") + state.order.append(self.obj) + + +class _Argument: + def __init__(self, obj: CoreArgument, dest: str | None, nargs: int = 1): + self.dest = dest + self.nargs = nargs + self.obj = obj + + def process( + self, + value: str | cabc.Sequence[str | None] | None | T_UNSET, + state: _ParsingState, + ) -> None: + if self.nargs > 1: + assert isinstance(value, cabc.Sequence) + holes = sum(1 for x in value if x is UNSET) + if holes == len(value): + value = UNSET + elif holes != 0: + raise BadArgumentUsage( + _("Argument {name!r} takes {nargs} values.").format( + name=self.dest, nargs=self.nargs + ) + ) + + # We failed to collect any argument value so we consider the argument as unset. + if value == (): + value = UNSET + + state.opts[self.dest] = value # type: ignore + state.order.append(self.obj) + + +class _ParsingState: + def __init__(self, rargs: list[str]) -> None: + self.opts: dict[str, t.Any] = {} + self.largs: list[str] = [] + self.rargs = rargs + self.order: list[CoreParameter] = [] + + +class _OptionParser: + """The option parser is an internal class that is ultimately used to + parse options and arguments. It's modelled after optparse and brings + a similar but vastly simplified API. It should generally not be used + directly as the high level Click classes wrap it for you. + + It's not nearly as extensible as optparse or argparse as it does not + implement features that are implemented on a higher level (such as + types or defaults). + + :param ctx: optionally the :class:`~click.Context` where this parser + should go with. + + .. deprecated:: 8.2 + Will be removed in Click 9.0. + """ + + def __init__(self, ctx: Context | None = None) -> None: + #: The :class:`~click.Context` for this parser. This might be + #: `None` for some advanced use cases. + self.ctx = ctx + #: This controls how the parser deals with interspersed arguments. + #: If this is set to `False`, the parser will stop on the first + #: non-option. Click uses this to implement nested subcommands + #: safely. + self.allow_interspersed_args: bool = True + #: This tells the parser how to deal with unknown options. By + #: default it will error out (which is sensible), but there is a + #: second mode where it will ignore it and continue processing + #: after shifting all the unknown options into the resulting args. + self.ignore_unknown_options: bool = False + + if ctx is not None: + self.allow_interspersed_args = ctx.allow_interspersed_args + self.ignore_unknown_options = ctx.ignore_unknown_options + + self._short_opt: dict[str, _Option] = {} + self._long_opt: dict[str, _Option] = {} + self._opt_prefixes = {"-", "--"} + self._args: list[_Argument] = [] + + def add_option( + self, + obj: CoreOption, + opts: cabc.Sequence[str], + dest: str | None, + action: str | None = None, + nargs: int = 1, + const: t.Any | None = None, + ) -> None: + """Adds a new option named `dest` to the parser. The destination + is not inferred (unlike with optparse) and needs to be explicitly + provided. Action can be any of ``store``, ``store_const``, + ``append``, ``append_const`` or ``count``. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + opts = [_normalize_opt(opt, self.ctx) for opt in opts] + option = _Option(obj, opts, dest, action=action, nargs=nargs, const=const) + self._opt_prefixes.update(option.prefixes) + for opt in option._short_opts: + self._short_opt[opt] = option + for opt in option._long_opts: + self._long_opt[opt] = option + + def add_argument(self, obj: CoreArgument, dest: str | None, nargs: int = 1) -> None: + """Adds a positional argument named `dest` to the parser. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + self._args.append(_Argument(obj, dest=dest, nargs=nargs)) + + def parse_args( + self, args: list[str] + ) -> tuple[dict[str, t.Any], list[str], list[CoreParameter]]: + """Parses positional arguments and returns ``(values, args, order)`` + for the parsed options and arguments as well as the leftover + arguments if there are any. The order is a list of objects as they + appear on the command line. If arguments appear multiple times they + will be memorized multiple times as well. + """ + state = _ParsingState(args) + try: + self._process_args_for_options(state) + self._process_args_for_args(state) + except UsageError: + if self.ctx is None or not self.ctx.resilient_parsing: + raise + return state.opts, state.largs, state.order + + def _process_args_for_args(self, state: _ParsingState) -> None: + pargs, args = _unpack_args( + state.largs + state.rargs, [x.nargs for x in self._args] + ) + + for idx, arg in enumerate(self._args): + arg.process(pargs[idx], state) + + state.largs = args + state.rargs = [] + + def _process_args_for_options(self, state: _ParsingState) -> None: + while state.rargs: + arg = state.rargs.pop(0) + arglen = len(arg) + # Double dashes always handled explicitly regardless of what + # prefixes are valid. + if arg == "--": + return + elif arg[:1] in self._opt_prefixes and arglen > 1: + self._process_opts(arg, state) + elif self.allow_interspersed_args: + state.largs.append(arg) + else: + state.rargs.insert(0, arg) + return + + # Say this is the original argument list: + # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] + # ^ + # (we are about to process arg(i)). + # + # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of + # [arg0, ..., arg(i-1)] (any options and their arguments will have + # been removed from largs). + # + # The while loop will usually consume 1 or more arguments per pass. + # If it consumes 1 (eg. arg is an option that takes no arguments), + # then after _process_arg() is done the situation is: + # + # largs = subset of [arg0, ..., arg(i)] + # rargs = [arg(i+1), ..., arg(N-1)] + # + # If allow_interspersed_args is false, largs will always be + # *empty* -- still a subset of [arg0, ..., arg(i-1)], but + # not a very interesting subset! + + def _match_long_opt( + self, opt: str, explicit_value: str | None, state: _ParsingState + ) -> None: + if opt not in self._long_opt: + from difflib import get_close_matches + + possibilities = get_close_matches(opt, self._long_opt) + raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx) + + option = self._long_opt[opt] + if option.takes_value: + # At this point it's safe to modify rargs by injecting the + # explicit value, because no exception is raised in this + # branch. This means that the inserted value will be fully + # consumed. + if explicit_value is not None: + state.rargs.insert(0, explicit_value) + + value = self._get_value_from_state(opt, option, state) + + elif explicit_value is not None: + raise BadOptionUsage( + opt, _("Option {name!r} does not take a value.").format(name=opt) + ) + + else: + value = UNSET + + option.process(value, state) + + def _match_short_opt(self, arg: str, state: _ParsingState) -> None: + stop = False + i = 1 + prefix = arg[0] + unknown_options = [] + + for ch in arg[1:]: + opt = _normalize_opt(f"{prefix}{ch}", self.ctx) + option = self._short_opt.get(opt) + i += 1 + + if not option: + if self.ignore_unknown_options: + unknown_options.append(ch) + continue + raise NoSuchOption(opt, ctx=self.ctx) + if option.takes_value: + # Any characters left in arg? Pretend they're the + # next arg, and stop consuming characters of arg. + if i < len(arg): + state.rargs.insert(0, arg[i:]) + stop = True + + value = self._get_value_from_state(opt, option, state) + + else: + value = UNSET + + option.process(value, state) + + if stop: + break + + # If we got any unknown options we recombine the string of the + # remaining options and re-attach the prefix, then report that + # to the state as new larg. This way there is basic combinatorics + # that can be achieved while still ignoring unknown arguments. + if self.ignore_unknown_options and unknown_options: + state.largs.append(f"{prefix}{''.join(unknown_options)}") + + def _get_value_from_state( + self, option_name: str, option: _Option, state: _ParsingState + ) -> str | cabc.Sequence[str] | T_FLAG_NEEDS_VALUE: + nargs = option.nargs + + value: str | cabc.Sequence[str] | T_FLAG_NEEDS_VALUE + + if len(state.rargs) < nargs: + if option.obj._flag_needs_value: + # Option allows omitting the value. + value = FLAG_NEEDS_VALUE + else: + raise BadOptionUsage( + option_name, + ngettext( + "Option {name!r} requires an argument.", + "Option {name!r} requires {nargs} arguments.", + nargs, + ).format(name=option_name, nargs=nargs), + ) + elif nargs == 1: + next_rarg = state.rargs[0] + + if ( + option.obj._flag_needs_value + and isinstance(next_rarg, str) + and next_rarg[:1] in self._opt_prefixes + and len(next_rarg) > 1 + ): + # The next arg looks like the start of an option, don't + # use it as the value if omitting the value is allowed. + value = FLAG_NEEDS_VALUE + else: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + return value + + def _process_opts(self, arg: str, state: _ParsingState) -> None: + explicit_value = None + # Long option handling happens in two parts. The first part is + # supporting explicitly attached values. In any case, we will try + # to long match the option first. + if "=" in arg: + long_opt, explicit_value = arg.split("=", 1) + else: + long_opt = arg + norm_long_opt = _normalize_opt(long_opt, self.ctx) + + # At this point we will match the (assumed) long option through + # the long option matching code. Note that this allows options + # like "-foo" to be matched as long options. + try: + self._match_long_opt(norm_long_opt, explicit_value, state) + except NoSuchOption: + # At this point the long option matching failed, and we need + # to try with short options. However there is a special rule + # which says, that if we have a two character options prefix + # (applies to "--foo" for instance), we do not dispatch to the + # short option code and will instead raise the no option + # error. + if arg[:2] not in self._opt_prefixes: + self._match_short_opt(arg, state) + return + + if not self.ignore_unknown_options: + raise + + state.largs.append(arg) + + +def __getattr__(name: str) -> object: + import warnings + + if name in { + "OptionParser", + "Argument", + "Option", + "split_opt", + "normalize_opt", + "ParsingState", + }: + warnings.warn( + f"'parser.{name}' is deprecated and will be removed in Click 9.0." + " The old parser is available in 'optparse'.", + DeprecationWarning, + stacklevel=2, + ) + return globals()[f"_{name}"] + + if name == "split_arg_string": + from .shell_completion import split_arg_string + + warnings.warn( + "Importing 'parser.split_arg_string' is deprecated, it will only be" + " available in 'shell_completion' in Click 9.0.", + DeprecationWarning, + stacklevel=2, + ) + return split_arg_string + + raise AttributeError(name) diff --git a/venv/lib/python3.12/site-packages/click/py.typed b/venv/lib/python3.12/site-packages/click/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/click/shell_completion.py b/venv/lib/python3.12/site-packages/click/shell_completion.py new file mode 100644 index 0000000..8f1564c --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/shell_completion.py @@ -0,0 +1,667 @@ +from __future__ import annotations + +import collections.abc as cabc +import os +import re +import typing as t +from gettext import gettext as _ + +from .core import Argument +from .core import Command +from .core import Context +from .core import Group +from .core import Option +from .core import Parameter +from .core import ParameterSource +from .utils import echo + + +def shell_complete( + cli: Command, + ctx_args: cabc.MutableMapping[str, t.Any], + prog_name: str, + complete_var: str, + instruction: str, +) -> int: + """Perform shell completion for the given CLI program. + + :param cli: Command being called. + :param ctx_args: Extra arguments to pass to + ``cli.make_context``. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + :param instruction: Value of ``complete_var`` with the completion + instruction and shell, in the form ``instruction_shell``. + :return: Status code to exit with. + """ + shell, _, instruction = instruction.partition("_") + comp_cls = get_completion_class(shell) + + if comp_cls is None: + return 1 + + comp = comp_cls(cli, ctx_args, prog_name, complete_var) + + if instruction == "source": + echo(comp.source()) + return 0 + + if instruction == "complete": + echo(comp.complete()) + return 0 + + return 1 + + +class CompletionItem: + """Represents a completion value and metadata about the value. The + default metadata is ``type`` to indicate special shell handling, + and ``help`` if a shell supports showing a help string next to the + value. + + Arbitrary parameters can be passed when creating the object, and + accessed using ``item.attr``. If an attribute wasn't passed, + accessing it returns ``None``. + + :param value: The completion suggestion. + :param type: Tells the shell script to provide special completion + support for the type. Click uses ``"dir"`` and ``"file"``. + :param help: String shown next to the value if supported. + :param kwargs: Arbitrary metadata. The built-in implementations + don't use this, but custom type completions paired with custom + shell support could use it. + """ + + __slots__ = ("value", "type", "help", "_info") + + def __init__( + self, + value: t.Any, + type: str = "plain", + help: str | None = None, + **kwargs: t.Any, + ) -> None: + self.value: t.Any = value + self.type: str = type + self.help: str | None = help + self._info = kwargs + + def __getattr__(self, name: str) -> t.Any: + return self._info.get(name) + + +# Only Bash >= 4.4 has the nosort option. +_SOURCE_BASH = """\ +%(complete_func)s() { + local IFS=$'\\n' + local response + + response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \ +%(complete_var)s=bash_complete $1) + + for completion in $response; do + IFS=',' read type value <<< "$completion" + + if [[ $type == 'dir' ]]; then + COMPREPLY=() + compopt -o dirnames + elif [[ $type == 'file' ]]; then + COMPREPLY=() + compopt -o default + elif [[ $type == 'plain' ]]; then + COMPREPLY+=($value) + fi + done + + return 0 +} + +%(complete_func)s_setup() { + complete -o nosort -F %(complete_func)s %(prog_name)s +} + +%(complete_func)s_setup; +""" + +# See ZshComplete.format_completion below, and issue #2703, before +# changing this script. +# +# (TL;DR: _describe is picky about the format, but this Zsh script snippet +# is already widely deployed. So freeze this script, and use clever-ish +# handling of colons in ZshComplet.format_completion.) +_SOURCE_ZSH = """\ +#compdef %(prog_name)s + +%(complete_func)s() { + local -a completions + local -a completions_with_descriptions + local -a response + (( ! $+commands[%(prog_name)s] )) && return 1 + + response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \ +%(complete_var)s=zsh_complete %(prog_name)s)}") + + for type key descr in ${response}; do + if [[ "$type" == "plain" ]]; then + if [[ "$descr" == "_" ]]; then + completions+=("$key") + else + completions_with_descriptions+=("$key":"$descr") + fi + elif [[ "$type" == "dir" ]]; then + _path_files -/ + elif [[ "$type" == "file" ]]; then + _path_files -f + fi + done + + if [ -n "$completions_with_descriptions" ]; then + _describe -V unsorted completions_with_descriptions -U + fi + + if [ -n "$completions" ]; then + compadd -U -V unsorted -a completions + fi +} + +if [[ $zsh_eval_context[-1] == loadautofunc ]]; then + # autoload from fpath, call function directly + %(complete_func)s "$@" +else + # eval/source/. command, register function for later + compdef %(complete_func)s %(prog_name)s +fi +""" + +_SOURCE_FISH = """\ +function %(complete_func)s; + set -l response (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \ +COMP_CWORD=(commandline -t) %(prog_name)s); + + for completion in $response; + set -l metadata (string split "," $completion); + + if test $metadata[1] = "dir"; + __fish_complete_directories $metadata[2]; + else if test $metadata[1] = "file"; + __fish_complete_path $metadata[2]; + else if test $metadata[1] = "plain"; + echo $metadata[2]; + end; + end; +end; + +complete --no-files --command %(prog_name)s --arguments \ +"(%(complete_func)s)"; +""" + + +class ShellComplete: + """Base class for providing shell completion support. A subclass for + a given shell will override attributes and methods to implement the + completion instructions (``source`` and ``complete``). + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + + .. versionadded:: 8.0 + """ + + name: t.ClassVar[str] + """Name to register the shell as with :func:`add_completion_class`. + This is used in completion instructions (``{name}_source`` and + ``{name}_complete``). + """ + + source_template: t.ClassVar[str] + """Completion script template formatted by :meth:`source`. This must + be provided by subclasses. + """ + + def __init__( + self, + cli: Command, + ctx_args: cabc.MutableMapping[str, t.Any], + prog_name: str, + complete_var: str, + ) -> None: + self.cli = cli + self.ctx_args = ctx_args + self.prog_name = prog_name + self.complete_var = complete_var + + @property + def func_name(self) -> str: + """The name of the shell function defined by the completion + script. + """ + safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), flags=re.ASCII) + return f"_{safe_name}_completion" + + def source_vars(self) -> dict[str, t.Any]: + """Vars for formatting :attr:`source_template`. + + By default this provides ``complete_func``, ``complete_var``, + and ``prog_name``. + """ + return { + "complete_func": self.func_name, + "complete_var": self.complete_var, + "prog_name": self.prog_name, + } + + def source(self) -> str: + """Produce the shell script that defines the completion + function. By default this ``%``-style formats + :attr:`source_template` with the dict returned by + :meth:`source_vars`. + """ + return self.source_template % self.source_vars() + + def get_completion_args(self) -> tuple[list[str], str]: + """Use the env vars defined by the shell script to return a + tuple of ``args, incomplete``. This must be implemented by + subclasses. + """ + raise NotImplementedError + + def get_completions(self, args: list[str], incomplete: str) -> list[CompletionItem]: + """Determine the context and last complete command or parameter + from the complete args. Call that object's ``shell_complete`` + method to get the completions for the incomplete value. + + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args) + obj, incomplete = _resolve_incomplete(ctx, args, incomplete) + return obj.shell_complete(ctx, incomplete) + + def format_completion(self, item: CompletionItem) -> str: + """Format a completion item into the form recognized by the + shell script. This must be implemented by subclasses. + + :param item: Completion item to format. + """ + raise NotImplementedError + + def complete(self) -> str: + """Produce the completion data to send back to the shell. + + By default this calls :meth:`get_completion_args`, gets the + completions, then calls :meth:`format_completion` for each + completion. + """ + args, incomplete = self.get_completion_args() + completions = self.get_completions(args, incomplete) + out = [self.format_completion(item) for item in completions] + return "\n".join(out) + + +class BashComplete(ShellComplete): + """Shell completion for Bash.""" + + name = "bash" + source_template = _SOURCE_BASH + + @staticmethod + def _check_version() -> None: + import shutil + import subprocess + + bash_exe = shutil.which("bash") + + if bash_exe is None: + match = None + else: + output = subprocess.run( + [bash_exe, "--norc", "-c", 'echo "${BASH_VERSION}"'], + stdout=subprocess.PIPE, + ) + match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) + + if match is not None: + major, minor = match.groups() + + if major < "4" or major == "4" and minor < "4": + echo( + _( + "Shell completion is not supported for Bash" + " versions older than 4.4." + ), + err=True, + ) + else: + echo( + _("Couldn't detect Bash version, shell completion is not supported."), + err=True, + ) + + def source(self) -> str: + self._check_version() + return super().source() + + def get_completion_args(self) -> tuple[list[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + return f"{item.type},{item.value}" + + +class ZshComplete(ShellComplete): + """Shell completion for Zsh.""" + + name = "zsh" + source_template = _SOURCE_ZSH + + def get_completion_args(self) -> tuple[list[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + help_ = item.help or "_" + # The zsh completion script uses `_describe` on items with help + # texts (which splits the item help from the item value at the + # first unescaped colon) and `compadd` on items without help + # text (which uses the item value as-is and does not support + # colon escaping). So escape colons in the item value if and + # only if the item help is not the sentinel "_" value, as used + # by the completion script. + # + # (The zsh completion script is potentially widely deployed, and + # thus harder to fix than this method.) + # + # See issue #1812 and issue #2703 for further context. + value = item.value.replace(":", r"\:") if help_ != "_" else item.value + return f"{item.type}\n{value}\n{help_}" + + +class FishComplete(ShellComplete): + """Shell completion for Fish.""" + + name = "fish" + source_template = _SOURCE_FISH + + def get_completion_args(self) -> tuple[list[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + incomplete = os.environ["COMP_CWORD"] + if incomplete: + incomplete = split_arg_string(incomplete)[0] + args = cwords[1:] + + # Fish stores the partial word in both COMP_WORDS and + # COMP_CWORD, remove it from complete args. + if incomplete and args and args[-1] == incomplete: + args.pop() + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + if item.help: + return f"{item.type},{item.value}\t{item.help}" + + return f"{item.type},{item.value}" + + +ShellCompleteType = t.TypeVar("ShellCompleteType", bound="type[ShellComplete]") + + +_available_shells: dict[str, type[ShellComplete]] = { + "bash": BashComplete, + "fish": FishComplete, + "zsh": ZshComplete, +} + + +def add_completion_class( + cls: ShellCompleteType, name: str | None = None +) -> ShellCompleteType: + """Register a :class:`ShellComplete` subclass under the given name. + The name will be provided by the completion instruction environment + variable during completion. + + :param cls: The completion class that will handle completion for the + shell. + :param name: Name to register the class under. Defaults to the + class's ``name`` attribute. + """ + if name is None: + name = cls.name + + _available_shells[name] = cls + + return cls + + +def get_completion_class(shell: str) -> type[ShellComplete] | None: + """Look up a registered :class:`ShellComplete` subclass by the name + provided by the completion instruction environment variable. If the + name isn't registered, returns ``None``. + + :param shell: Name the class is registered under. + """ + return _available_shells.get(shell) + + +def split_arg_string(string: str) -> list[str]: + """Split an argument string as with :func:`shlex.split`, but don't + fail if the string is incomplete. Ignores a missing closing quote or + incomplete escape sequence and uses the partial token as-is. + + .. code-block:: python + + split_arg_string("example 'my file") + ["example", "my file"] + + split_arg_string("example my\\") + ["example", "my"] + + :param string: String to split. + + .. versionchanged:: 8.2 + Moved to ``shell_completion`` from ``parser``. + """ + import shlex + + lex = shlex.shlex(string, posix=True) + lex.whitespace_split = True + lex.commenters = "" + out = [] + + try: + for token in lex: + out.append(token) + except ValueError: + # Raised when end-of-string is reached in an invalid state. Use + # the partial token as-is. The quote or escape character is in + # lex.state, not lex.token. + out.append(lex.token) + + return out + + +def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool: + """Determine if the given parameter is an argument that can still + accept values. + + :param ctx: Invocation context for the command represented by the + parsed complete args. + :param param: Argument object being checked. + """ + if not isinstance(param, Argument): + return False + + assert param.name is not None + # Will be None if expose_value is False. + value = ctx.params.get(param.name) + return ( + param.nargs == -1 + or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE + or ( + param.nargs > 1 + and isinstance(value, (tuple, list)) + and len(value) < param.nargs + ) + ) + + +def _start_of_option(ctx: Context, value: str) -> bool: + """Check if the value looks like the start of an option.""" + if not value: + return False + + c = value[0] + return c in ctx._opt_prefixes + + +def _is_incomplete_option(ctx: Context, args: list[str], param: Parameter) -> bool: + """Determine if the given parameter is an option that needs a value. + + :param args: List of complete args before the incomplete value. + :param param: Option object being checked. + """ + if not isinstance(param, Option): + return False + + if param.is_flag or param.count: + return False + + last_option = None + + for index, arg in enumerate(reversed(args)): + if index + 1 > param.nargs: + break + + if _start_of_option(ctx, arg): + last_option = arg + break + + return last_option is not None and last_option in param.opts + + +def _resolve_context( + cli: Command, + ctx_args: cabc.MutableMapping[str, t.Any], + prog_name: str, + args: list[str], +) -> Context: + """Produce the context hierarchy starting with the command and + traversing the complete arguments. This only follows the commands, + it doesn't trigger input prompts or callbacks. + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param args: List of complete args before the incomplete value. + """ + ctx_args["resilient_parsing"] = True + with cli.make_context(prog_name, args.copy(), **ctx_args) as ctx: + args = ctx._protected_args + ctx.args + + while args: + command = ctx.command + + if isinstance(command, Group): + if not command.chain: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + with cmd.make_context( + name, args, parent=ctx, resilient_parsing=True + ) as sub_ctx: + ctx = sub_ctx + args = ctx._protected_args + ctx.args + else: + sub_ctx = ctx + + while args: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + with cmd.make_context( + name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + resilient_parsing=True, + ) as sub_sub_ctx: + sub_ctx = sub_sub_ctx + args = sub_ctx.args + + ctx = sub_ctx + args = [*sub_ctx._protected_args, *sub_ctx.args] + else: + break + + return ctx + + +def _resolve_incomplete( + ctx: Context, args: list[str], incomplete: str +) -> tuple[Command | Parameter, str]: + """Find the Click object that will handle the completion of the + incomplete value. Return the object and the incomplete value. + + :param ctx: Invocation context for the command represented by + the parsed complete args. + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + # Different shells treat an "=" between a long option name and + # value differently. Might keep the value joined, return the "=" + # as a separate item, or return the split name and value. Always + # split and discard the "=" to make completion easier. + if incomplete == "=": + incomplete = "" + elif "=" in incomplete and _start_of_option(ctx, incomplete): + name, _, incomplete = incomplete.partition("=") + args.append(name) + + # The "--" marker tells Click to stop treating values as options + # even if they start with the option character. If it hasn't been + # given and the incomplete arg looks like an option, the current + # command will provide option name completions. + if "--" not in args and _start_of_option(ctx, incomplete): + return ctx.command, incomplete + + params = ctx.command.get_params(ctx) + + # If the last complete arg is an option name with an incomplete + # value, the option will provide value completions. + for param in params: + if _is_incomplete_option(ctx, args, param): + return param, incomplete + + # It's not an option name or value. The first argument without a + # parsed value will provide value completions. + for param in params: + if _is_incomplete_argument(ctx, param): + return param, incomplete + + # There were no unparsed arguments, the command may be a group that + # will provide command name completions. + return ctx.command, incomplete diff --git a/venv/lib/python3.12/site-packages/click/termui.py b/venv/lib/python3.12/site-packages/click/termui.py new file mode 100644 index 0000000..dcbb222 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/termui.py @@ -0,0 +1,877 @@ +from __future__ import annotations + +import collections.abc as cabc +import inspect +import io +import itertools +import sys +import typing as t +from contextlib import AbstractContextManager +from gettext import gettext as _ + +from ._compat import isatty +from ._compat import strip_ansi +from .exceptions import Abort +from .exceptions import UsageError +from .globals import resolve_color_default +from .types import Choice +from .types import convert_type +from .types import ParamType +from .utils import echo +from .utils import LazyFile + +if t.TYPE_CHECKING: + from ._termui_impl import ProgressBar + +V = t.TypeVar("V") + +# The prompt functions to use. The doc tools currently override these +# functions to customize how they work. +visible_prompt_func: t.Callable[[str], str] = input + +_ansi_colors = { + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "white": 37, + "reset": 39, + "bright_black": 90, + "bright_red": 91, + "bright_green": 92, + "bright_yellow": 93, + "bright_blue": 94, + "bright_magenta": 95, + "bright_cyan": 96, + "bright_white": 97, +} +_ansi_reset_all = "\033[0m" + + +def hidden_prompt_func(prompt: str) -> str: + import getpass + + return getpass.getpass(prompt) + + +def _build_prompt( + text: str, + suffix: str, + show_default: bool = False, + default: t.Any | None = None, + show_choices: bool = True, + type: ParamType | None = None, +) -> str: + prompt = text + if type is not None and show_choices and isinstance(type, Choice): + prompt += f" ({', '.join(map(str, type.choices))})" + if default is not None and show_default: + prompt = f"{prompt} [{_format_default(default)}]" + return f"{prompt}{suffix}" + + +def _format_default(default: t.Any) -> t.Any: + if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"): + return default.name + + return default + + +def prompt( + text: str, + default: t.Any | None = None, + hide_input: bool = False, + confirmation_prompt: bool | str = False, + type: ParamType | t.Any | None = None, + value_proc: t.Callable[[str], t.Any] | None = None, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, + show_choices: bool = True, +) -> t.Any: + """Prompts a user for input. This is a convenience function that can + be used to prompt a user for input later. + + If the user aborts the input by sending an interrupt signal, this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the text to show for the prompt. + :param default: the default value to use if no input happens. If this + is not given it will prompt until it's aborted. + :param hide_input: if this is set to true then the input value will + be hidden. + :param confirmation_prompt: Prompt a second time to confirm the + value. Can be set to a string instead of ``True`` to customize + the message. + :param type: the type to use to check the value against. + :param value_proc: if this parameter is provided it's a function that + is invoked instead of the type conversion to + convert a value. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + :param show_choices: Show or hide choices if the passed type is a Choice. + For example if type is a Choice of either day or week, + show_choices is true and text is "Group by" then the + prompt will be "Group by (day, week): ". + + .. versionadded:: 8.0 + ``confirmation_prompt`` can be a custom string. + + .. versionadded:: 7.0 + Added the ``show_choices`` parameter. + + .. versionadded:: 6.0 + Added unicode support for cmd.exe on Windows. + + .. versionadded:: 4.0 + Added the `err` parameter. + + """ + + def prompt_func(text: str) -> str: + f = hidden_prompt_func if hide_input else visible_prompt_func + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(text.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + return f(" ") + except (KeyboardInterrupt, EOFError): + # getpass doesn't print a newline if the user aborts input with ^C. + # Allegedly this behavior is inherited from getpass(3). + # A doc bug has been filed at https://bugs.python.org/issue24711 + if hide_input: + echo(None, err=err) + raise Abort() from None + + if value_proc is None: + value_proc = convert_type(type, default) + + prompt = _build_prompt( + text, prompt_suffix, show_default, default, show_choices, type + ) + + if confirmation_prompt: + if confirmation_prompt is True: + confirmation_prompt = _("Repeat for confirmation") + + confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix) + + while True: + while True: + value = prompt_func(prompt) + if value: + break + elif default is not None: + value = default + break + try: + result = value_proc(value) + except UsageError as e: + if hide_input: + echo(_("Error: The value you entered was invalid."), err=err) + else: + echo(_("Error: {e.message}").format(e=e), err=err) + continue + if not confirmation_prompt: + return result + while True: + value2 = prompt_func(confirmation_prompt) + is_empty = not value and not value2 + if value2 or is_empty: + break + if value == value2: + return result + echo(_("Error: The two entered values do not match."), err=err) + + +def confirm( + text: str, + default: bool | None = False, + abort: bool = False, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, +) -> bool: + """Prompts for confirmation (yes/no question). + + If the user aborts the input by sending a interrupt signal this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the question to ask. + :param default: The default value to use when no input is given. If + ``None``, repeat until input is given. + :param abort: if this is set to `True` a negative answer aborts the + exception by raising :exc:`Abort`. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + + .. versionchanged:: 8.0 + Repeat until input is given if ``default`` is ``None``. + + .. versionadded:: 4.0 + Added the ``err`` parameter. + """ + prompt = _build_prompt( + text, + prompt_suffix, + show_default, + "y/n" if default is None else ("Y/n" if default else "y/N"), + ) + + while True: + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(prompt.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + value = visible_prompt_func(" ").lower().strip() + except (KeyboardInterrupt, EOFError): + raise Abort() from None + if value in ("y", "yes"): + rv = True + elif value in ("n", "no"): + rv = False + elif default is not None and value == "": + rv = default + else: + echo(_("Error: invalid input"), err=err) + continue + break + if abort and not rv: + raise Abort() + return rv + + +def echo_via_pager( + text_or_generator: cabc.Iterable[str] | t.Callable[[], cabc.Iterable[str]] | str, + color: bool | None = None, +) -> None: + """This function takes a text and shows it via an environment specific + pager on stdout. + + .. versionchanged:: 3.0 + Added the `color` flag. + + :param text_or_generator: the text to page, or alternatively, a + generator emitting the text to page. + :param color: controls if the pager supports ANSI colors or not. The + default is autodetection. + """ + color = resolve_color_default(color) + + if inspect.isgeneratorfunction(text_or_generator): + i = t.cast("t.Callable[[], cabc.Iterable[str]]", text_or_generator)() + elif isinstance(text_or_generator, str): + i = [text_or_generator] + else: + i = iter(t.cast("cabc.Iterable[str]", text_or_generator)) + + # convert every element of i to a text type if necessary + text_generator = (el if isinstance(el, str) else str(el) for el in i) + + from ._termui_impl import pager + + return pager(itertools.chain(text_generator, "\n"), color) + + +@t.overload +def progressbar( + *, + length: int, + label: str | None = None, + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, +) -> ProgressBar[int]: ... + + +@t.overload +def progressbar( + iterable: cabc.Iterable[V] | None = None, + length: int | None = None, + label: str | None = None, + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + item_show_func: t.Callable[[V | None], str | None] | None = None, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, +) -> ProgressBar[V]: ... + + +def progressbar( + iterable: cabc.Iterable[V] | None = None, + length: int | None = None, + label: str | None = None, + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + item_show_func: t.Callable[[V | None], str | None] | None = None, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, +) -> ProgressBar[V]: + """This function creates an iterable context manager that can be used + to iterate over something while showing a progress bar. It will + either iterate over the `iterable` or `length` items (that are counted + up). While iteration happens, this function will print a rendered + progress bar to the given `file` (defaults to stdout) and will attempt + to calculate remaining time and more. By default, this progress bar + will not be rendered if the file is not a terminal. + + The context manager creates the progress bar. When the context + manager is entered the progress bar is already created. With every + iteration over the progress bar, the iterable passed to the bar is + advanced and the bar is updated. When the context manager exits, + a newline is printed and the progress bar is finalized on screen. + + Note: The progress bar is currently designed for use cases where the + total progress can be expected to take at least several seconds. + Because of this, the ProgressBar class object won't display + progress that is considered too fast, and progress where the time + between steps is less than a second. + + No printing must happen or the progress bar will be unintentionally + destroyed. + + Example usage:: + + with progressbar(items) as bar: + for item in bar: + do_something_with(item) + + Alternatively, if no iterable is specified, one can manually update the + progress bar through the `update()` method instead of directly + iterating over the progress bar. The update method accepts the number + of steps to increment the bar with:: + + with progressbar(length=chunks.total_bytes) as bar: + for chunk in chunks: + process_chunk(chunk) + bar.update(chunks.bytes) + + The ``update()`` method also takes an optional value specifying the + ``current_item`` at the new position. This is useful when used + together with ``item_show_func`` to customize the output for each + manual step:: + + with click.progressbar( + length=total_size, + label='Unzipping archive', + item_show_func=lambda a: a.filename + ) as bar: + for archive in zip_file: + archive.extract() + bar.update(archive.size, archive) + + :param iterable: an iterable to iterate over. If not provided the length + is required. + :param length: the number of items to iterate over. By default the + progressbar will attempt to ask the iterator about its + length, which might or might not work. If an iterable is + also provided this parameter can be used to override the + length. If an iterable is not provided the progress bar + will iterate over a range of that length. + :param label: the label to show next to the progress bar. + :param hidden: hide the progressbar. Defaults to ``False``. When no tty is + detected, it will only print the progressbar label. Setting this to + ``False`` also disables that. + :param show_eta: enables or disables the estimated time display. This is + automatically disabled if the length cannot be + determined. + :param show_percent: enables or disables the percentage display. The + default is `True` if the iterable has a length or + `False` if not. + :param show_pos: enables or disables the absolute position display. The + default is `False`. + :param item_show_func: A function called with the current item which + can return a string to show next to the progress bar. If the + function returns ``None`` nothing is shown. The current item can + be ``None``, such as when entering and exiting the bar. + :param fill_char: the character to use to show the filled part of the + progress bar. + :param empty_char: the character to use to show the non-filled part of + the progress bar. + :param bar_template: the format string to use as template for the bar. + The parameters in it are ``label`` for the label, + ``bar`` for the progress bar and ``info`` for the + info section. + :param info_sep: the separator between multiple info items (eta etc.) + :param width: the width of the progress bar in characters, 0 means full + terminal width + :param file: The file to write to. If this is not a terminal then + only the label is printed. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are included anywhere in the progress bar output + which is not the case by default. + :param update_min_steps: Render only when this many updates have + completed. This allows tuning for very fast iterators. + + .. versionadded:: 8.2 + The ``hidden`` argument. + + .. versionchanged:: 8.0 + Output is shown even if execution time is less than 0.5 seconds. + + .. versionchanged:: 8.0 + ``item_show_func`` shows the current item, not the previous one. + + .. versionchanged:: 8.0 + Labels are echoed if the output is not a TTY. Reverts a change + in 7.0 that removed all output. + + .. versionadded:: 8.0 + The ``update_min_steps`` parameter. + + .. versionadded:: 4.0 + The ``color`` parameter and ``update`` method. + + .. versionadded:: 2.0 + """ + from ._termui_impl import ProgressBar + + color = resolve_color_default(color) + return ProgressBar( + iterable=iterable, + length=length, + hidden=hidden, + show_eta=show_eta, + show_percent=show_percent, + show_pos=show_pos, + item_show_func=item_show_func, + fill_char=fill_char, + empty_char=empty_char, + bar_template=bar_template, + info_sep=info_sep, + file=file, + label=label, + width=width, + color=color, + update_min_steps=update_min_steps, + ) + + +def clear() -> None: + """Clears the terminal screen. This will have the effect of clearing + the whole visible space of the terminal and moving the cursor to the + top left. This does not do anything if not connected to a terminal. + + .. versionadded:: 2.0 + """ + if not isatty(sys.stdout): + return + + # ANSI escape \033[2J clears the screen, \033[1;1H moves the cursor + echo("\033[2J\033[1;1H", nl=False) + + +def _interpret_color(color: int | tuple[int, int, int] | str, offset: int = 0) -> str: + if isinstance(color, int): + return f"{38 + offset};5;{color:d}" + + if isinstance(color, (tuple, list)): + r, g, b = color + return f"{38 + offset};2;{r:d};{g:d};{b:d}" + + return str(_ansi_colors[color] + offset) + + +def style( + text: t.Any, + fg: int | tuple[int, int, int] | str | None = None, + bg: int | tuple[int, int, int] | str | None = None, + bold: bool | None = None, + dim: bool | None = None, + underline: bool | None = None, + overline: bool | None = None, + italic: bool | None = None, + blink: bool | None = None, + reverse: bool | None = None, + strikethrough: bool | None = None, + reset: bool = True, +) -> str: + """Styles a text with ANSI styles and returns the new string. By + default the styling is self contained which means that at the end + of the string a reset code is issued. This can be prevented by + passing ``reset=False``. + + Examples:: + + click.echo(click.style('Hello World!', fg='green')) + click.echo(click.style('ATTENTION!', blink=True)) + click.echo(click.style('Some things', reverse=True, fg='cyan')) + click.echo(click.style('More colors', fg=(255, 12, 128), bg=117)) + + Supported color names: + + * ``black`` (might be a gray) + * ``red`` + * ``green`` + * ``yellow`` (might be an orange) + * ``blue`` + * ``magenta`` + * ``cyan`` + * ``white`` (might be light gray) + * ``bright_black`` + * ``bright_red`` + * ``bright_green`` + * ``bright_yellow`` + * ``bright_blue`` + * ``bright_magenta`` + * ``bright_cyan`` + * ``bright_white`` + * ``reset`` (reset the color code only) + + If the terminal supports it, color may also be specified as: + + - An integer in the interval [0, 255]. The terminal must support + 8-bit/256-color mode. + - An RGB tuple of three integers in [0, 255]. The terminal must + support 24-bit/true-color mode. + + See https://en.wikipedia.org/wiki/ANSI_color and + https://gist.github.com/XVilka/8346728 for more information. + + :param text: the string to style with ansi codes. + :param fg: if provided this will become the foreground color. + :param bg: if provided this will become the background color. + :param bold: if provided this will enable or disable bold mode. + :param dim: if provided this will enable or disable dim mode. This is + badly supported. + :param underline: if provided this will enable or disable underline. + :param overline: if provided this will enable or disable overline. + :param italic: if provided this will enable or disable italic. + :param blink: if provided this will enable or disable blinking. + :param reverse: if provided this will enable or disable inverse + rendering (foreground becomes background and the + other way round). + :param strikethrough: if provided this will enable or disable + striking through text. + :param reset: by default a reset-all code is added at the end of the + string which means that styles do not carry over. This + can be disabled to compose styles. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. + + .. versionchanged:: 8.0 + Added support for 256 and RGB color codes. + + .. versionchanged:: 8.0 + Added the ``strikethrough``, ``italic``, and ``overline`` + parameters. + + .. versionchanged:: 7.0 + Added support for bright colors. + + .. versionadded:: 2.0 + """ + if not isinstance(text, str): + text = str(text) + + bits = [] + + if fg: + try: + bits.append(f"\033[{_interpret_color(fg)}m") + except KeyError: + raise TypeError(f"Unknown color {fg!r}") from None + + if bg: + try: + bits.append(f"\033[{_interpret_color(bg, 10)}m") + except KeyError: + raise TypeError(f"Unknown color {bg!r}") from None + + if bold is not None: + bits.append(f"\033[{1 if bold else 22}m") + if dim is not None: + bits.append(f"\033[{2 if dim else 22}m") + if underline is not None: + bits.append(f"\033[{4 if underline else 24}m") + if overline is not None: + bits.append(f"\033[{53 if overline else 55}m") + if italic is not None: + bits.append(f"\033[{3 if italic else 23}m") + if blink is not None: + bits.append(f"\033[{5 if blink else 25}m") + if reverse is not None: + bits.append(f"\033[{7 if reverse else 27}m") + if strikethrough is not None: + bits.append(f"\033[{9 if strikethrough else 29}m") + bits.append(text) + if reset: + bits.append(_ansi_reset_all) + return "".join(bits) + + +def unstyle(text: str) -> str: + """Removes ANSI styling information from a string. Usually it's not + necessary to use this function as Click's echo function will + automatically remove styling if necessary. + + .. versionadded:: 2.0 + + :param text: the text to remove style information from. + """ + return strip_ansi(text) + + +def secho( + message: t.Any | None = None, + file: t.IO[t.AnyStr] | None = None, + nl: bool = True, + err: bool = False, + color: bool | None = None, + **styles: t.Any, +) -> None: + """This function combines :func:`echo` and :func:`style` into one + call. As such the following two calls are the same:: + + click.secho('Hello World!', fg='green') + click.echo(click.style('Hello World!', fg='green')) + + All keyword arguments are forwarded to the underlying functions + depending on which one they go with. + + Non-string types will be converted to :class:`str`. However, + :class:`bytes` are passed directly to :meth:`echo` without applying + style. If you want to style bytes that represent text, call + :meth:`bytes.decode` first. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. Bytes are + passed through without style applied. + + .. versionadded:: 2.0 + """ + if message is not None and not isinstance(message, (bytes, bytearray)): + message = style(message, **styles) + + return echo(message, file=file, nl=nl, err=err, color=color) + + +@t.overload +def edit( + text: bytes | bytearray, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = False, + extension: str = ".txt", +) -> bytes | None: ... + + +@t.overload +def edit( + text: str, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", +) -> str | None: ... + + +@t.overload +def edit( + text: None = None, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", + filename: str | cabc.Iterable[str] | None = None, +) -> None: ... + + +def edit( + text: str | bytes | bytearray | None = None, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", + filename: str | cabc.Iterable[str] | None = None, +) -> str | bytes | bytearray | None: + r"""Edits the given text in the defined editor. If an editor is given + (should be the full path to the executable but the regular operating + system search path is used for finding the executable) it overrides + the detected editor. Optionally, some environment variables can be + used. If the editor is closed without changes, `None` is returned. In + case a file is edited directly the return value is always `None` and + `require_save` and `extension` are ignored. + + If the editor cannot be opened a :exc:`UsageError` is raised. + + Note for Windows: to simplify cross-platform usage, the newlines are + automatically converted from POSIX to Windows and vice versa. As such, + the message here will have ``\n`` as newline markers. + + :param text: the text to edit. + :param editor: optionally the editor to use. Defaults to automatic + detection. + :param env: environment variables to forward to the editor. + :param require_save: if this is true, then not saving in the editor + will make the return value become `None`. + :param extension: the extension to tell the editor about. This defaults + to `.txt` but changing this might change syntax + highlighting. + :param filename: if provided it will edit this file instead of the + provided text contents. It will not use a temporary + file as an indirection in that case. If the editor supports + editing multiple files at once, a sequence of files may be + passed as well. Invoke `click.file` once per file instead + if multiple files cannot be managed at once or editing the + files serially is desired. + + .. versionchanged:: 8.2.0 + ``filename`` now accepts any ``Iterable[str]`` in addition to a ``str`` + if the ``editor`` supports editing multiple files at once. + + """ + from ._termui_impl import Editor + + ed = Editor(editor=editor, env=env, require_save=require_save, extension=extension) + + if filename is None: + return ed.edit(text) + + if isinstance(filename, str): + filename = (filename,) + + ed.edit_files(filenames=filename) + return None + + +def launch(url: str, wait: bool = False, locate: bool = False) -> int: + """This function launches the given URL (or filename) in the default + viewer application for this file type. If this is an executable, it + might launch the executable in a new session. The return value is + the exit code of the launched application. Usually, ``0`` indicates + success. + + Examples:: + + click.launch('https://click.palletsprojects.com/') + click.launch('/my/downloaded/file', locate=True) + + .. versionadded:: 2.0 + + :param url: URL or filename of the thing to launch. + :param wait: Wait for the program to exit before returning. This + only works if the launched program blocks. In particular, + ``xdg-open`` on Linux does not block. + :param locate: if this is set to `True` then instead of launching the + application associated with the URL it will attempt to + launch a file manager with the file located. This + might have weird effects if the URL does not point to + the filesystem. + """ + from ._termui_impl import open_url + + return open_url(url, wait=wait, locate=locate) + + +# If this is provided, getchar() calls into this instead. This is used +# for unittesting purposes. +_getchar: t.Callable[[bool], str] | None = None + + +def getchar(echo: bool = False) -> str: + """Fetches a single character from the terminal and returns it. This + will always return a unicode character and under certain rare + circumstances this might return more than one character. The + situations which more than one character is returned is when for + whatever reason multiple characters end up in the terminal buffer or + standard input was not actually a terminal. + + Note that this will always read from the terminal, even if something + is piped into the standard input. + + Note for Windows: in rare cases when typing non-ASCII characters, this + function might wait for a second character and then return both at once. + This is because certain Unicode characters look like special-key markers. + + .. versionadded:: 2.0 + + :param echo: if set to `True`, the character read will also show up on + the terminal. The default is to not show it. + """ + global _getchar + + if _getchar is None: + from ._termui_impl import getchar as f + + _getchar = f + + return _getchar(echo) + + +def raw_terminal() -> AbstractContextManager[int]: + from ._termui_impl import raw_terminal as f + + return f() + + +def pause(info: str | None = None, err: bool = False) -> None: + """This command stops execution and waits for the user to press any + key to continue. This is similar to the Windows batch "pause" + command. If the program is not run through a terminal, this command + will instead do nothing. + + .. versionadded:: 2.0 + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param info: The message to print before pausing. Defaults to + ``"Press any key to continue..."``. + :param err: if set to message goes to ``stderr`` instead of + ``stdout``, the same as with echo. + """ + if not isatty(sys.stdin) or not isatty(sys.stdout): + return + + if info is None: + info = _("Press any key to continue...") + + try: + if info: + echo(info, nl=False, err=err) + try: + getchar() + except (KeyboardInterrupt, EOFError): + pass + finally: + if info: + echo(err=err) diff --git a/venv/lib/python3.12/site-packages/click/testing.py b/venv/lib/python3.12/site-packages/click/testing.py new file mode 100644 index 0000000..f6f60b8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/testing.py @@ -0,0 +1,577 @@ +from __future__ import annotations + +import collections.abc as cabc +import contextlib +import io +import os +import shlex +import sys +import tempfile +import typing as t +from types import TracebackType + +from . import _compat +from . import formatting +from . import termui +from . import utils +from ._compat import _find_binary_reader + +if t.TYPE_CHECKING: + from _typeshed import ReadableBuffer + + from .core import Command + + +class EchoingStdin: + def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None: + self._input = input + self._output = output + self._paused = False + + def __getattr__(self, x: str) -> t.Any: + return getattr(self._input, x) + + def _echo(self, rv: bytes) -> bytes: + if not self._paused: + self._output.write(rv) + + return rv + + def read(self, n: int = -1) -> bytes: + return self._echo(self._input.read(n)) + + def read1(self, n: int = -1) -> bytes: + return self._echo(self._input.read1(n)) # type: ignore + + def readline(self, n: int = -1) -> bytes: + return self._echo(self._input.readline(n)) + + def readlines(self) -> list[bytes]: + return [self._echo(x) for x in self._input.readlines()] + + def __iter__(self) -> cabc.Iterator[bytes]: + return iter(self._echo(x) for x in self._input) + + def __repr__(self) -> str: + return repr(self._input) + + +@contextlib.contextmanager +def _pause_echo(stream: EchoingStdin | None) -> cabc.Iterator[None]: + if stream is None: + yield + else: + stream._paused = True + yield + stream._paused = False + + +class BytesIOCopy(io.BytesIO): + """Patch ``io.BytesIO`` to let the written stream be copied to another. + + .. versionadded:: 8.2 + """ + + def __init__(self, copy_to: io.BytesIO) -> None: + super().__init__() + self.copy_to = copy_to + + def flush(self) -> None: + super().flush() + self.copy_to.flush() + + def write(self, b: ReadableBuffer) -> int: + self.copy_to.write(b) + return super().write(b) + + +class StreamMixer: + """Mixes `` and `` streams. + + The result is available in the ``output`` attribute. + + .. versionadded:: 8.2 + """ + + def __init__(self) -> None: + self.output: io.BytesIO = io.BytesIO() + self.stdout: io.BytesIO = BytesIOCopy(copy_to=self.output) + self.stderr: io.BytesIO = BytesIOCopy(copy_to=self.output) + + def __del__(self) -> None: + """ + Guarantee that embedded file-like objects are closed in a + predictable order, protecting against races between + self.output being closed and other streams being flushed on close + + .. versionadded:: 8.2.2 + """ + self.stderr.close() + self.stdout.close() + self.output.close() + + +class _NamedTextIOWrapper(io.TextIOWrapper): + def __init__( + self, buffer: t.BinaryIO, name: str, mode: str, **kwargs: t.Any + ) -> None: + super().__init__(buffer, **kwargs) + self._name = name + self._mode = mode + + @property + def name(self) -> str: + return self._name + + @property + def mode(self) -> str: + return self._mode + + +def make_input_stream( + input: str | bytes | t.IO[t.Any] | None, charset: str +) -> t.BinaryIO: + # Is already an input stream. + if hasattr(input, "read"): + rv = _find_binary_reader(t.cast("t.IO[t.Any]", input)) + + if rv is not None: + return rv + + raise TypeError("Could not find binary reader for input stream.") + + if input is None: + input = b"" + elif isinstance(input, str): + input = input.encode(charset) + + return io.BytesIO(input) + + +class Result: + """Holds the captured result of an invoked CLI script. + + :param runner: The runner that created the result + :param stdout_bytes: The standard output as bytes. + :param stderr_bytes: The standard error as bytes. + :param output_bytes: A mix of ``stdout_bytes`` and ``stderr_bytes``, as the + user would see it in its terminal. + :param return_value: The value returned from the invoked command. + :param exit_code: The exit code as integer. + :param exception: The exception that happened if one did. + :param exc_info: Exception information (exception type, exception instance, + traceback type). + + .. versionchanged:: 8.2 + ``stderr_bytes`` no longer optional, ``output_bytes`` introduced and + ``mix_stderr`` has been removed. + + .. versionadded:: 8.0 + Added ``return_value``. + """ + + def __init__( + self, + runner: CliRunner, + stdout_bytes: bytes, + stderr_bytes: bytes, + output_bytes: bytes, + return_value: t.Any, + exit_code: int, + exception: BaseException | None, + exc_info: tuple[type[BaseException], BaseException, TracebackType] + | None = None, + ): + self.runner = runner + self.stdout_bytes = stdout_bytes + self.stderr_bytes = stderr_bytes + self.output_bytes = output_bytes + self.return_value = return_value + self.exit_code = exit_code + self.exception = exception + self.exc_info = exc_info + + @property + def output(self) -> str: + """The terminal output as unicode string, as the user would see it. + + .. versionchanged:: 8.2 + No longer a proxy for ``self.stdout``. Now has its own independent stream + that is mixing `` and ``, in the order they were written. + """ + return self.output_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + @property + def stdout(self) -> str: + """The standard output as unicode string.""" + return self.stdout_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + @property + def stderr(self) -> str: + """The standard error as unicode string. + + .. versionchanged:: 8.2 + No longer raise an exception, always returns the `` string. + """ + return self.stderr_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + def __repr__(self) -> str: + exc_str = repr(self.exception) if self.exception else "okay" + return f"<{type(self).__name__} {exc_str}>" + + +class CliRunner: + """The CLI runner provides functionality to invoke a Click command line + script for unittesting purposes in a isolated environment. This only + works in single-threaded systems without any concurrency as it changes the + global interpreter state. + + :param charset: the character set for the input and output data. + :param env: a dictionary with environment variables for overriding. + :param echo_stdin: if this is set to `True`, then reading from `` writes + to ``. This is useful for showing examples in + some circumstances. Note that regular prompts + will automatically echo the input. + :param catch_exceptions: Whether to catch any exceptions other than + ``SystemExit`` when running :meth:`~CliRunner.invoke`. + + .. versionchanged:: 8.2 + Added the ``catch_exceptions`` parameter. + + .. versionchanged:: 8.2 + ``mix_stderr`` parameter has been removed. + """ + + def __init__( + self, + charset: str = "utf-8", + env: cabc.Mapping[str, str | None] | None = None, + echo_stdin: bool = False, + catch_exceptions: bool = True, + ) -> None: + self.charset = charset + self.env: cabc.Mapping[str, str | None] = env or {} + self.echo_stdin = echo_stdin + self.catch_exceptions = catch_exceptions + + def get_default_prog_name(self, cli: Command) -> str: + """Given a command object it will return the default program name + for it. The default is the `name` attribute or ``"root"`` if not + set. + """ + return cli.name or "root" + + def make_env( + self, overrides: cabc.Mapping[str, str | None] | None = None + ) -> cabc.Mapping[str, str | None]: + """Returns the environment overrides for invoking a script.""" + rv = dict(self.env) + if overrides: + rv.update(overrides) + return rv + + @contextlib.contextmanager + def isolation( + self, + input: str | bytes | t.IO[t.Any] | None = None, + env: cabc.Mapping[str, str | None] | None = None, + color: bool = False, + ) -> cabc.Iterator[tuple[io.BytesIO, io.BytesIO, io.BytesIO]]: + """A context manager that sets up the isolation for invoking of a + command line tool. This sets up `` with the given input data + and `os.environ` with the overrides from the given dictionary. + This also rebinds some internals in Click to be mocked (like the + prompt functionality). + + This is automatically done in the :meth:`invoke` method. + + :param input: the input stream to put into `sys.stdin`. + :param env: the environment overrides as dictionary. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + + .. versionadded:: 8.2 + An additional output stream is returned, which is a mix of + `` and `` streams. + + .. versionchanged:: 8.2 + Always returns the `` stream. + + .. versionchanged:: 8.0 + `` is opened with ``errors="backslashreplace"`` + instead of the default ``"strict"``. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + """ + bytes_input = make_input_stream(input, self.charset) + echo_input = None + + old_stdin = sys.stdin + old_stdout = sys.stdout + old_stderr = sys.stderr + old_forced_width = formatting.FORCED_WIDTH + formatting.FORCED_WIDTH = 80 + + env = self.make_env(env) + + stream_mixer = StreamMixer() + + if self.echo_stdin: + bytes_input = echo_input = t.cast( + t.BinaryIO, EchoingStdin(bytes_input, stream_mixer.stdout) + ) + + sys.stdin = text_input = _NamedTextIOWrapper( + bytes_input, encoding=self.charset, name="", mode="r" + ) + + if self.echo_stdin: + # Force unbuffered reads, otherwise TextIOWrapper reads a + # large chunk which is echoed early. + text_input._CHUNK_SIZE = 1 # type: ignore + + sys.stdout = _NamedTextIOWrapper( + stream_mixer.stdout, encoding=self.charset, name="", mode="w" + ) + + sys.stderr = _NamedTextIOWrapper( + stream_mixer.stderr, + encoding=self.charset, + name="", + mode="w", + errors="backslashreplace", + ) + + @_pause_echo(echo_input) # type: ignore + def visible_input(prompt: str | None = None) -> str: + sys.stdout.write(prompt or "") + try: + val = next(text_input).rstrip("\r\n") + except StopIteration as e: + raise EOFError() from e + sys.stdout.write(f"{val}\n") + sys.stdout.flush() + return val + + @_pause_echo(echo_input) # type: ignore + def hidden_input(prompt: str | None = None) -> str: + sys.stdout.write(f"{prompt or ''}\n") + sys.stdout.flush() + try: + return next(text_input).rstrip("\r\n") + except StopIteration as e: + raise EOFError() from e + + @_pause_echo(echo_input) # type: ignore + def _getchar(echo: bool) -> str: + char = sys.stdin.read(1) + + if echo: + sys.stdout.write(char) + + sys.stdout.flush() + return char + + default_color = color + + def should_strip_ansi( + stream: t.IO[t.Any] | None = None, color: bool | None = None + ) -> bool: + if color is None: + return not default_color + return not color + + old_visible_prompt_func = termui.visible_prompt_func + old_hidden_prompt_func = termui.hidden_prompt_func + old__getchar_func = termui._getchar + old_should_strip_ansi = utils.should_strip_ansi # type: ignore + old__compat_should_strip_ansi = _compat.should_strip_ansi + termui.visible_prompt_func = visible_input + termui.hidden_prompt_func = hidden_input + termui._getchar = _getchar + utils.should_strip_ansi = should_strip_ansi # type: ignore + _compat.should_strip_ansi = should_strip_ansi + + old_env = {} + try: + for key, value in env.items(): + old_env[key] = os.environ.get(key) + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + yield (stream_mixer.stdout, stream_mixer.stderr, stream_mixer.output) + finally: + for key, value in old_env.items(): + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + sys.stdout = old_stdout + sys.stderr = old_stderr + sys.stdin = old_stdin + termui.visible_prompt_func = old_visible_prompt_func + termui.hidden_prompt_func = old_hidden_prompt_func + termui._getchar = old__getchar_func + utils.should_strip_ansi = old_should_strip_ansi # type: ignore + _compat.should_strip_ansi = old__compat_should_strip_ansi + formatting.FORCED_WIDTH = old_forced_width + + def invoke( + self, + cli: Command, + args: str | cabc.Sequence[str] | None = None, + input: str | bytes | t.IO[t.Any] | None = None, + env: cabc.Mapping[str, str | None] | None = None, + catch_exceptions: bool | None = None, + color: bool = False, + **extra: t.Any, + ) -> Result: + """Invokes a command in an isolated environment. The arguments are + forwarded directly to the command line script, the `extra` keyword + arguments are passed to the :meth:`~clickpkg.Command.main` function of + the command. + + This returns a :class:`Result` object. + + :param cli: the command to invoke + :param args: the arguments to invoke. It may be given as an iterable + or a string. When given as string it will be interpreted + as a Unix shell command. More details at + :func:`shlex.split`. + :param input: the input data for `sys.stdin`. + :param env: the environment overrides. + :param catch_exceptions: Whether to catch any other exceptions than + ``SystemExit``. If :data:`None`, the value + from :class:`CliRunner` is used. + :param extra: the keyword arguments to pass to :meth:`main`. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + + .. versionadded:: 8.2 + The result object has the ``output_bytes`` attribute with + the mix of ``stdout_bytes`` and ``stderr_bytes``, as the user would + see it in its terminal. + + .. versionchanged:: 8.2 + The result object always returns the ``stderr_bytes`` stream. + + .. versionchanged:: 8.0 + The result object has the ``return_value`` attribute with + the value returned from the invoked command. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionchanged:: 3.0 + Added the ``catch_exceptions`` parameter. + + .. versionchanged:: 3.0 + The result object has the ``exc_info`` attribute with the + traceback if available. + """ + exc_info = None + if catch_exceptions is None: + catch_exceptions = self.catch_exceptions + + with self.isolation(input=input, env=env, color=color) as outstreams: + return_value = None + exception: BaseException | None = None + exit_code = 0 + + if isinstance(args, str): + args = shlex.split(args) + + try: + prog_name = extra.pop("prog_name") + except KeyError: + prog_name = self.get_default_prog_name(cli) + + try: + return_value = cli.main(args=args or (), prog_name=prog_name, **extra) + except SystemExit as e: + exc_info = sys.exc_info() + e_code = t.cast("int | t.Any | None", e.code) + + if e_code is None: + e_code = 0 + + if e_code != 0: + exception = e + + if not isinstance(e_code, int): + sys.stdout.write(str(e_code)) + sys.stdout.write("\n") + e_code = 1 + + exit_code = e_code + + except Exception as e: + if not catch_exceptions: + raise + exception = e + exit_code = 1 + exc_info = sys.exc_info() + finally: + sys.stdout.flush() + sys.stderr.flush() + stdout = outstreams[0].getvalue() + stderr = outstreams[1].getvalue() + output = outstreams[2].getvalue() + + return Result( + runner=self, + stdout_bytes=stdout, + stderr_bytes=stderr, + output_bytes=output, + return_value=return_value, + exit_code=exit_code, + exception=exception, + exc_info=exc_info, # type: ignore + ) + + @contextlib.contextmanager + def isolated_filesystem( + self, temp_dir: str | os.PathLike[str] | None = None + ) -> cabc.Iterator[str]: + """A context manager that creates a temporary directory and + changes the current working directory to it. This isolates tests + that affect the contents of the CWD to prevent them from + interfering with each other. + + :param temp_dir: Create the temporary directory under this + directory. If given, the created directory is not removed + when exiting. + + .. versionchanged:: 8.0 + Added the ``temp_dir`` parameter. + """ + cwd = os.getcwd() + dt = tempfile.mkdtemp(dir=temp_dir) + os.chdir(dt) + + try: + yield dt + finally: + os.chdir(cwd) + + if temp_dir is None: + import shutil + + try: + shutil.rmtree(dt) + except OSError: + pass diff --git a/venv/lib/python3.12/site-packages/click/types.py b/venv/lib/python3.12/site-packages/click/types.py new file mode 100644 index 0000000..e71c1c2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/types.py @@ -0,0 +1,1209 @@ +from __future__ import annotations + +import collections.abc as cabc +import enum +import os +import stat +import sys +import typing as t +from datetime import datetime +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import _get_argv_encoding +from ._compat import open_stream +from .exceptions import BadParameter +from .utils import format_filename +from .utils import LazyFile +from .utils import safecall + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .core import Context + from .core import Parameter + from .shell_completion import CompletionItem + +ParamTypeValue = t.TypeVar("ParamTypeValue") + + +class ParamType: + """Represents the type of a parameter. Validates and converts values + from the command line or Python into the correct type. + + To implement a custom type, subclass and implement at least the + following: + + - The :attr:`name` class attribute must be set. + - Calling an instance of the type with ``None`` must return + ``None``. This is already implemented by default. + - :meth:`convert` must convert string values to the correct type. + - :meth:`convert` must accept values that are already the correct + type. + - It must be able to convert a value if the ``ctx`` and ``param`` + arguments are ``None``. This can occur when converting prompt + input. + """ + + is_composite: t.ClassVar[bool] = False + arity: t.ClassVar[int] = 1 + + #: the descriptive name of this type + name: str + + #: if a list of this type is expected and the value is pulled from a + #: string environment variable, this is what splits it up. `None` + #: means any whitespace. For all parameters the general rule is that + #: whitespace splits them up. The exception are paths and files which + #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on + #: Windows). + envvar_list_splitter: t.ClassVar[str | None] = None + + def to_info_dict(self) -> dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + # The class name without the "ParamType" suffix. + param_type = type(self).__name__.partition("ParamType")[0] + param_type = param_type.partition("ParameterType")[0] + + # Custom subclasses might not remember to set a name. + if hasattr(self, "name"): + name = self.name + else: + name = param_type + + return {"param_type": param_type, "name": name} + + def __call__( + self, + value: t.Any, + param: Parameter | None = None, + ctx: Context | None = None, + ) -> t.Any: + if value is not None: + return self.convert(value, param, ctx) + + def get_metavar(self, param: Parameter, ctx: Context) -> str | None: + """Returns the metavar default for this param if it provides one.""" + + def get_missing_message(self, param: Parameter, ctx: Context | None) -> str | None: + """Optionally might return extra information about a missing + parameter. + + .. versionadded:: 2.0 + """ + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + """Convert the value to the correct type. This is not called if + the value is ``None`` (the missing value). + + This must accept string values from the command line, as well as + values that are already the correct type. It may also convert + other compatible types. + + The ``param`` and ``ctx`` arguments may be ``None`` in certain + situations, such as when converting prompt input. + + If the value cannot be converted, call :meth:`fail` with a + descriptive message. + + :param value: The value to convert. + :param param: The parameter that is using this type to convert + its value. May be ``None``. + :param ctx: The current context that arrived at this value. May + be ``None``. + """ + return value + + def split_envvar_value(self, rv: str) -> cabc.Sequence[str]: + """Given a value from an environment variable this splits it up + into small chunks depending on the defined envvar list splitter. + + If the splitter is set to `None`, which means that whitespace splits, + then leading and trailing whitespace is ignored. Otherwise, leading + and trailing splitters usually lead to empty items being included. + """ + return (rv or "").split(self.envvar_list_splitter) + + def fail( + self, + message: str, + param: Parameter | None = None, + ctx: Context | None = None, + ) -> t.NoReturn: + """Helper method to fail with an invalid value message.""" + raise BadParameter(message, ctx=ctx, param=param) + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> list[CompletionItem]: + """Return a list of + :class:`~click.shell_completion.CompletionItem` objects for the + incomplete value. Most types do not provide completions, but + some do, and this allows custom types to provide custom + completions as well. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + return [] + + +class CompositeParamType(ParamType): + is_composite = True + + @property + def arity(self) -> int: # type: ignore + raise NotImplementedError() + + +class FuncParamType(ParamType): + def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None: + self.name: str = func.__name__ + self.func = func + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["func"] = self.func + return info_dict + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + try: + return self.func(value) + except ValueError: + try: + value = str(value) + except UnicodeError: + value = value.decode("utf-8", "replace") + + self.fail(value, param, ctx) + + +class UnprocessedParamType(ParamType): + name = "text" + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + return value + + def __repr__(self) -> str: + return "UNPROCESSED" + + +class StringParamType(ParamType): + name = "text" + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + if isinstance(value, bytes): + enc = _get_argv_encoding() + try: + value = value.decode(enc) + except UnicodeError: + fs_enc = sys.getfilesystemencoding() + if fs_enc != enc: + try: + value = value.decode(fs_enc) + except UnicodeError: + value = value.decode("utf-8", "replace") + else: + value = value.decode("utf-8", "replace") + return value + return str(value) + + def __repr__(self) -> str: + return "STRING" + + +class Choice(ParamType, t.Generic[ParamTypeValue]): + """The choice type allows a value to be checked against a fixed set + of supported values. + + You may pass any iterable value which will be converted to a tuple + and thus will only be iterated once. + + The resulting value will always be one of the originally passed choices. + See :meth:`normalize_choice` for more info on the mapping of strings + to choices. See :ref:`choice-opts` for an example. + + :param case_sensitive: Set to false to make choices case + insensitive. Defaults to true. + + .. versionchanged:: 8.2.0 + Non-``str`` ``choices`` are now supported. It can additionally be any + iterable. Before you were not recommended to pass anything but a list or + tuple. + + .. versionadded:: 8.2.0 + Choice normalization can be overridden via :meth:`normalize_choice`. + """ + + name = "choice" + + def __init__( + self, choices: cabc.Iterable[ParamTypeValue], case_sensitive: bool = True + ) -> None: + self.choices: cabc.Sequence[ParamTypeValue] = tuple(choices) + self.case_sensitive = case_sensitive + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["choices"] = self.choices + info_dict["case_sensitive"] = self.case_sensitive + return info_dict + + def _normalized_mapping( + self, ctx: Context | None = None + ) -> cabc.Mapping[ParamTypeValue, str]: + """ + Returns mapping where keys are the original choices and the values are + the normalized values that are accepted via the command line. + + This is a simple wrapper around :meth:`normalize_choice`, use that + instead which is supported. + """ + return { + choice: self.normalize_choice( + choice=choice, + ctx=ctx, + ) + for choice in self.choices + } + + def normalize_choice(self, choice: ParamTypeValue, ctx: Context | None) -> str: + """ + Normalize a choice value, used to map a passed string to a choice. + Each choice must have a unique normalized value. + + By default uses :meth:`Context.token_normalize_func` and if not case + sensitive, convert it to a casefolded value. + + .. versionadded:: 8.2.0 + """ + normed_value = choice.name if isinstance(choice, enum.Enum) else str(choice) + + if ctx is not None and ctx.token_normalize_func is not None: + normed_value = ctx.token_normalize_func(normed_value) + + if not self.case_sensitive: + normed_value = normed_value.casefold() + + return normed_value + + def get_metavar(self, param: Parameter, ctx: Context) -> str | None: + if param.param_type_name == "option" and not param.show_choices: # type: ignore + choice_metavars = [ + convert_type(type(choice)).name.upper() for choice in self.choices + ] + choices_str = "|".join([*dict.fromkeys(choice_metavars)]) + else: + choices_str = "|".join( + [str(i) for i in self._normalized_mapping(ctx=ctx).values()] + ) + + # Use curly braces to indicate a required argument. + if param.required and param.param_type_name == "argument": + return f"{{{choices_str}}}" + + # Use square braces to indicate an option or optional argument. + return f"[{choices_str}]" + + def get_missing_message(self, param: Parameter, ctx: Context | None) -> str: + """ + Message shown when no choice is passed. + + .. versionchanged:: 8.2.0 Added ``ctx`` argument. + """ + return _("Choose from:\n\t{choices}").format( + choices=",\n\t".join(self._normalized_mapping(ctx=ctx).values()) + ) + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> ParamTypeValue: + """ + For a given value from the parser, normalize it and find its + matching normalized value in the list of choices. Then return the + matched "original" choice. + """ + normed_value = self.normalize_choice(choice=value, ctx=ctx) + normalized_mapping = self._normalized_mapping(ctx=ctx) + + try: + return next( + original + for original, normalized in normalized_mapping.items() + if normalized == normed_value + ) + except StopIteration: + self.fail( + self.get_invalid_choice_message(value=value, ctx=ctx), + param=param, + ctx=ctx, + ) + + def get_invalid_choice_message(self, value: t.Any, ctx: Context | None) -> str: + """Get the error message when the given choice is invalid. + + :param value: The invalid value. + + .. versionadded:: 8.2 + """ + choices_str = ", ".join(map(repr, self._normalized_mapping(ctx=ctx).values())) + return ngettext( + "{value!r} is not {choice}.", + "{value!r} is not one of {choices}.", + len(self.choices), + ).format(value=value, choice=choices_str, choices=choices_str) + + def __repr__(self) -> str: + return f"Choice({list(self.choices)})" + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> list[CompletionItem]: + """Complete choices that start with the incomplete value. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + str_choices = map(str, self.choices) + + if self.case_sensitive: + matched = (c for c in str_choices if c.startswith(incomplete)) + else: + incomplete = incomplete.lower() + matched = (c for c in str_choices if c.lower().startswith(incomplete)) + + return [CompletionItem(c) for c in matched] + + +class DateTime(ParamType): + """The DateTime type converts date strings into `datetime` objects. + + The format strings which are checked are configurable, but default to some + common (non-timezone aware) ISO 8601 formats. + + When specifying *DateTime* formats, you should only pass a list or a tuple. + Other iterables, like generators, may lead to surprising results. + + The format strings are processed using ``datetime.strptime``, and this + consequently defines the format strings which are allowed. + + Parsing is tried using each format, in order, and the first format which + parses successfully is used. + + :param formats: A list or tuple of date format strings, in the order in + which they should be tried. Defaults to + ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``, + ``'%Y-%m-%d %H:%M:%S'``. + """ + + name = "datetime" + + def __init__(self, formats: cabc.Sequence[str] | None = None): + self.formats: cabc.Sequence[str] = formats or [ + "%Y-%m-%d", + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%d %H:%M:%S", + ] + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["formats"] = self.formats + return info_dict + + def get_metavar(self, param: Parameter, ctx: Context) -> str | None: + return f"[{'|'.join(self.formats)}]" + + def _try_to_convert_date(self, value: t.Any, format: str) -> datetime | None: + try: + return datetime.strptime(value, format) + except ValueError: + return None + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + if isinstance(value, datetime): + return value + + for format in self.formats: + converted = self._try_to_convert_date(value, format) + + if converted is not None: + return converted + + formats_str = ", ".join(map(repr, self.formats)) + self.fail( + ngettext( + "{value!r} does not match the format {format}.", + "{value!r} does not match the formats {formats}.", + len(self.formats), + ).format(value=value, format=formats_str, formats=formats_str), + param, + ctx, + ) + + def __repr__(self) -> str: + return "DateTime" + + +class _NumberParamTypeBase(ParamType): + _number_class: t.ClassVar[type[t.Any]] + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + try: + return self._number_class(value) + except ValueError: + self.fail( + _("{value!r} is not a valid {number_type}.").format( + value=value, number_type=self.name + ), + param, + ctx, + ) + + +class _NumberRangeBase(_NumberParamTypeBase): + def __init__( + self, + min: float | None = None, + max: float | None = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + self.min = min + self.max = max + self.min_open = min_open + self.max_open = max_open + self.clamp = clamp + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + min=self.min, + max=self.max, + min_open=self.min_open, + max_open=self.max_open, + clamp=self.clamp, + ) + return info_dict + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + import operator + + rv = super().convert(value, param, ctx) + lt_min: bool = self.min is not None and ( + operator.le if self.min_open else operator.lt + )(rv, self.min) + gt_max: bool = self.max is not None and ( + operator.ge if self.max_open else operator.gt + )(rv, self.max) + + if self.clamp: + if lt_min: + return self._clamp(self.min, 1, self.min_open) # type: ignore + + if gt_max: + return self._clamp(self.max, -1, self.max_open) # type: ignore + + if lt_min or gt_max: + self.fail( + _("{value} is not in the range {range}.").format( + value=rv, range=self._describe_range() + ), + param, + ctx, + ) + + return rv + + def _clamp(self, bound: float, dir: t.Literal[1, -1], open: bool) -> float: + """Find the valid value to clamp to bound in the given + direction. + + :param bound: The boundary value. + :param dir: 1 or -1 indicating the direction to move. + :param open: If true, the range does not include the bound. + """ + raise NotImplementedError + + def _describe_range(self) -> str: + """Describe the range for use in help text.""" + if self.min is None: + op = "<" if self.max_open else "<=" + return f"x{op}{self.max}" + + if self.max is None: + op = ">" if self.min_open else ">=" + return f"x{op}{self.min}" + + lop = "<" if self.min_open else "<=" + rop = "<" if self.max_open else "<=" + return f"{self.min}{lop}x{rop}{self.max}" + + def __repr__(self) -> str: + clamp = " clamped" if self.clamp else "" + return f"<{type(self).__name__} {self._describe_range()}{clamp}>" + + +class IntParamType(_NumberParamTypeBase): + name = "integer" + _number_class = int + + def __repr__(self) -> str: + return "INT" + + +class IntRange(_NumberRangeBase, IntParamType): + """Restrict an :data:`click.INT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "integer range" + + def _clamp( # type: ignore + self, bound: int, dir: t.Literal[1, -1], open: bool + ) -> int: + if not open: + return bound + + return bound + dir + + +class FloatParamType(_NumberParamTypeBase): + name = "float" + _number_class = float + + def __repr__(self) -> str: + return "FLOAT" + + +class FloatRange(_NumberRangeBase, FloatParamType): + """Restrict a :data:`click.FLOAT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. This is not supported if either + boundary is marked ``open``. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "float range" + + def __init__( + self, + min: float | None = None, + max: float | None = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + super().__init__( + min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp + ) + + if (min_open or max_open) and clamp: + raise TypeError("Clamping is not supported for open bounds.") + + def _clamp(self, bound: float, dir: t.Literal[1, -1], open: bool) -> float: + if not open: + return bound + + # Could use math.nextafter here, but clamping an + # open float range doesn't seem to be particularly useful. It's + # left up to the user to write a callback to do it if needed. + raise RuntimeError("Clamping is not supported for open bounds.") + + +class BoolParamType(ParamType): + name = "boolean" + + bool_states: dict[str, bool] = { + "1": True, + "0": False, + "yes": True, + "no": False, + "true": True, + "false": False, + "on": True, + "off": False, + "t": True, + "f": False, + "y": True, + "n": False, + # Absence of value is considered False. + "": False, + } + """A mapping of string values to boolean states. + + Mapping is inspired by :py:attr:`configparser.ConfigParser.BOOLEAN_STATES` + and extends it. + + .. caution:: + String values are lower-cased, as the ``str_to_bool`` comparison function + below is case-insensitive. + + .. warning:: + The mapping is not exhaustive, and does not cover all possible boolean strings + representations. It will remains as it is to avoid endless bikeshedding. + + Future work my be considered to make this mapping user-configurable from public + API. + """ + + @staticmethod + def str_to_bool(value: str | bool) -> bool | None: + """Convert a string to a boolean value. + + If the value is already a boolean, it is returned as-is. If the value is a + string, it is stripped of whitespaces and lower-cased, then checked against + the known boolean states pre-defined in the `BoolParamType.bool_states` mapping + above. + + Returns `None` if the value does not match any known boolean state. + """ + if isinstance(value, bool): + return value + return BoolParamType.bool_states.get(value.strip().lower()) + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> bool: + normalized = self.str_to_bool(value) + if normalized is None: + self.fail( + _( + "{value!r} is not a valid boolean. Recognized values: {states}" + ).format(value=value, states=", ".join(sorted(self.bool_states))), + param, + ctx, + ) + return normalized + + def __repr__(self) -> str: + return "BOOL" + + +class UUIDParameterType(ParamType): + name = "uuid" + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + import uuid + + if isinstance(value, uuid.UUID): + return value + + value = value.strip() + + try: + return uuid.UUID(value) + except ValueError: + self.fail( + _("{value!r} is not a valid UUID.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "UUID" + + +class File(ParamType): + """Declares a parameter to be a file for reading or writing. The file + is automatically closed once the context tears down (after the command + finished working). + + Files can be opened for reading or writing. The special value ``-`` + indicates stdin or stdout depending on the mode. + + By default, the file is opened for reading text data, but it can also be + opened in binary mode or for writing. The encoding parameter can be used + to force a specific encoding. + + The `lazy` flag controls if the file should be opened immediately or upon + first IO. The default is to be non-lazy for standard input and output + streams as well as files opened for reading, `lazy` otherwise. When opening a + file lazily for reading, it is still opened temporarily for validation, but + will not be held open until first IO. lazy is mainly useful when opening + for writing to avoid creating the file until it is needed. + + Files can also be opened atomically in which case all writes go into a + separate file in the same folder and upon completion the file will + be moved over to the original location. This is useful if a file + regularly read by other users is modified. + + See :ref:`file-args` for more information. + + .. versionchanged:: 2.0 + Added the ``atomic`` parameter. + """ + + name = "filename" + envvar_list_splitter: t.ClassVar[str] = os.path.pathsep + + def __init__( + self, + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool | None = None, + atomic: bool = False, + ) -> None: + self.mode = mode + self.encoding = encoding + self.errors = errors + self.lazy = lazy + self.atomic = atomic + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update(mode=self.mode, encoding=self.encoding) + return info_dict + + def resolve_lazy_flag(self, value: str | os.PathLike[str]) -> bool: + if self.lazy is not None: + return self.lazy + if os.fspath(value) == "-": + return False + elif "w" in self.mode: + return True + return False + + def convert( + self, + value: str | os.PathLike[str] | t.IO[t.Any], + param: Parameter | None, + ctx: Context | None, + ) -> t.IO[t.Any]: + if _is_file_like(value): + return value + + value = t.cast("str | os.PathLike[str]", value) + + try: + lazy = self.resolve_lazy_flag(value) + + if lazy: + lf = LazyFile( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + + if ctx is not None: + ctx.call_on_close(lf.close_intelligently) + + return t.cast("t.IO[t.Any]", lf) + + f, should_close = open_stream( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + + # If a context is provided, we automatically close the file + # at the end of the context execution (or flush out). If a + # context does not exist, it's the caller's responsibility to + # properly close the file. This for instance happens when the + # type is used with prompts. + if ctx is not None: + if should_close: + ctx.call_on_close(safecall(f.close)) + else: + ctx.call_on_close(safecall(f.flush)) + + return f + except OSError as e: + self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx) + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> list[CompletionItem]: + """Return a special completion marker that tells the completion + system to use the shell to provide file path completions. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + return [CompletionItem(incomplete, type="file")] + + +def _is_file_like(value: t.Any) -> te.TypeGuard[t.IO[t.Any]]: + return hasattr(value, "read") or hasattr(value, "write") + + +class Path(ParamType): + """The ``Path`` type is similar to the :class:`File` type, but + returns the filename instead of an open file. Various checks can be + enabled to validate the type of file and permissions. + + :param exists: The file or directory needs to exist for the value to + be valid. If this is not set to ``True``, and the file does not + exist, then all further checks are silently skipped. + :param file_okay: Allow a file as a value. + :param dir_okay: Allow a directory as a value. + :param readable: if true, a readable check is performed. + :param writable: if true, a writable check is performed. + :param executable: if true, an executable check is performed. + :param resolve_path: Make the value absolute and resolve any + symlinks. A ``~`` is not expanded, as this is supposed to be + done by the shell only. + :param allow_dash: Allow a single dash as a value, which indicates + a standard stream (but does not open it). Use + :func:`~click.open_file` to handle opening this value. + :param path_type: Convert the incoming path value to this type. If + ``None``, keep Python's default, which is ``str``. Useful to + convert to :class:`pathlib.Path`. + + .. versionchanged:: 8.1 + Added the ``executable`` parameter. + + .. versionchanged:: 8.0 + Allow passing ``path_type=pathlib.Path``. + + .. versionchanged:: 6.0 + Added the ``allow_dash`` parameter. + """ + + envvar_list_splitter: t.ClassVar[str] = os.path.pathsep + + def __init__( + self, + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: type[t.Any] | None = None, + executable: bool = False, + ): + self.exists = exists + self.file_okay = file_okay + self.dir_okay = dir_okay + self.readable = readable + self.writable = writable + self.executable = executable + self.resolve_path = resolve_path + self.allow_dash = allow_dash + self.type = path_type + + if self.file_okay and not self.dir_okay: + self.name: str = _("file") + elif self.dir_okay and not self.file_okay: + self.name = _("directory") + else: + self.name = _("path") + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + exists=self.exists, + file_okay=self.file_okay, + dir_okay=self.dir_okay, + writable=self.writable, + readable=self.readable, + allow_dash=self.allow_dash, + ) + return info_dict + + def coerce_path_result( + self, value: str | os.PathLike[str] + ) -> str | bytes | os.PathLike[str]: + if self.type is not None and not isinstance(value, self.type): + if self.type is str: + return os.fsdecode(value) + elif self.type is bytes: + return os.fsencode(value) + else: + return t.cast("os.PathLike[str]", self.type(value)) + + return value + + def convert( + self, + value: str | os.PathLike[str], + param: Parameter | None, + ctx: Context | None, + ) -> str | bytes | os.PathLike[str]: + rv = value + + is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-") + + if not is_dash: + if self.resolve_path: + rv = os.path.realpath(rv) + + try: + st = os.stat(rv) + except OSError: + if not self.exists: + return self.coerce_path_result(rv) + self.fail( + _("{name} {filename!r} does not exist.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if not self.file_okay and stat.S_ISREG(st.st_mode): + self.fail( + _("{name} {filename!r} is a file.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + if not self.dir_okay and stat.S_ISDIR(st.st_mode): + self.fail( + _("{name} {filename!r} is a directory.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if self.readable and not os.access(rv, os.R_OK): + self.fail( + _("{name} {filename!r} is not readable.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if self.writable and not os.access(rv, os.W_OK): + self.fail( + _("{name} {filename!r} is not writable.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if self.executable and not os.access(value, os.X_OK): + self.fail( + _("{name} {filename!r} is not executable.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + return self.coerce_path_result(rv) + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> list[CompletionItem]: + """Return a special completion marker that tells the completion + system to use the shell to provide path completions for only + directories or any paths. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + type = "dir" if self.dir_okay and not self.file_okay else "file" + return [CompletionItem(incomplete, type=type)] + + +class Tuple(CompositeParamType): + """The default behavior of Click is to apply a type on a value directly. + This works well in most cases, except for when `nargs` is set to a fixed + count and different types should be used for different items. In this + case the :class:`Tuple` type can be used. This type can only be used + if `nargs` is set to a fixed number. + + For more information see :ref:`tuple-type`. + + This can be selected by using a Python tuple literal as a type. + + :param types: a list of types that should be used for the tuple items. + """ + + def __init__(self, types: cabc.Sequence[type[t.Any] | ParamType]) -> None: + self.types: cabc.Sequence[ParamType] = [convert_type(ty) for ty in types] + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["types"] = [t.to_info_dict() for t in self.types] + return info_dict + + @property + def name(self) -> str: # type: ignore + return f"<{' '.join(ty.name for ty in self.types)}>" + + @property + def arity(self) -> int: # type: ignore + return len(self.types) + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + len_type = len(self.types) + len_value = len(value) + + if len_value != len_type: + self.fail( + ngettext( + "{len_type} values are required, but {len_value} was given.", + "{len_type} values are required, but {len_value} were given.", + len_value, + ).format(len_type=len_type, len_value=len_value), + param=param, + ctx=ctx, + ) + + return tuple( + ty(x, param, ctx) for ty, x in zip(self.types, value, strict=False) + ) + + +def convert_type(ty: t.Any | None, default: t.Any | None = None) -> ParamType: + """Find the most appropriate :class:`ParamType` for the given Python + type. If the type isn't provided, it can be inferred from a default + value. + """ + guessed_type = False + + if ty is None and default is not None: + if isinstance(default, (tuple, list)): + # If the default is empty, ty will remain None and will + # return STRING. + if default: + item = default[0] + + # A tuple of tuples needs to detect the inner types. + # Can't call convert recursively because that would + # incorrectly unwind the tuple to a single type. + if isinstance(item, (tuple, list)): + ty = tuple(map(type, item)) + else: + ty = type(item) + else: + ty = type(default) + + guessed_type = True + + if isinstance(ty, tuple): + return Tuple(ty) + + if isinstance(ty, ParamType): + return ty + + if ty is str or ty is None: + return STRING + + if ty is int: + return INT + + if ty is float: + return FLOAT + + if ty is bool: + return BOOL + + if guessed_type: + return STRING + + if __debug__: + try: + if issubclass(ty, ParamType): + raise AssertionError( + f"Attempted to use an uninstantiated parameter type ({ty})." + ) + except TypeError: + # ty is an instance (correct), so issubclass fails. + pass + + return FuncParamType(ty) + + +#: A dummy parameter type that just does nothing. From a user's +#: perspective this appears to just be the same as `STRING` but +#: internally no string conversion takes place if the input was bytes. +#: This is usually useful when working with file paths as they can +#: appear in bytes and unicode. +#: +#: For path related uses the :class:`Path` type is a better choice but +#: there are situations where an unprocessed type is useful which is why +#: it is is provided. +#: +#: .. versionadded:: 4.0 +UNPROCESSED = UnprocessedParamType() + +#: A unicode string parameter type which is the implicit default. This +#: can also be selected by using ``str`` as type. +STRING = StringParamType() + +#: An integer parameter. This can also be selected by using ``int`` as +#: type. +INT = IntParamType() + +#: A floating point value parameter. This can also be selected by using +#: ``float`` as type. +FLOAT = FloatParamType() + +#: A boolean parameter. This is the default for boolean flags. This can +#: also be selected by using ``bool`` as a type. +BOOL = BoolParamType() + +#: A UUID parameter. +UUID = UUIDParameterType() + + +class OptionHelpExtra(t.TypedDict, total=False): + envvars: tuple[str, ...] + default: str + range: str + required: str diff --git a/venv/lib/python3.12/site-packages/click/utils.py b/venv/lib/python3.12/site-packages/click/utils.py new file mode 100644 index 0000000..beae26f --- /dev/null +++ b/venv/lib/python3.12/site-packages/click/utils.py @@ -0,0 +1,627 @@ +from __future__ import annotations + +import collections.abc as cabc +import os +import re +import sys +import typing as t +from functools import update_wrapper +from types import ModuleType +from types import TracebackType + +from ._compat import _default_text_stderr +from ._compat import _default_text_stdout +from ._compat import _find_binary_writer +from ._compat import auto_wrap_for_ansi +from ._compat import binary_streams +from ._compat import open_stream +from ._compat import should_strip_ansi +from ._compat import strip_ansi +from ._compat import text_streams +from ._compat import WIN +from .globals import resolve_color_default + +if t.TYPE_CHECKING: + import typing_extensions as te + + P = te.ParamSpec("P") + +R = t.TypeVar("R") + + +def _posixify(name: str) -> str: + return "-".join(name.split()).lower() + + +def safecall(func: t.Callable[P, R]) -> t.Callable[P, R | None]: + """Wraps a function so that it swallows exceptions.""" + + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R | None: + try: + return func(*args, **kwargs) + except Exception: + pass + return None + + return update_wrapper(wrapper, func) + + +def make_str(value: t.Any) -> str: + """Converts a value into a valid string.""" + if isinstance(value, bytes): + try: + return value.decode(sys.getfilesystemencoding()) + except UnicodeError: + return value.decode("utf-8", "replace") + return str(value) + + +def make_default_short_help(help: str, max_length: int = 45) -> str: + """Returns a condensed version of help string.""" + # Consider only the first paragraph. + paragraph_end = help.find("\n\n") + + if paragraph_end != -1: + help = help[:paragraph_end] + + # Collapse newlines, tabs, and spaces. + words = help.split() + + if not words: + return "" + + # The first paragraph started with a "no rewrap" marker, ignore it. + if words[0] == "\b": + words = words[1:] + + total_length = 0 + last_index = len(words) - 1 + + for i, word in enumerate(words): + total_length += len(word) + (i > 0) + + if total_length > max_length: # too long, truncate + break + + if word[-1] == ".": # sentence end, truncate without "..." + return " ".join(words[: i + 1]) + + if total_length == max_length and i != last_index: + break # not at sentence end, truncate with "..." + else: + return " ".join(words) # no truncation needed + + # Account for the length of the suffix. + total_length += len("...") + + # remove words until the length is short enough + while i > 0: + total_length -= len(words[i]) + (i > 0) + + if total_length <= max_length: + break + + i -= 1 + + return " ".join(words[:i]) + "..." + + +class LazyFile: + """A lazy file works like a regular file but it does not fully open + the file but it does perform some basic checks early to see if the + filename parameter does make sense. This is useful for safely opening + files for writing. + """ + + def __init__( + self, + filename: str | os.PathLike[str], + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + atomic: bool = False, + ): + self.name: str = os.fspath(filename) + self.mode = mode + self.encoding = encoding + self.errors = errors + self.atomic = atomic + self._f: t.IO[t.Any] | None + self.should_close: bool + + if self.name == "-": + self._f, self.should_close = open_stream(filename, mode, encoding, errors) + else: + if "r" in mode: + # Open and close the file in case we're opening it for + # reading so that we can catch at least some errors in + # some cases early. + open(filename, mode).close() + self._f = None + self.should_close = True + + def __getattr__(self, name: str) -> t.Any: + return getattr(self.open(), name) + + def __repr__(self) -> str: + if self._f is not None: + return repr(self._f) + return f"" + + def open(self) -> t.IO[t.Any]: + """Opens the file if it's not yet open. This call might fail with + a :exc:`FileError`. Not handling this error will produce an error + that Click shows. + """ + if self._f is not None: + return self._f + try: + rv, self.should_close = open_stream( + self.name, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + except OSError as e: + from .exceptions import FileError + + raise FileError(self.name, hint=e.strerror) from e + self._f = rv + return rv + + def close(self) -> None: + """Closes the underlying file, no matter what.""" + if self._f is not None: + self._f.close() + + def close_intelligently(self) -> None: + """This function only closes the file if it was opened by the lazy + file wrapper. For instance this will never close stdin. + """ + if self.should_close: + self.close() + + def __enter__(self) -> LazyFile: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.close_intelligently() + + def __iter__(self) -> cabc.Iterator[t.AnyStr]: + self.open() + return iter(self._f) # type: ignore + + +class KeepOpenFile: + def __init__(self, file: t.IO[t.Any]) -> None: + self._file: t.IO[t.Any] = file + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._file, name) + + def __enter__(self) -> KeepOpenFile: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + pass + + def __repr__(self) -> str: + return repr(self._file) + + def __iter__(self) -> cabc.Iterator[t.AnyStr]: + return iter(self._file) + + +def echo( + message: t.Any | None = None, + file: t.IO[t.Any] | None = None, + nl: bool = True, + err: bool = False, + color: bool | None = None, +) -> None: + """Print a message and newline to stdout or a file. This should be + used instead of :func:`print` because it provides better support + for different data, files, and environments. + + Compared to :func:`print`, this does the following: + + - Ensures that the output encoding is not misconfigured on Linux. + - Supports Unicode in the Windows console. + - Supports writing to binary outputs, and supports writing bytes + to text outputs. + - Supports colors and styles on Windows. + - Removes ANSI color and style codes if the output does not look + like an interactive terminal. + - Always flushes the output. + + :param message: The string or bytes to output. Other objects are + converted to strings. + :param file: The file to write to. Defaults to ``stdout``. + :param err: Write to ``stderr`` instead of ``stdout``. + :param nl: Print a newline after the message. Enabled by default. + :param color: Force showing or hiding colors and other styles. By + default Click will remove color if the output does not look like + an interactive terminal. + + .. versionchanged:: 6.0 + Support Unicode output on the Windows console. Click does not + modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()`` + will still not support Unicode. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionadded:: 3.0 + Added the ``err`` parameter. + + .. versionchanged:: 2.0 + Support colors on Windows if colorama is installed. + """ + if file is None: + if err: + file = _default_text_stderr() + else: + file = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if file is None: + return + + # Convert non bytes/text into the native string type. + if message is not None and not isinstance(message, (str, bytes, bytearray)): + out: str | bytes | bytearray | None = str(message) + else: + out = message + + if nl: + out = out or "" + if isinstance(out, str): + out += "\n" + else: + out += b"\n" + + if not out: + file.flush() + return + + # If there is a message and the value looks like bytes, we manually + # need to find the binary stream and write the message in there. + # This is done separately so that most stream types will work as you + # would expect. Eg: you can write to StringIO for other cases. + if isinstance(out, (bytes, bytearray)): + binary_file = _find_binary_writer(file) + + if binary_file is not None: + file.flush() + binary_file.write(out) + binary_file.flush() + return + + # ANSI style code support. For no message or bytes, nothing happens. + # When outputting to a file instead of a terminal, strip codes. + else: + color = resolve_color_default(color) + + if should_strip_ansi(file, color): + out = strip_ansi(out) + elif WIN: + if auto_wrap_for_ansi is not None: + file = auto_wrap_for_ansi(file, color) # type: ignore + elif not color: + out = strip_ansi(out) + + file.write(out) # type: ignore + file.flush() + + +def get_binary_stream(name: t.Literal["stdin", "stdout", "stderr"]) -> t.BinaryIO: + """Returns a system stream for byte processing. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + """ + opener = binary_streams.get(name) + if opener is None: + raise TypeError(f"Unknown standard stream '{name}'") + return opener() + + +def get_text_stream( + name: t.Literal["stdin", "stdout", "stderr"], + encoding: str | None = None, + errors: str | None = "strict", +) -> t.TextIO: + """Returns a system stream for text processing. This usually returns + a wrapped stream around a binary stream returned from + :func:`get_binary_stream` but it also can take shortcuts for already + correctly configured streams. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + :param encoding: overrides the detected default encoding. + :param errors: overrides the default error mode. + """ + opener = text_streams.get(name) + if opener is None: + raise TypeError(f"Unknown standard stream '{name}'") + return opener(encoding, errors) + + +def open_file( + filename: str | os.PathLike[str], + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool = False, + atomic: bool = False, +) -> t.IO[t.Any]: + """Open a file, with extra behavior to handle ``'-'`` to indicate + a standard stream, lazy open on write, and atomic write. Similar to + the behavior of the :class:`~click.File` param type. + + If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is + wrapped so that using it in a context manager will not close it. + This makes it possible to use the function without accidentally + closing a standard stream: + + .. code-block:: python + + with open_file(filename) as f: + ... + + :param filename: The name or Path of the file to open, or ``'-'`` for + ``stdin``/``stdout``. + :param mode: The mode in which to open the file. + :param encoding: The encoding to decode or encode a file opened in + text mode. + :param errors: The error handling mode. + :param lazy: Wait to open the file until it is accessed. For read + mode, the file is temporarily opened to raise access errors + early, then closed until it is read again. + :param atomic: Write to a temporary file and replace the given file + on close. + + .. versionadded:: 3.0 + """ + if lazy: + return t.cast( + "t.IO[t.Any]", LazyFile(filename, mode, encoding, errors, atomic=atomic) + ) + + f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic) + + if not should_close: + f = t.cast("t.IO[t.Any]", KeepOpenFile(f)) + + return f + + +def format_filename( + filename: str | bytes | os.PathLike[str] | os.PathLike[bytes], + shorten: bool = False, +) -> str: + """Format a filename as a string for display. Ensures the filename can be + displayed by replacing any invalid bytes or surrogate escapes in the name + with the replacement character ``�``. + + Invalid bytes or surrogate escapes will raise an error when written to a + stream with ``errors="strict"``. This will typically happen with ``stdout`` + when the locale is something like ``en_GB.UTF-8``. + + Many scenarios *are* safe to write surrogates though, due to PEP 538 and + PEP 540, including: + + - Writing to ``stderr``, which uses ``errors="backslashreplace"``. + - The system has ``LANG=C.UTF-8``, ``C``, or ``POSIX``. Python opens + stdout and stderr with ``errors="surrogateescape"``. + - None of ``LANG/LC_*`` are set. Python assumes ``LANG=C.UTF-8``. + - Python is started in UTF-8 mode with ``PYTHONUTF8=1`` or ``-X utf8``. + Python opens stdout and stderr with ``errors="surrogateescape"``. + + :param filename: formats a filename for UI display. This will also convert + the filename into unicode without failing. + :param shorten: this optionally shortens the filename to strip of the + path that leads up to it. + """ + if shorten: + filename = os.path.basename(filename) + else: + filename = os.fspath(filename) + + if isinstance(filename, bytes): + filename = filename.decode(sys.getfilesystemencoding(), "replace") + else: + filename = filename.encode("utf-8", "surrogateescape").decode( + "utf-8", "replace" + ) + + return filename + + +def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str: + r"""Returns the config folder for the application. The default behavior + is to return whatever is most appropriate for the operating system. + + To give you an idea, for an app called ``"Foo Bar"``, something like + the following folders could be returned: + + Mac OS X: + ``~/Library/Application Support/Foo Bar`` + Mac OS X (POSIX): + ``~/.foo-bar`` + Unix: + ``~/.config/foo-bar`` + Unix (POSIX): + ``~/.foo-bar`` + Windows (roaming): + ``C:\Users\\AppData\Roaming\Foo Bar`` + Windows (not roaming): + ``C:\Users\\AppData\Local\Foo Bar`` + + .. versionadded:: 2.0 + + :param app_name: the application name. This should be properly capitalized + and can contain whitespace. + :param roaming: controls if the folder should be roaming or not on Windows. + Has no effect otherwise. + :param force_posix: if this is set to `True` then on any POSIX system the + folder will be stored in the home folder with a leading + dot instead of the XDG config home or darwin's + application support folder. + """ + if WIN: + key = "APPDATA" if roaming else "LOCALAPPDATA" + folder = os.environ.get(key) + if folder is None: + folder = os.path.expanduser("~") + return os.path.join(folder, app_name) + if force_posix: + return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}")) + if sys.platform == "darwin": + return os.path.join( + os.path.expanduser("~/Library/Application Support"), app_name + ) + return os.path.join( + os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), + _posixify(app_name), + ) + + +class PacifyFlushWrapper: + """This wrapper is used to catch and suppress BrokenPipeErrors resulting + from ``.flush()`` being called on broken pipe during the shutdown/final-GC + of the Python interpreter. Notably ``.flush()`` is always called on + ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any + other cleanup code, and the case where the underlying file is not a broken + pipe, all calls and attributes are proxied. + """ + + def __init__(self, wrapped: t.IO[t.Any]) -> None: + self.wrapped = wrapped + + def flush(self) -> None: + try: + self.wrapped.flush() + except OSError as e: + import errno + + if e.errno != errno.EPIPE: + raise + + def __getattr__(self, attr: str) -> t.Any: + return getattr(self.wrapped, attr) + + +def _detect_program_name( + path: str | None = None, _main: ModuleType | None = None +) -> str: + """Determine the command used to run the program, for use in help + text. If a file or entry point was executed, the file name is + returned. If ``python -m`` was used to execute a module or package, + ``python -m name`` is returned. + + This doesn't try to be too precise, the goal is to give a concise + name for help text. Files are only shown as their name without the + path. ``python`` is only shown for modules, and the full path to + ``sys.executable`` is not shown. + + :param path: The Python file being executed. Python puts this in + ``sys.argv[0]``, which is used by default. + :param _main: The ``__main__`` module. This should only be passed + during internal testing. + + .. versionadded:: 8.0 + Based on command args detection in the Werkzeug reloader. + + :meta private: + """ + if _main is None: + _main = sys.modules["__main__"] + + if not path: + path = sys.argv[0] + + # The value of __package__ indicates how Python was called. It may + # not exist if a setuptools script is installed as an egg. It may be + # set incorrectly for entry points created with pip on Windows. + # It is set to "" inside a Shiv or PEX zipapp. + if getattr(_main, "__package__", None) in {None, ""} or ( + os.name == "nt" + and _main.__package__ == "" + and not os.path.exists(path) + and os.path.exists(f"{path}.exe") + ): + # Executed a file, like "python app.py". + return os.path.basename(path) + + # Executed a module, like "python -m example". + # Rewritten by Python from "-m script" to "/path/to/script.py". + # Need to look at main module to determine how it was executed. + py_module = t.cast(str, _main.__package__) + name = os.path.splitext(os.path.basename(path))[0] + + # A submodule like "example.cli". + if name != "__main__": + py_module = f"{py_module}.{name}" + + return f"python -m {py_module.lstrip('.')}" + + +def _expand_args( + args: cabc.Iterable[str], + *, + user: bool = True, + env: bool = True, + glob_recursive: bool = True, +) -> list[str]: + """Simulate Unix shell expansion with Python functions. + + See :func:`glob.glob`, :func:`os.path.expanduser`, and + :func:`os.path.expandvars`. + + This is intended for use on Windows, where the shell does not do any + expansion. It may not exactly match what a Unix shell would do. + + :param args: List of command line arguments to expand. + :param user: Expand user home directory. + :param env: Expand environment variables. + :param glob_recursive: ``**`` matches directories recursively. + + .. versionchanged:: 8.1 + Invalid glob patterns are treated as empty expansions rather + than raising an error. + + .. versionadded:: 8.0 + + :meta private: + """ + from glob import glob + + out = [] + + for arg in args: + if user: + arg = os.path.expanduser(arg) + + if env: + arg = os.path.expandvars(arg) + + try: + matches = glob(arg, recursive=glob_recursive) + except re.error: + matches = [] + + if not matches: + out.append(arg) + else: + out.extend(matches) + + return out diff --git a/venv/lib/python3.12/site-packages/dns/__init__.py b/venv/lib/python3.12/site-packages/dns/__init__.py new file mode 100644 index 0000000..d30fd74 --- /dev/null +++ b/venv/lib/python3.12/site-packages/dns/__init__.py @@ -0,0 +1,72 @@ +# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license + +# Copyright (C) 2003-2007, 2009, 2011 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation 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 NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM 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. + +"""dnspython DNS toolkit""" + +__all__ = [ + "asyncbackend", + "asyncquery", + "asyncresolver", + "btree", + "btreezone", + "dnssec", + "dnssecalgs", + "dnssectypes", + "e164", + "edns", + "entropy", + "exception", + "flags", + "immutable", + "inet", + "ipv4", + "ipv6", + "message", + "name", + "namedict", + "node", + "opcode", + "query", + "quic", + "rcode", + "rdata", + "rdataclass", + "rdataset", + "rdatatype", + "renderer", + "resolver", + "reversename", + "rrset", + "serial", + "set", + "tokenizer", + "transaction", + "tsig", + "tsigkeyring", + "ttl", + "rdtypes", + "update", + "version", + "versioned", + "wire", + "xfr", + "zone", + "zonetypes", + "zonefile", +] + +from dns.version import version as __version__ # noqa diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8aab5b4166eb4da5e8fa1437c834a880b1ecb4c3 GIT binary patch literal 725 zcmXw1y^a$x5MF0@v)P1{h~|nbXd*#YInYT6M0NEwbZJ(OJxQ#1?LFHGx2f(esA%YT z4N7=}DQKwZfRiY$Vs&WDx9M~o(fa!3`}X57ihi`p|1~&~`!gaRA{i~C zO(fYTwq$Z3V?C(iWx}E+nWe|a()hXwTWij*uHT&pYxPcr*#aMN-x#stVh2XzxYKU- z;2NBE+<~|H01netZ~&Nh;%E)vkYaieIPN9a8;_Hoho%OP2`nyO5|RxwFu~cn!5nS{ z)InK;=~{F0$yHVD17B&tL>UN#^@02dFEOjYd%mF~!z;j1i&6=?WUQndY%N-u_Bkgv zdnGV+z0FA;IF36ZH1{4y9ryG`-mx}MGE(W_s77L^Y9|h;Xb;_^sUsF}=)ozjG4A|> zrH-j1c};@j#&gltlmxFfWFp)_Ho$-XJWeNAZrW^Meo3;BtF#N@qlfbs3x zVbZlYZ?X3qwREjC{A|ZQW9}J=>sg9fSxPI)vL#$=BJB|7r>NaEoRsBBFU!x-eH-E7 zs@U2J3SsrCDa4kC&=dzSheE4W(f^ef^TkEsRe^i n+-rF2UQGQ2U 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/_features.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/_features.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3c8246427de0d6ce8cb090f312e1838e2618c84 GIT binary patch literal 3294 zcmai0O>7&-6`uX2NG`=6{acc?wuIUg6iT-1{s0nFC$U=_sRC3g3=1u&)$UMSiQHvo zhmt6e087pX!ZW+Su)dg_g}bO?0nn-!N*R2LoW z&YOAj=FQA|-+MEEjYL8S%IAOnd+tv`g#O7oehIW1M}L9FGxSqrAcM=J1*6<#e%dT732lE5LgIs$PjRcA>u$*G^EGkLeP-CehB&j=!Z?TpkN7i;82zy0R1>( zbW8&^f^E4F$aQ-CFb)}_!DVGb0bbOIEOX1q===?5M3*@u27es>1pHl}$_p`1FRaJ( zwzYRN?PV_6^Dm%iHYvE`d!|lZ@qN>wt~6gN=CK=C!^AOd%asYHC1U9u6K)k|4*~uo z9=Oj?1+APPZ3!9N%NDl^d#v&m?mhJLcM+-z2EW2)Uno@a87s2iI-`d07*&Lq{3)lc zSq!2IzXJZcSZLViR779eryP2ES41@0mf-!7IW&r>;`7H4>EzHO#QhK1b+jQoLhD>o zoIT-EF8YRz*iO6PKl&C_%tq8#i&k2vXfvR(hUQR`n@#er>`-D_%dTvg%O-V_oXc%E z%#!NK-N~F?z>~V2Un)=PIgQft`WS# zGnvU^4cv&GRIz;0F`d^6ONN%-yqclR+nFq}3mJ+xXlgod>ssDPL*rDY6IOPZscPuO zmEW%YuKHPZcYL~rj(~-d9j@dQ^Cp02#WpQB2n!Qx+j++gQf(El=S;wotQCvcGF-v5 zs2jYyq2nTD(2~5xlM-S6c0+9aOi?2gCWEBkY_na(vq+{$aMs*#7+h3V2-}?-yLc3G z8Q2UN&M%>=p+>y_+3?51+s=M`wDzD8OVsW+dPZKOfRv~Q8+`-M)<0f9RC*4Skv;g- z>EE0C%8!l&zCTn~nz9f~G}z$Ko-*|5-1DLR;nWw(WHSu2k7G!QZw>5+2Y1E6ua6}Z z8+eVlVB$+9ei)AV15F87Cr$$PvV$(&xyJ8Y3*JdfJ2M>AZkXT?OD4euY|(GPNCbH= z)INvC{|~jF0ko=Il@B7?2H6VJaiOgEDeWA;au%>cMW}IQrsXV*1V2cs;yG?RbgEKC ztVpY{DuJ100RpMRR}O(Bus|yFXCbqR?>j2ZVfEa})odxBHS>9Fyd4TL*Un!UFO(dr zE@9QqdQ-DDDVz#2;TGW2Gp*F+Y1rh`Gxz3`GGT@XTosBop%5-^M*&mK(5U92kMzT^ zhZqnYVWNSSIyX=-EwfN6oT4;HGk#`-W^2e{0A^e8!Pw~wW%L}@SIIRP?}OhdLsdh~ z1nM6>=)1nxcl|~8eqU-&?5_Q={;+g%kC+ z8(sZP0V#vW9VmX~dEb64S-XE2Q(LV@Ahz}WzXgUG(e6e#`t@-HDLsHgsprdRcT)g* z;>2O~UthV?i*|a0cdiOMS2(C$IiqW^O*hcW5pTxdA7SpZ^GWOtDZGN(7~v}1N09R8 z5N)IHO-cXU48FqGLI!u1CM)RlqB8iZP~qY7fb9eJ+Q>ai?1Xz7HMlBZ%;H42CNAoz z2#{Qb&DVgq@zx>7Rc%c(^V(7#x3OjdAhAOo)y%3|KCjwdn$q$axQg>)1P>ksiQ6~&XW>eTx3hJAn>)o7TWuo@boiH zi;MRlC%42+s)CedQx$dr(|sHR*PBo<9S81@l-B|1i{v`E&J~t1bt9Q`(dKxtg!G$a z|cN%@8uaN-xID8l# z-d_D8I^Kx)*583d+6(}@*@5~79^b2f_oELQ;YjU1VR&`}*kWEZ^mf7nE(52RHhZA~ zY4Z)RO7fd8@7YAho&y{T_oKrS%Natb&2r6nOaFMwh4VQuKNvk{8_W?!Vy~GD6dJfr zUAM`qM(h$eje$h7%Brq8_|!A&ISe3ar=Zk1 (`vstWDa}7^0$9fP0Ft$B8o8&x4 zCV9_aDaqk?Ue@;FU?07}`j%lQLTkU%-^SVr8XuTbnO6!`}l+e2ebIm``h z>rDjYIX+^fIn=|&8ga)aY`A|Tsi zM~pPTBXgIxZZ#2*=iMH80VB|B_M+I;-Oj6Sc)Fns?S_XNk%9W`X{Gen)BlbJukp1= Zoo>Cm-My`UGO#tf7acuDL5}y%_8;JZC*lAA literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/_immutable_ctx.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/_immutable_ctx.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55d20f6614b0428b6c08229688578b981390242f GIT binary patch literal 3220 zcmb_eT}&KR6ux(6_K%%imcK4-w`FVjTcND9RZ~(+wb+K%@KU33YaExI0k&Os@y;v- zvLr-FT#~9Kv1uD0N)w|r7}HnZjF0-l3Jo|m24i1*qb{+PC(oJL85UxrMsG6b{+)a0 z{C?+t=krw%Xy1PG)6kb5LVm%C)r8WZ_$Cal6Pd^~MPg=8W0WHbxoGiNXP}okng2pq z;LN2M&yh1kc1#o5X|D;gYnsFyvKy##oJKsqB6FW!Cnbl6vs%1ArAW!N1Yc5Q6VuZlNcw{7%y`%Ax{RV%ul;x4r3(1$eAZ&H0C-_ zA`ZPm>a~71QD+as!^yE^I-E$wRrL-@@MV8QZE;>40&0u?Ufnwe=>*@TUqUoMj$48Wx8Ot!;0f!X^Jd&ztc!4BE1YJ;- z)POFe%d0itdpl!wA5LiigADsm-&|q0F!nO=MF2PMA~fiS0!=KoVA1-~_#5 zcx(U$|GURH%VU;5(O(Sm2%?K&Vlgig5s4278eLXWC8P7K5;p=!s)48|C>WX5;0jt` zhjr|uA>?iY@=vXEPi$hfz#nnzZb?EXOA>^?G@OyMkZY1IO42*oc&fDH0x>fQScVW# zQyEQ_B*R;VP`a-a7YQYmg4m&^@EFl2P0=x=84}c4MPVOLlOKeJb&*unO!aOEg5%I5 zQE)V`6Cj3Q2798E6>y-;bEjc~$!(-Copw)B&E~sU?rY;1Av47CMvhAMevU%QbA5>t zlhI*9Ho#Z>49MRHM-BjRH*i|lPjVi@5lq`8B<-j4LrTj5!A^HLQfiRcCDAcZ4*m=7 z%VdmygIuPd0z;i5yb)hI4>_zmm;xOpahwgRy7SUyqj!%mWi*+|syZF8GuZHu&ZP$? z_LgNN0&U0fR3;HmsV6$p1C8(twFd|WSk3U z3`U!QK(Y{??b&vhQH?D=voE{L4yhS3Nim|awiAP2}Fu-!I^7xXT4U;Y7L55 zWi#f0Cx-KNau;g2Z7zR_4{Ev1=Q3)#`#hKeYCH51$7axeNtDHPBBe6aXY0CzA9MH3WJXRMnhF5)v~BK!ow zp!%6tod7Pi4hTf^gFxM^s}Km!H7*7^=bM)TohDi$_Ww5}SQM@(slo6%)(K=gOc?7E zjZYSM%hNr0*b=&S_w1QMZR>pFV(qcJ%}ce%%-Z8&LHtiW62wuq7YQaXL(Ax{p}2~r z(G&;E2FQnnQRl&N1`M1uEeY=wbS1tIy5Lh^0P@E)S*zcZ?_KkSiX8PFF7m{`ZyiSu zog@&PRd4hySGKKHg<-d<3wEmyY#1XK3ACG5eHN~Xc#Kk{LkS#a8K`@Z?a?_?Nvc_ZvFq)s<*>A=MtI`?~%=;`{n>xQ~P zV6eWQ><--$Z;E;0if=X4uo7x7gxd4MvU~UHo_)7EZg#*%<*K)G>YV8jm+nnu(wZ`+ zjmDYEnn50xnX>6VZoVN-F!Tgwj`|#sb)HgM^b=b1 uBk`~K8}pY|{EY>F<6_hCua#y0D^uP_&KlbMkO29uy@L9SdqOl|-1{dAbf}vE literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/_no_ssl.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/_no_ssl.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da31767034c530ea0be6a8aed48ac640f579c971 GIT binary patch literal 3141 zcmbtW&5zqe6rZsj$NAjve$WL4v@Oj-H?$QMh=K&wQYuwJ+O4RRi{WTNpPV8(8RCFXizxmC_dvE6bp8aE? zkR{N5`2IotL5h$+u`_7Um#zOZytWA=j5bLP-%69Vl$t_8uc-<-Pnfzzn8wK!BU+Pq z-B!a+EhT5BV5ZLXA)1zG8c-vF8WPO_noXb?iRJ*!C(x`!3xF1HE0L|7#Kr&{A7Xhn z!E>t$o7~cBg)S{mJ;ce?G7Z#^ovt0O3?+>^{c%vX2`4oQp(>2l)UHxigY2b?%U^Ke zHC*Rm9;Q^wT40!_W3{+x23gZ=xvbMfJ#U&{cC2PJl7@8~C(Tnkq>mP!o~zW|7O&WD zbER9c>z40#D;wO|s5BcZm3G&!yUyu_lcy?P!{;a3mc4GRaj(K0uVOl`>3PkCc30p% zNs$J{L$O8f7RSFnFVHnP^8Da%I1Ua@L8&yy zD5=#nrfrd03a&OCWWj}VuH*AfziJN>92OJYN`q(kKzQ1eTC&PVknNl;~^QgNMBJ?^ZhIZp@Rr% zk8^BzhE0pK{%4>h*g?4p9{^;C#+Mkxtxrn245tGF$_+9Y&vAt0$`B$ zqqGgQ71wQw)942JFVH>lK8g=ee2C%`6#ED*>4V}pJldBcT+lm(-%zNL=y!XDxH}fTxiUVn%KmDkr zH2{98O=Ph#7(A8WJ#c{wJ+Pyq=E17Aqe0;6SjWbp%ybQ`VZ((AN3pC;%gVChns1FA zGlY5cTeaNGLt$>!YaSM;fhj460#VHC)OVn|ADbbgF4y2(Iz|5)%BAtEoF?FM%qPKl`gI{wp1yXusRa$Fd3y}9Da zYnz8t#BbOhadRU)>>;9Tg8*5v&4LF>hAGIKMs=F&gyZo zYfN8Ef*a}fc5A!6+uA)@JxeW&QqW&1I=DI8oqhN%yV6ar^b(nI=|J*=JfiC51>#{% z3QLs|!q^J}k4{K9obn@}S%uTmT~0iy25#NMMS5Fq)&F3Rr0eSt!T|y>{vF)uqa>W^ mCDWZk>seoy_@@~^E_80S9(UNu*1N-QV*abSaAC@uVdp=4AkY8+ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/_trio_backend.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/_trio_backend.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1d225d939f56055d9fbeaf30659c6668fb523db GIT binary patch literal 13610 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/asyncquery.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/asyncquery.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a63e7a28514572ac81fdfcb289598b6a42d50a39 GIT binary patch literal 38582 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/dns/__pycache__/asyncresolver.cpython-312.pyc b/venv/lib/python3.12/site-packages/dns/__pycache__/asyncresolver.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..607123242df83223ff0612868a506afbbe6d5707 GIT binary patch literal 21131 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