diff --git a/.gitattributes b/.gitattributes index 123e82f8bc0c14f7ecd4c5b0304a51db9d8f9f4d..8c1e1a93a92164f628766ae27c9aaa785c0ac74d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -55,3 +55,54 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text .venvs/kimodo/lib/python3.10/site-packages/_brotli.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text .venvs/kimodo/lib/python3.10/site-packages/accelerate/__pycache__/accelerator.cpython-310.pyc filter=lfs diff=lfs merge=lfs -text .venvs/kimodo/lib/python3.10/site-packages/accelerate/utils/__pycache__/dataclasses.cpython-310.pyc filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libSvtAv1Enc.4.1.0.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavcodec.62.28.101.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavdevice.62.3.101.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavfilter.11.14.101.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavformat.62.12.101.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavutil.60.26.101.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libdav1d.7.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libmp3lame.0.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libopencore-amrnb.0.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libopencore-amrwb.0.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libopus.0.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libswresample.6.3.101.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libswscale.9.5.101.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libvpx.12.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libwebp.7.2.0.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libx264.165.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libx265.216.dylib filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/audio/codeccontext.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/audio/fifo.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/audio/format.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/audio/frame.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/audio/layout.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/audio/resampler.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/audio/stream.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/bitstream.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/buffer.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/codec/codec.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/codec/context.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/codec/hwaccel.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/container/core.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/container/input.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/container/output.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/container/pyio.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/container/streams.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/device.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/dictionary.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/error.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/filter/context.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/filter/graph.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/filter/link.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/format.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/frame.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/index.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/logging.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/packet.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/plane.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/encparams.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/motionvectors.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/sidedata.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text +.venvs/kimodo/lib/python3.10/site-packages/av/stream.cpython-310-darwin.so filter=lfs diff=lfs merge=lfs -text diff --git a/.venvs/kimodo/lib/python3.10/site-packages/antlr4_python3_runtime-4.9.3.dist-info/WHEEL b/.venvs/kimodo/lib/python3.10/site-packages/antlr4_python3_runtime-4.9.3.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..13013d7b49be5a84c3ee5931b06285b9553cf5d0 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/antlr4_python3_runtime-4.9.3.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (84.0.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venvs/kimodo/lib/python3.10/site-packages/antlr4_python3_runtime-4.9.3.dist-info/top_level.txt b/.venvs/kimodo/lib/python3.10/site-packages/antlr4_python3_runtime-4.9.3.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..3dee19f3e13c2cbbb49b818e6ac67c63f77b865f --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/antlr4_python3_runtime-4.9.3.dist-info/top_level.txt @@ -0,0 +1 @@ +antlr4 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/INSTALLER b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/METADATA b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..13734ecac7501953843b99eef680987bad705350 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/METADATA @@ -0,0 +1,108 @@ +Metadata-Version: 2.4 +Name: anyio +Version: 4.15.1 +Summary: High-level concurrency and networking framework on top of asyncio or Trio +Author-email: Alex Grönholm +License-Expression: MIT +Project-URL: Documentation, https://anyio.readthedocs.io/en/latest/ +Project-URL: Changelog, https://anyio.readthedocs.io/en/stable/versionhistory.html +Project-URL: Source code, https://github.com/agronholm/anyio +Project-URL: Issue tracker, https://github.com/agronholm/anyio/issues +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Framework :: AnyIO +Classifier: Typing :: Typed +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: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3.15 +Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta +Requires-Python: >=3.10 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11" +Requires-Dist: idna>=2.8 +Requires-Dist: typing_extensions>=4.16.0; python_version < "3.15" +Provides-Extra: trio +Requires-Dist: trio>=0.32.0; extra == "trio" +Dynamic: license-file + +.. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg + :target: https://github.com/agronholm/anyio/actions/workflows/test.yml + :alt: Build Status +.. image:: https://coveralls.io/repos/github/agronholm/anyio/badge.svg?branch=master + :target: https://coveralls.io/github/agronholm/anyio?branch=master + :alt: Code Coverage +.. image:: https://readthedocs.org/projects/anyio/badge/?version=latest + :target: https://anyio.readthedocs.io/en/latest/?badge=latest + :alt: Documentation +.. image:: https://badges.gitter.im/gitterHQ/gitter.svg + :target: https://gitter.im/python-trio/AnyIO + :alt: Gitter chat +.. image:: https://tidelift.com/badges/package/pypi/anyio + :target: https://tidelift.com/subscription/pkg/pypi-anyio + :alt: Tidelift + +AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or +Trio_. It implements Trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony +with the native SC of Trio itself. + +Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or +Trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full +refactoring necessary. It will blend in with the native libraries of your chosen backend. + +To find out why you might want to use AnyIO's APIs instead of asyncio's, you can read about it +`here `_. + +Documentation +------------- + +View full documentation at: https://anyio.readthedocs.io/ + +Features +-------- + +AnyIO offers the following functionality: + +* Task groups (nurseries_ in trio terminology) +* High-level networking (TCP, UDP and UNIX sockets) + + * `Happy eyeballs`_ algorithm for TCP connections (more robust than that of asyncio on Python + 3.8) + * async/await style UDP sockets (unlike asyncio where you still have to use Transports and + Protocols) + +* A versatile API for byte streams and object streams +* Inter-task synchronization and communication (locks, conditions, events, semaphores, object + streams and futures) +* Worker threads +* Subprocesses +* Subinterpreter support for code parallelization (on Python 3.13 and later) +* Asynchronous file I/O (using worker threads) +* Signal handling +* Asynchronous versions of the functools_ and itertools_ modules + +AnyIO also comes with its own pytest_ plugin which also supports asynchronous fixtures. +It even works with the popular Hypothesis_ library. + +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _Trio: https://github.com/python-trio/trio +.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency +.. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning +.. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs +.. _pytest: https://docs.pytest.org/en/latest/ +.. _functools: https://docs.python.org/3/library/functools.html +.. _itertools: https://docs.python.org/3/library/itertools.html +.. _Hypothesis: https://hypothesis.works/ + +Security contact information +---------------------------- + +To report a security vulnerability, please use the `Tidelift security contact`_. +Tidelift will coordinate the fix and disclosure. + +.. _Tidelift security contact: https://tidelift.com/security diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/RECORD b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..6831feafe63eeb25c4a98b0d857dce6334b5f9c1 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/RECORD @@ -0,0 +1,55 @@ +anyio-4.15.1.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +anyio-4.15.1.dist-info/METADATA,sha256=BDrstXGvcYnopsUo9djjJUXxss36r3YdUebVtn6jyoE,4733 +anyio-4.15.1.dist-info/RECORD,, +anyio-4.15.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio-4.15.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91 +anyio-4.15.1.dist-info/entry_points.txt,sha256=_d6Yu6uiaZmNe0CydowirE9Cmg7zUL2g08tQpoS3Qvc,39 +anyio-4.15.1.dist-info/licenses/LICENSE,sha256=U2GsncWPLvX9LpsJxoKXwX8ElQkJu8gCO9uC6s8iwrA,1081 +anyio-4.15.1.dist-info/top_level.txt,sha256=QglSMiWX8_5dpoVAEIHdEYzvqFMdSYWmCj6tYw2ITkQ,6 +anyio/__init__.py,sha256=OfroYRjIUw_JFj0Zwh1JAaajMQg4VqtoAt0XoOHDRwE,7602 +anyio/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/_backends/_asyncio.py,sha256=Vqy7O1ymmEpxTiPzehSuBCVTziGALNV-BbsIRaKXsg4,107767 +anyio/_backends/_trio.py,sha256=Gs8XzX0IeBvje8IEZtJmpNylKCUAChKv7I_YZi3wlmg,45715 +anyio/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/_core/_asyncio_selector_thread.py,sha256=2PdxFM3cs02Kp6BSppbvmRT7q7asreTW5FgBxEsflBo,5626 +anyio/_core/_concurrency_utils.py,sha256=kvRXMrnDqgQTBkZc-f279-lBnZM4YbSToXqhnAUCfSc,4522 +anyio/_core/_contextmanagers.py,sha256=YInBCabiEeS-UaP_Jdxa1CaFC71ETPW8HZTHIM8Rsc8,7215 +anyio/_core/_eventloop.py,sha256=ByZUeJD9alMfcyTseRo5IzTO0IltEul_Gyq9iqSjqDk,6658 +anyio/_core/_exceptions.py,sha256=B4ZhLkBQT4tI6vShv4-GJui492IJsoIDTw1FQJEkmAI,5618 +anyio/_core/_fileio.py,sha256=q--ft9h5Y_FRLzhAnoYrXf3tOuZ8ZyIN2A9mCfMbZRc,34163 +anyio/_core/_futures.py,sha256=YQq5uhnCnKQMQD62tOLfzEm7dUXY3GQorJdH6ghfgIQ,6177 +anyio/_core/_resources.py,sha256=Ld5duCe9NwgPfI7EIOh3CMIBYQ7rq6ZQEa7CFS0u9Ec,450 +anyio/_core/_signals.py,sha256=mjTBB2hTKNPRlU0IhnijeQedpWOGERDiMjSlJQsFrug,1016 +anyio/_core/_sockets.py,sha256=C8b-3w5H9n0Fo4g7X8WJvk0m6CGh6NVAAFtieBGHK3g,35289 +anyio/_core/_streams.py,sha256=FczFwIgDpnkK0bODWJXMpsUJYdvAD04kaUaGzJU8DK0,1806 +anyio/_core/_subprocesses.py,sha256=TMBuGNILz-47w5t9vayJ1GpCpDT6LxsMohJDFCewIRc,7938 +anyio/_core/_synchronization.py,sha256=ggsd74QPd846faA2HQeT07ONQ5HQ1S78gy8mvB3qCrw,21658 +anyio/_core/_tasks.py,sha256=J2dhM_Jq3Grsfg_enNWpRz2SxfM15uLt-pEfvoQq3uA,15391 +anyio/_core/_tempfile.py,sha256=IgoBCFaPhrFvsF9KHcCcrnCRn-UqMfw0WRdSDWgxAiI,19726 +anyio/_core/_testing.py,sha256=ZRucLh5kqhiFWAJMMbd8yZ6uIvz0rq3WwL72ldosxAU,2340 +anyio/_core/_typedattr.py,sha256=gt9Dzn-IL0QW14aHPDD8PAtq95FYKNNupHyf37xZGPQ,2607 +anyio/_lazyimport.py,sha256=LCjnYanIfYE9G-Dv_EsqNLDuVRppNicWZjCTfS9-sJU,6082 +anyio/abc/__init__.py,sha256=XIVvCI2c6oyaOcqXe94k2rzU5_s7-ZNnlxGTk34H4T0,3050 +anyio/abc/_eventloop.py,sha256=0-UwgB351rkocCt8m3fjTmcNx2Fxn52nUoujG5DzMvU,11127 +anyio/abc/_resources.py,sha256=hrb0LXUgHSp3i8G6W8JJchdzKXgg1m0T-L7udXmv2y0,900 +anyio/abc/_sockets.py,sha256=jQWnPX7iDIDyvyvVkbuFhjezw4oI2WggzbW6Vl2-eqs,13162 +anyio/abc/_streams.py,sha256=IMeOPPutFXRbyeAutxV1HJe7FUQZYp2qKrJJpP1DCv0,7620 +anyio/abc/_subprocesses.py,sha256=CDqU1p0HuBS0pXUzBW_hbfhihTJRM67n__7qZmoPU84,2123 +anyio/abc/_tasks.py,sha256=HBPpT72aOy0bwMAxBCKmnXsR328wCYRjYm3iaVw2fQo,7070 +anyio/abc/_testing.py,sha256=A9Y8RAClIPn1A0aNHBN8YoaRy_MKOpbFGEbTHAXgB6U,2145 +anyio/from_thread.py,sha256=iZZbfcNySi4wAcx1_cxAQE4J0YZC88qRMIYqQ6SF9sc,19415 +anyio/functools.py,sha256=T4JS8IXq-x1S0Lbo2owF8l9fza2KypO147QLeyz4cjs,11797 +anyio/itertools.py,sha256=TS7_UCa2Y3cggtaLlWHbjOyf6QhPfQUdPduNW-ddXTE,16255 +anyio/lowlevel.py,sha256=QyWkvfetbCH0mHkqv1yxGwOaiZ7fyxhpZkHWOXPV5JU,6280 +anyio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/pytest_plugin.py,sha256=oLXyOstvRZW3VVbqIODhZsPt4jSz2UGh44YeG2nJXx0,14516 +anyio/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/streams/buffered.py,sha256=u7hCD8SNrYHcutG6K5wEiy1F88-pizgjvEFM22Kq2Cw,6746 +anyio/streams/file.py,sha256=6jujI2m-QJITqqKFamrupX_DNsU7y2Fz3omLZxOLuY0,4524 +anyio/streams/memory.py,sha256=rpxNzDMag9PCOynBqN64KyuAfAPB41vFyRP-Npo-lE0,10793 +anyio/streams/stapled.py,sha256=UJ2Rgt0NUmM1VovRNOoZoic960lcbDjb3vhfZg3b_Z0,4845 +anyio/streams/text.py,sha256=BcVAGJw1VRvtIqnv-o0Rb0pwH7p8vwlvl21xHq522ag,5765 +anyio/streams/tls.py,sha256=fqLmxvULFO9rvxGyJ33VPrOIN7E3eiU1q90BkmKigQ8,15818 +anyio/to_interpreter.py,sha256=ATnoT6kei9wDmQZtXTA3SsY4CK7XKr8Ta4_x5i6pbTw,7100 +anyio/to_process.py,sha256=p6n8j0nreJUl1DEkR_9PL6KRoKtNqFxORTn8qZBvn94,9850 +anyio/to_thread.py,sha256=8MPVrrQq8ttGvV21cSd68rMto5bhxJ0-i9CupXag5Gs,2750 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/REQUESTED b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/WHEEL b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..13013d7b49be5a84c3ee5931b06285b9553cf5d0 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (84.0.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/entry_points.txt b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..44dd9bdc3039122cc98014c1439ca254313fd014 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[pytest11] +anyio = anyio.pytest_plugin diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/licenses/LICENSE b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..104eebf5a3002fccdaceef3a4cb936173c1c2035 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/licenses/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 Alex Grönholm + +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/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/top_level.txt b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..c77c069ecc9b7f8b1f97dbcfec905725db0253a8 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio-4.15.1.dist-info/top_level.txt @@ -0,0 +1 @@ +anyio diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/__init__.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..30a3a16151bef66d432cb8e06ad858c3ada002fd --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/__init__.py @@ -0,0 +1,154 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ._lazyimport import ( + fix_package_names, + install_lazy_importer, + set_deprecated_aliases, +) + +if TYPE_CHECKING or not install_lazy_importer(): + from ._core._concurrency_utils import amap as amap + from ._core._concurrency_utils import as_completed as as_completed + from ._core._concurrency_utils import gather as gather + from ._core._contextmanagers import ( + AsyncContextManagerMixin as AsyncContextManagerMixin, + ) + from ._core._contextmanagers import ContextManagerMixin as ContextManagerMixin + from ._core._eventloop import current_time as current_time + from ._core._eventloop import get_all_backends as get_all_backends + from ._core._eventloop import get_available_backends as get_available_backends + from ._core._eventloop import get_cancelled_exc_class as get_cancelled_exc_class + from ._core._eventloop import run as run + from ._core._eventloop import sleep as sleep + from ._core._eventloop import sleep_forever as sleep_forever + from ._core._eventloop import sleep_until as sleep_until + from ._core._exceptions import BrokenResourceError as BrokenResourceError + from ._core._exceptions import BrokenWorkerInterpreter as BrokenWorkerInterpreter + from ._core._exceptions import BrokenWorkerProcess as BrokenWorkerProcess + from ._core._exceptions import BusyResourceError as BusyResourceError + from ._core._exceptions import ClosedResourceError as ClosedResourceError + from ._core._exceptions import ConnectionFailed as ConnectionFailed + from ._core._exceptions import DelimiterNotFound as DelimiterNotFound + from ._core._exceptions import EndOfStream as EndOfStream + from ._core._exceptions import FutureAlreadyFinished as FutureAlreadyFinished + from ._core._exceptions import FutureCancelled as FutureCancelled + from ._core._exceptions import FutureFailed as FutureFailed + from ._core._exceptions import FutureNotFinished as FutureNotFinished + from ._core._exceptions import IncompleteRead as IncompleteRead + from ._core._exceptions import NoEventLoopError as NoEventLoopError + from ._core._exceptions import RunFinishedError as RunFinishedError + from ._core._exceptions import TaskCancelled as TaskCancelled + from ._core._exceptions import TaskFailed as TaskFailed + from ._core._exceptions import TaskNotFinished as TaskNotFinished + from ._core._exceptions import ( + TypedAttributeLookupError as TypedAttributeLookupError, + ) + from ._core._exceptions import WouldBlock as WouldBlock + from ._core._fileio import AsyncFile as AsyncFile + from ._core._fileio import Path as Path + from ._core._fileio import open_file as open_file + from ._core._fileio import wrap_file as wrap_file + from ._core._futures import Future as Future + from ._core._resources import aclose_forcefully as aclose_forcefully + from ._core._signals import open_signal_receiver as open_signal_receiver + from ._core._sockets import TCPConnectable as TCPConnectable + from ._core._sockets import UNIXConnectable as UNIXConnectable + from ._core._sockets import as_connectable as as_connectable + from ._core._sockets import connect_tcp as connect_tcp + from ._core._sockets import connect_unix as connect_unix + from ._core._sockets import ( + create_connected_udp_socket as create_connected_udp_socket, + ) + from ._core._sockets import ( + create_connected_unix_datagram_socket as create_connected_unix_datagram_socket, + ) + from ._core._sockets import create_tcp_listener as create_tcp_listener + from ._core._sockets import create_udp_socket as create_udp_socket + from ._core._sockets import ( + create_unix_datagram_socket as create_unix_datagram_socket, + ) + from ._core._sockets import create_unix_listener as create_unix_listener + from ._core._sockets import getaddrinfo as getaddrinfo + from ._core._sockets import getnameinfo as getnameinfo + from ._core._sockets import notify_closing as notify_closing + from ._core._sockets import wait_readable as wait_readable + from ._core._sockets import wait_socket_readable as wait_socket_readable + from ._core._sockets import wait_socket_writable as wait_socket_writable + from ._core._sockets import wait_writable as wait_writable + from ._core._streams import ( + create_memory_object_stream as create_memory_object_stream, + ) + from ._core._subprocesses import open_process as open_process + from ._core._subprocesses import run_process as run_process + from ._core._synchronization import CapacityLimiter as CapacityLimiter + from ._core._synchronization import ( + CapacityLimiterStatistics as CapacityLimiterStatistics, + ) + from ._core._synchronization import Condition as Condition + from ._core._synchronization import ConditionStatistics as ConditionStatistics + from ._core._synchronization import Event as Event + from ._core._synchronization import EventStatistics as EventStatistics + from ._core._synchronization import Lock as Lock + from ._core._synchronization import LockStatistics as LockStatistics + from ._core._synchronization import ResourceGuard as ResourceGuard + from ._core._synchronization import Semaphore as Semaphore + from ._core._synchronization import SemaphoreStatistics as SemaphoreStatistics + from ._core._tasks import TASK_STATUS_IGNORED as TASK_STATUS_IGNORED + from ._core._tasks import CancelScope as CancelScope + from ._core._tasks import TaskHandle as TaskHandle + from ._core._tasks import create_task_group as create_task_group + from ._core._tasks import current_effective_deadline as current_effective_deadline + from ._core._tasks import fail_after as fail_after + from ._core._tasks import fail_at as fail_at + from ._core._tasks import move_on_after as move_on_after + from ._core._tasks import move_on_at as move_on_at + from ._core._tempfile import NamedTemporaryFile as NamedTemporaryFile + from ._core._tempfile import SpooledTemporaryFile as SpooledTemporaryFile + from ._core._tempfile import TemporaryDirectory as TemporaryDirectory + from ._core._tempfile import TemporaryFile as TemporaryFile + from ._core._tempfile import gettempdir as gettempdir + from ._core._tempfile import gettempdirb as gettempdirb + from ._core._tempfile import mkdtemp as mkdtemp + from ._core._tempfile import mkstemp as mkstemp + from ._core._testing import TaskInfo as TaskInfo + from ._core._testing import get_current_task as get_current_task + from ._core._testing import get_running_tasks as get_running_tasks + from ._core._testing import wait_all_tasks_blocked as wait_all_tasks_blocked + from ._core._typedattr import TypedAttributeProvider as TypedAttributeProvider + from ._core._typedattr import TypedAttributeSet as TypedAttributeSet + from ._core._typedattr import typed_attribute as typed_attribute + + # ruff: isort: off + from . import ( + abc as abc, + ) + from . import ( + from_thread as from_thread, + ) + from . import ( + functools as functools, + ) + from . import ( + itertools as itertools, + ) + from . import ( + lowlevel as lowlevel, + ) + from . import ( + to_interpreter as to_interpreter, + ) + from . import ( + to_process as to_process, + ) + from . import ( + to_thread as to_thread, + ) + + fix_package_names() + set_deprecated_aliases( + { + "BrokenWorkerIntepreter": "anyio.BrokenWorkerInterpreter", + } + ) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/__pycache__/__init__.cpython-310.pyc b/.venvs/kimodo/lib/python3.10/site-packages/anyio/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35742dfba90576249fcfdbe9de28d90354b006cd Binary files /dev/null and b/.venvs/kimodo/lib/python3.10/site-packages/anyio/__pycache__/__init__.cpython-310.pyc differ diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/__pycache__/_lazyimport.cpython-310.pyc b/.venvs/kimodo/lib/python3.10/site-packages/anyio/__pycache__/_lazyimport.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1563e919d1c4dbaa1ed865f2e087a6b2deee31a Binary files /dev/null and b/.venvs/kimodo/lib/python3.10/site-packages/anyio/__pycache__/_lazyimport.cpython-310.pyc differ diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_backends/__init__.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_backends/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_backends/_asyncio.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_backends/_asyncio.py new file mode 100644 index 0000000000000000000000000000000000000000..7eba257c962819dbf8192b51185a4f5faa4746fd --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_backends/_asyncio.py @@ -0,0 +1,3201 @@ +from __future__ import annotations + +import array +import asyncio +import concurrent.futures +import math +import os +import socket +import sys +import threading +import weakref +from asyncio import ( + AbstractEventLoop, + CancelledError, + all_tasks, + create_task, + current_task, + get_running_loop, + sleep, +) +from asyncio.base_events import _run_until_complete_cb # type: ignore[attr-defined] +from collections import OrderedDict, deque +from collections.abc import ( + AsyncGenerator, + AsyncIterator, + Awaitable, + Callable, + Collection, + Coroutine, + Iterable, + Sequence, +) +from concurrent.futures import Future +from contextlib import AbstractContextManager, suppress +from contextvars import Context, copy_context +from dataclasses import dataclass, field +from functools import partial, wraps +from inspect import ( + CORO_RUNNING, + CORO_SUSPENDED, + getcoroutinestate, +) +from io import IOBase +from os import PathLike +from queue import Queue +from signal import Signals +from socket import AddressFamily, SocketKind +from threading import Thread +from types import CodeType, TracebackType +from typing import ( + IO, + TYPE_CHECKING, + Any, + Literal, + ParamSpec, + TypeVar, + cast, +) +from weakref import WeakKeyDictionary + +from .. import ( + CapacityLimiterStatistics, + EventStatistics, + LockStatistics, + TaskInfo, + abc, +) +from .._core._eventloop import ( + claim_worker_thread, + set_current_async_library, + threadlocals, +) +from .._core._exceptions import ( + BrokenResourceError, + BusyResourceError, + ClosedResourceError, + EndOfStream, + RunFinishedError, + WouldBlock, +) +from .._core._sockets import convert_ipv6_sockaddr +from .._core._streams import create_memory_object_stream +from .._core._synchronization import ( + CapacityLimiter as BaseCapacityLimiter, +) +from .._core._synchronization import Event as BaseEvent +from .._core._synchronization import Lock as BaseLock +from .._core._synchronization import ( + ResourceGuard, + SemaphoreStatistics, +) +from .._core._synchronization import Semaphore as BaseSemaphore +from .._core._tasks import CancelScope as BaseCancelScope +from .._core._tasks import TaskHandle +from ..abc import ( + AsyncBackend, + IPSockAddrType, + SocketListener, + UDPPacketType, + UNIXDatagramPacketType, +) +from ..abc._tasks import call_for_coroutine, get_callable_name, get_coro_name +from ..lowlevel import RunVar, _run_vars + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike + + from ..abc._eventloop import StrOrBytesPath + from ..streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream +else: + FileDescriptorLike = object + +if sys.version_info >= (3, 11): + from asyncio import Runner + from typing import Self, TypeVarTuple, Unpack +else: + import contextvars + import enum + import signal + from asyncio import coroutines, events, exceptions, tasks + + from exceptiongroup import BaseExceptionGroup + from typing_extensions import Self, TypeVarTuple, Unpack + + class _State(enum.Enum): + CREATED = "created" + INITIALIZED = "initialized" + CLOSED = "closed" + + class Runner: + # Copied from CPython 3.11 + def __init__( + self, + *, + debug: bool | None = None, + loop_factory: Callable[[], AbstractEventLoop] | None = None, + ): + self._state = _State.CREATED + self._debug = debug + self._loop_factory = loop_factory + self._loop: AbstractEventLoop | None = None + self._context = None + self._interrupt_count = 0 + self._set_event_loop = False + + def __enter__(self) -> Self: + self._lazy_init() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def close(self) -> None: + """Shutdown and close event loop.""" + loop = self._loop + if self._state is not _State.INITIALIZED or loop is None: + return + try: + _cancel_all_tasks(loop) + loop.run_until_complete(loop.shutdown_asyncgens()) + if hasattr(loop, "shutdown_default_executor"): + loop.run_until_complete(loop.shutdown_default_executor()) + else: + loop.run_until_complete(_shutdown_default_executor(loop)) + finally: + if self._set_event_loop: + events.set_event_loop(None) + loop.close() + self._loop = None + self._state = _State.CLOSED + + def get_loop(self) -> AbstractEventLoop: + """Return embedded event loop.""" + self._lazy_init() + return self._loop + + def run(self, coro: Coroutine[T_Retval], *, context=None) -> T_Retval: + """Run a coroutine inside the embedded event loop.""" + if not coroutines.iscoroutine(coro): + raise ValueError(f"a coroutine was expected, got {coro!r}") + + if events._get_running_loop() is not None: + # fail fast with short traceback + raise RuntimeError( + "Runner.run() cannot be called from a running event loop" + ) + + self._lazy_init() + + if context is None: + context = self._context + task = context.run(self._loop.create_task, coro) + + if ( + threading.current_thread() is threading.main_thread() + and signal.getsignal(signal.SIGINT) is signal.default_int_handler + ): + sigint_handler = partial(self._on_sigint, main_task=task) + try: + signal.signal(signal.SIGINT, sigint_handler) + except ValueError: + # `signal.signal` may throw if `threading.main_thread` does + # not support signals (e.g. embedded interpreter with signals + # not registered - see gh-91880) + sigint_handler = None + else: + sigint_handler = None + + self._interrupt_count = 0 + try: + return self._loop.run_until_complete(task) + except exceptions.CancelledError: + if self._interrupt_count > 0: + uncancel = getattr(task, "uncancel", None) + if uncancel is not None and uncancel() == 0: + raise KeyboardInterrupt # noqa: B904 + raise # CancelledError + finally: + if ( + sigint_handler is not None + and signal.getsignal(signal.SIGINT) is sigint_handler + ): + signal.signal(signal.SIGINT, signal.default_int_handler) + + def _lazy_init(self) -> None: + if self._state is _State.CLOSED: + raise RuntimeError("Runner is closed") + if self._state is _State.INITIALIZED: + return + if self._loop_factory is None: + self._loop = events.new_event_loop() + if not self._set_event_loop: + # Call set_event_loop only once to avoid calling + # attach_loop multiple times on child watchers + events.set_event_loop(self._loop) + self._set_event_loop = True + else: + self._loop = self._loop_factory() + if self._debug is not None: + self._loop.set_debug(self._debug) + self._context = contextvars.copy_context() + self._state = _State.INITIALIZED + + def _on_sigint(self, signum, frame, main_task: asyncio.Task) -> None: + self._interrupt_count += 1 + if self._interrupt_count == 1 and not main_task.done(): + main_task.cancel() + # wakeup loop if it is blocked by select() with long timeout + self._loop.call_soon_threadsafe(lambda: None) + return + raise KeyboardInterrupt() + + def _cancel_all_tasks(loop: AbstractEventLoop) -> None: + to_cancel = tasks.all_tasks(loop) + if not to_cancel: + return + + for task in to_cancel: + task.cancel() + + loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True)) + + for task in to_cancel: + if task.cancelled(): + continue + if task.exception() is not None: + loop.call_exception_handler( + { + "message": "unhandled exception during asyncio.run() shutdown", + "exception": task.exception(), + "task": task, + } + ) + + async def _shutdown_default_executor(loop: AbstractEventLoop) -> None: + """Schedule the shutdown of the default executor.""" + + def _do_shutdown(future: asyncio.futures.Future) -> None: + try: + loop._default_executor.shutdown(wait=True) # type: ignore[attr-defined] + loop.call_soon_threadsafe(future.set_result, None) + except Exception as ex: + loop.call_soon_threadsafe(future.set_exception, ex) + + loop._executor_shutdown_called = True + if loop._default_executor is None: + return + future = loop.create_future() + thread = threading.Thread(target=_do_shutdown, args=(future,)) + thread.start() + try: + await future + finally: + thread.join() + + +T_Retval = TypeVar("T_Retval") +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) +PosArgsT = TypeVarTuple("PosArgsT") +P = ParamSpec("P") + +_root_task: RunVar[asyncio.Task[Any] | None] = RunVar("_root_task") + + +def find_root_task() -> asyncio.Task: + root_task = _root_task.get(None) + if root_task is not None and not root_task.done(): + return root_task + + # Look for a task that has been started via run_until_complete() + for task in all_tasks(): + if task._callbacks and not task.done(): + for cb, context in task._callbacks: + if ( + cb is _run_until_complete_cb + or getattr(cb, "__module__", None) == "uvloop.loop" + ): + _root_task.set(task) + + def _unset(t: asyncio.Task[Any]) -> None: + if vars := _run_vars.get(t.get_loop()): + vars.pop(_root_task, None) + + # Register a callback to break the task -> loop -> _run_var[loop][_root_task] -> task cycle + # Also run it in its own context to not create another reference. + # We can't use RunVar.reset() here since these are called synchronously + # and thus lowlevel.current_token() (which RunVar.reset() depends on) fails. + task.add_done_callback(_unset, context=context) + return task + + # Look up the topmost task in the AnyIO task tree, if possible + task = cast(asyncio.Task, current_task()) + state = _task_states.get(task) + if state: + cancel_scope = state.cancel_scope + while cancel_scope and cancel_scope._parent_scope is not None: + cancel_scope = cancel_scope._parent_scope + + if cancel_scope is not None: + return cast(asyncio.Task, cancel_scope._host_task) + + return task + + +def _task_started(task: asyncio.Task) -> bool: + """Return ``True`` if the task has been started and has not finished.""" + # The task coro should never be None here, as we never add finished tasks to the + # task list + coro = task.get_coro() + assert coro is not None + return getcoroutinestate(coro) in (CORO_RUNNING, CORO_SUSPENDED) + + +# +# Timeouts and cancellation +# + + +def is_anyio_cancellation(exc: CancelledError) -> bool: + # Sometimes third party frameworks catch a CancelledError and raise a new one, so as + # a workaround we have to look at the previous ones in __context__ too for a + # matching cancel message + while True: + if ( + exc.args + and isinstance(exc.args[0], str) + and exc.args[0].startswith("Cancelled via cancel scope ") + ): + return True + + if isinstance(exc.__context__, CancelledError): + exc = exc.__context__ + continue + + return False + + +class CancelScope(BaseCancelScope): + __slots__ = ( + "_active", + "_cancel_called", + "_cancel_handle", + "_cancel_reason", + "_cancelled_caught", + "_child_scopes", + "_deadline", + "_host_task", + "_parent_scope", + "_pending_uncancellations", + "_shield", + "_tasks", + "_timeout_handle", + ) + + def __new__(cls, *, deadline: float = math.inf, shield: bool = False) -> Self: + return object.__new__(cls) + + def __init__(self, deadline: float = math.inf, shield: bool = False): + self._deadline = deadline + self._shield = shield + self._parent_scope: CancelScope | None = None + self._child_scopes: set[CancelScope] = set() + self._cancel_called = False + self._cancel_reason: str | None = None + self._cancelled_caught = False + self._active = False + self._timeout_handle: asyncio.TimerHandle | None = None + self._cancel_handle: asyncio.Handle | None = None + self._tasks: set[asyncio.Task] = set() + self._host_task: asyncio.Task | None = None + if sys.version_info >= (3, 11): + self._pending_uncancellations: int | None = 0 + else: + self._pending_uncancellations = None + + def __enter__(self) -> Self: + if self._active: + raise RuntimeError( + "Each CancelScope may only be used for a single 'with' block" + ) + + self._host_task = host_task = cast(asyncio.Task, current_task()) + self._tasks.add(host_task) + try: + task_state = _task_states[host_task] + except KeyError: + task_state = TaskState(None, self) + _task_states[host_task] = task_state + else: + self._parent_scope = task_state.cancel_scope + task_state.cancel_scope = self + if self._parent_scope is not None: + # If using an eager task factory, the parent scope may not even contain + # the host task + self._parent_scope._child_scopes.add(self) + self._parent_scope._tasks.discard(host_task) + + self._timeout() + self._active = True + + # Start cancelling the host task if the scope was cancelled before entering + if self._cancel_called: + self._deliver_cancellation(self) + + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + del exc_tb + + if not self._active: + raise RuntimeError("This cancel scope is not active") + if current_task() is not self._host_task: + raise RuntimeError( + "Attempted to exit cancel scope in a different task than it was " + "entered in" + ) + + assert self._host_task is not None + host_task_state = _task_states.get(self._host_task) + if host_task_state is None or host_task_state.cancel_scope is not self: + raise RuntimeError( + "Attempted to exit a cancel scope that isn't the current tasks's " + "current cancel scope" + ) + + try: + self._active = False + if self._timeout_handle: + self._timeout_handle.cancel() + self._timeout_handle = None + + self._tasks.remove(self._host_task) + if self._parent_scope is not None: + self._parent_scope._child_scopes.remove(self) + self._parent_scope._tasks.add(self._host_task) + + host_task_state.cancel_scope = self._parent_scope + + # Restart the cancellation effort in the closest visible, cancelled parent + # scope if necessary + self._restart_cancellation_in_parent() + + # We only swallow the exception iff it was an AnyIO CancelledError, either + # directly as exc_val or inside an exception group and there are no cancelled + # parent cancel scopes visible to us here + if self._cancel_called and not self._parent_cancellation_is_visible_to_us: + # For each level-cancel() call made on the host task, call uncancel() + while self._pending_uncancellations: + self._host_task.uncancel() + self._pending_uncancellations -= 1 + + # Update cancelled_caught and check for exceptions we must not swallow + if isinstance(exc_val, BaseExceptionGroup): + cancelleds_caught, remaining = exc_val.split( + lambda exc: ( + isinstance(exc, CancelledError) + and is_anyio_cancellation(exc) + ) + ) + + if cancelleds_caught is None: + return False + + self._cancelled_caught = True + + if remaining is None: + return True + + context = remaining.__context__ + try: + # Preserve __cause__ and __suppress_context__ by avoiding `raise + # ... from ...` + raise remaining + finally: + # Preserve __context__ + remaining.__context__ = context + del context + else: + if isinstance(exc_val, CancelledError) and is_anyio_cancellation( + exc_val + ): + self._cancelled_caught = True + return True + else: + return False + else: + if self._pending_uncancellations: + assert self._parent_scope is not None + assert self._parent_scope._pending_uncancellations is not None + self._parent_scope._pending_uncancellations += ( + self._pending_uncancellations + ) + self._pending_uncancellations = 0 + + return False + finally: + self._host_task = None + del exc_val + + @property + def _effectively_cancelled(self) -> bool: + cancel_scope: CancelScope | None = self + while cancel_scope is not None: + if cancel_scope._cancel_called: + return True + + if cancel_scope.shield: + return False + + cancel_scope = cancel_scope._parent_scope + + return False + + @property + def _parent_cancellation_is_visible_to_us(self) -> bool: + return ( + self._parent_scope is not None + and not self.shield + and self._parent_scope._effectively_cancelled + ) + + def _timeout(self) -> None: + if self._deadline != math.inf: + loop = get_running_loop() + if loop.time() >= self._deadline: + self.cancel("deadline exceeded") + else: + self._timeout_handle = loop.call_at(self._deadline, self._timeout) + + def _deliver_cancellation(self, origin: CancelScope) -> bool: + """ + Deliver cancellation to directly contained tasks and nested cancel scopes. + + Schedule another run at the end if we still have tasks eligible for + cancellation. + + :param origin: the cancel scope that originated the cancellation + :return: ``True`` if the delivery needs to be retried on the next cycle + + """ + should_retry = False + current = current_task() + for task in self._tasks: + # Always skip tasks that are already done (see issue #1111) + if task.done(): + continue + + should_retry = True + if task._must_cancel: # type: ignore[attr-defined] + continue + + # The task is eligible for cancellation if it has started + if task is not current and (task is self._host_task or _task_started(task)): + waiter = task._fut_waiter # type: ignore[attr-defined] + if not isinstance(waiter, asyncio.Future) or not waiter.done(): + task.cancel(origin._cancel_reason) + if ( + task is origin._host_task + and origin._pending_uncancellations is not None + ): + origin._pending_uncancellations += 1 + + # Deliver cancellation to child scopes that aren't shielded or running their own + # cancellation callbacks + for scope in self._child_scopes: + if not scope._shield and not scope.cancel_called: + should_retry = scope._deliver_cancellation(origin) or should_retry + + # Schedule another callback if there are still tasks left + if origin is self: + if should_retry: + self._cancel_handle = get_running_loop().call_soon( + self._deliver_cancellation, origin + ) + else: + self._cancel_handle = None + + return should_retry + + def _restart_cancellation_in_parent(self) -> None: + """ + Restart the cancellation effort in the closest directly cancelled parent scope. + + """ + scope = self._parent_scope + while scope is not None: + if scope._cancel_called: + if scope._cancel_handle is None: + scope._deliver_cancellation(scope) + + break + + # No point in looking beyond any shielded scope + if scope._shield: + break + + scope = scope._parent_scope + + def _reparent(self, new_parent: CancelScope) -> None: + """ + Move this active scope from its current parent to ``new_parent``. + + Used by :meth:`TaskGroup.start` to move a task that has just called + ``task_status.started()`` into the target task group's cancel scope. + """ + if self._parent_scope is new_parent: + return + + if self._parent_scope is not None: + self._parent_scope._child_scopes.discard(self) + + self._parent_scope = new_parent + new_parent._child_scopes.add(self) + + # If the new parent (or an ancestor) is already cancelled, (re)start the + # delivery loop to ensure we're cancelled at next checkpoint like Trio. + self._restart_cancellation_in_parent() + + def cancel(self, reason: str | None = None) -> None: + if not self._cancel_called: + if self._timeout_handle: + self._timeout_handle.cancel() + self._timeout_handle = None + + self._cancel_called = True + self._cancel_reason = f"Cancelled via cancel scope {id(self):x}" + if task := current_task(): + self._cancel_reason += f" by {task}" + + if reason: + self._cancel_reason += f"; reason: {reason}" + + if self._host_task is not None: + self._deliver_cancellation(self) + + @property + def deadline(self) -> float: + return self._deadline + + @deadline.setter + def deadline(self, value: float) -> None: + self._deadline = float(value) + if self._timeout_handle is not None: + self._timeout_handle.cancel() + self._timeout_handle = None + + if self._active and not self._cancel_called: + self._timeout() + + @property + def cancel_called(self) -> bool: + return self._cancel_called + + @property + def cancelled_caught(self) -> bool: + return self._cancelled_caught + + @property + def shield(self) -> bool: + return self._shield + + @shield.setter + def shield(self, value: bool) -> None: + if self._shield != value: + self._shield = value + if not value: + self._restart_cancellation_in_parent() + + +# +# Task states +# + + +class TaskState: + """ + Encapsulates auxiliary task information that cannot be added to the Task instance + itself because there are no guarantees about its implementation. + """ + + __slots__ = "__weakref__", "cancel_scope", "parent_id" + + def __init__(self, parent_id: int | None, cancel_scope: CancelScope | None): + self.parent_id = parent_id + self.cancel_scope = cancel_scope + + +_task_states: WeakKeyDictionary[asyncio.Task, TaskState] = WeakKeyDictionary() + + +# +# Task groups +# + + +class _AsyncioTaskStatus(abc.TaskStatus[T_contra]): + def __init__( + self, + future: asyncio.Future, + parent_id: int, + target_scope: CancelScope, + spawn_scope: CancelScope, + ): + self._future = future + self._parent_id = parent_id + # The eventual parent scope for this spawn_scope + # (after task_status.started() has been called) + self._target_scope = target_scope + # The task's own cancel scope, also held by its TaskHandle + self._spawn_scope = spawn_scope + + def started(self, value: T_contra | None = None) -> None: + task = cast(asyncio.Task, current_task()) + _task_states[task].parent_id = self._parent_id + if self._future.done(): + if not self._future.cancelled(): + raise RuntimeError("called 'started' twice on the same task status") + else: + # Caller of start() was cancelled, nothing to reparent + return + + self._future.set_result(value) + self._spawn_scope._reparent(self._target_scope) + + +if sys.version_info >= (3, 12): + _eager_task_factory_code: CodeType | None = asyncio.eager_task_factory.__code__ +else: + _eager_task_factory_code = None + + +class TaskGroup(abc.TaskGroup): + def __init__(self) -> None: + self.cancel_scope: CancelScope = CancelScope() + self._entered = False + self._exceptions: list[BaseException] = [] + self._tasks: set[asyncio.Task] = set() + self._on_completed_fut: asyncio.Future[None] | None = None + + async def __aenter__(self) -> Self: + if self._entered: + raise RuntimeError("TaskGroup cannot be entered more than once") + + self._entered = True + + self.cancel_scope.__enter__() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + try: + if exc_val is not None: + self.cancel_scope.cancel() + if not isinstance(exc_val, CancelledError): + self._exceptions.append(exc_val) + + loop = get_running_loop() + try: + if self._tasks: + with CancelScope() as wait_scope: + while self._tasks: + self._on_completed_fut = loop.create_future() + + try: + await self._on_completed_fut + except CancelledError as exc: + # Shield the scope against further cancellation attempts, + # as they're not productive (#695) + wait_scope.shield = True + self.cancel_scope.cancel() + + # Set exc_val from the cancellation exception if it was + # previously unset. However, we should not replace a native + # cancellation exception with one raise by a cancel scope. + if exc_val is None or ( + isinstance(exc_val, CancelledError) + and not is_anyio_cancellation(exc) + ): + exc_val = exc + + self._on_completed_fut = None + else: + # If there are no child tasks to wait on, run at least one checkpoint + # anyway + await AsyncIOBackend.cancel_shielded_checkpoint() + + if self._exceptions: + # The exception that got us here should already have been + # added to self._exceptions so it's ok to break exception + # chaining and avoid adding a "During handling of above..." + # for each nesting level. + raise BaseExceptionGroup( + "unhandled errors in a TaskGroup", self._exceptions + ) from None + elif exc_val: + raise exc_val + except BaseException as exc: + if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__): + return True + + raise + + return self.cancel_scope.__exit__(exc_type, exc_val, exc_tb) + finally: + del exc_val, exc_tb, self._exceptions + + def _spawn( + self, + coro: Coroutine[Any, Any, T_co], + name: object, + task_status: _AsyncioTaskStatus | None = None, + ) -> TaskHandle[T_co]: + task_status_future = task_status._future if task_status is not None else None + + def task_done(_task: asyncio.Task) -> None: + if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None: + asyncio.future_discard_from_awaited_by( + _task, self.cancel_scope._host_task + ) + + task_state = _task_states[_task] + assert task_state.cancel_scope is not None + assert _task in task_state.cancel_scope._tasks + task_state.cancel_scope._tasks.remove(_task) + self._tasks.remove(task) + del _task_states[_task] + + if self._on_completed_fut is not None and not self._tasks: + try: + self._on_completed_fut.set_result(None) + except asyncio.InvalidStateError: + pass + + try: + exc = _task.exception() + except CancelledError as e: + while isinstance(e.__context__, CancelledError): + e = e.__context__ + + exc = e + + if exc is not None: + # The future can only be in the cancelled state if the host task was + # cancelled, so return immediately instead of adding one more + # CancelledError to the exceptions list + if task_status_future is not None and task_status_future.cancelled(): + return + + if task_status_future is None or task_status_future.done(): + if not isinstance(exc, CancelledError): + self._exceptions.append(exc) + + if not self.cancel_scope._effectively_cancelled: + self.cancel_scope.cancel() + else: + task_status_future.set_exception(exc) + elif task_status_future is not None and not task_status_future.done(): + task_status_future.set_exception( + RuntimeError("Child exited without calling task_status.started()") + ) + + if task_status_future is not None: + parent_id = id(current_task()) + caller_state = _task_states.get(cast(asyncio.Task, current_task())) + if caller_state is not None and caller_state.cancel_scope is not None: + initial_scope = caller_state.cancel_scope + else: + # The caller is an unmanaged task (no task state) + initial_scope = self.cancel_scope + else: + parent_id = id(self.cancel_scope._host_task) + initial_scope = self.cancel_scope + + spawn_scope = task_status._spawn_scope if task_status is not None else None + handle = TaskHandle(coro, name, cancel_scope=spawn_scope) + loop = asyncio.get_running_loop() + wrapper_coro = handle._run_coro() + try: + if ( + (factory := loop.get_task_factory()) + and getattr(factory, "__code__", None) is _eager_task_factory_code + and (closure := getattr(factory, "__closure__", None)) + ): + custom_task_constructor = closure[0].cell_contents + task = custom_task_constructor( + wrapper_coro, loop=loop, name=handle.name + ) + else: + task = loop.create_task(wrapper_coro, name=handle.name) + except BaseException: + with suppress(BaseException): + wrapper_coro.close() + + with suppress(BaseException): + coro.close() + + raise + + # Make the spawned task inherit the initial cancel scope + _task_states[task] = TaskState(parent_id=parent_id, cancel_scope=initial_scope) + initial_scope._tasks.add(task) + self._tasks.add(task) + if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None: + asyncio.future_add_to_awaited_by(task, self.cancel_scope._host_task) + + task.add_done_callback(task_done) + return handle + + def create_task( + self, + coro: Coroutine[Any, Any, T_co], + *, + name: object = None, + context: Context | None = None, + ) -> TaskHandle[T_co]: + if not isinstance(coro, Coroutine): + raise TypeError(f"expected a coroutine, got {coro.__class__.__qualname__}") + + if not self._entered or not self.cancel_scope._active: + coro.close() + raise RuntimeError( + "This task group is not active; no new tasks can be started." + ) + + final_name = get_coro_name(coro, name) + + if context is not None: + return context.run(self._spawn, coro, name=final_name) + else: + return self._spawn(coro, name=final_name) + + async def start( + self, + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + *args: Unpack[PosArgsT], + name: object = None, + return_handle: Literal[False, True] = False, + ) -> Any: + if not self._entered or not self.cancel_scope._active: + raise RuntimeError( + "This task group is not active; no new tasks can be started." + ) + + # Until task_status.started() is called, a task spawned via start() belongs to + # the *caller's* cancel scope, not the target group's, so cancelling the group + # does not cancel a task that hasn't reported startup yet. The + # task_status.started() call moves the task's cancel scope to this task group's + # cancel scope. + # + # The caller may be an unmanaged task (no _task_states entry), in which case + # fall back to the group's own scope. + future: asyncio.Future = asyncio.Future() + final_name = get_callable_name(func, name) + task_status: _AsyncioTaskStatus[Any] = _AsyncioTaskStatus( + future, id(self.cancel_scope._host_task), self.cancel_scope, CancelScope() + ) + coro = call_for_coroutine(func, args, task_status=task_status) + handle = self._spawn(coro, final_name, task_status) + + # If the task raises an exception after sending a start value without a switch + # point between, the task group is cancelled and this method never proceeds to + # process the completed future. That's why we have to have a shielded cancel + # scope here. + try: + await future + except BaseException: + if handle.status is TaskHandle.Status.PENDING: + # Cancel the task and wait for it to exit before returning + handle.cancel() + with CancelScope(shield=True): + await handle.wait() + + raise + + if return_handle: + handle._start_value = future.result() + return handle + else: + return future.result() + + +# +# Threads +# + +_Retval_Queue_Type = tuple[T_Retval | None, BaseException | None] + + +class WorkerThread(Thread): + MAX_IDLE_TIME = 10 # seconds + + def __init__( + self, + root_task: asyncio.Task, + workers: set[WorkerThread], + idle_workers: deque[WorkerThread], + ): + kwargs: dict[str, Any] = {} + if sys.version_info >= (3, 14): + kwargs["context"] = Context() + + super().__init__(name="AnyIO worker thread", **kwargs) + self.root_task = root_task + self.workers = workers + self.idle_workers = idle_workers + self.loop = root_task._loop + self.queue: Queue[ + tuple[Context, Callable, tuple, asyncio.Future, CancelScope] | None + ] = Queue(2) + self.idle_since = AsyncIOBackend.current_time() + self.stopping = False + + def _report_result( + self, future: asyncio.Future, result: Any, exc: BaseException | None + ) -> None: + self.idle_since = AsyncIOBackend.current_time() + if not self.stopping: + self.idle_workers.append(self) + + if not future.cancelled(): + if exc is not None: + if isinstance(exc, StopIteration): + new_exc = RuntimeError("coroutine raised StopIteration") + new_exc.__cause__ = exc + exc = new_exc + + future.set_exception(exc) + else: + future.set_result(result) + + def run(self) -> None: + with claim_worker_thread(AsyncIOBackend, self.loop): + while True: + item = self.queue.get() + if item is None: + # Shutdown command received + return + + context, func, args, future, cancel_scope = item + if not future.cancelled(): + result = None + exception: BaseException | None = None + threadlocals.current_cancel_scope = cancel_scope + try: + result = context.run(func, *args) + except BaseException as exc: + exception = exc + finally: + del threadlocals.current_cancel_scope + + try: + self.loop.call_soon_threadsafe( + self._report_result, future, result, exception + ) + except RuntimeError: + if not self.loop.is_closed(): + raise + + del result, exception + + self.queue.task_done() + del item, context, func, args, future, cancel_scope + + def stop(self, f: asyncio.Task | None = None) -> None: + self.stopping = True + self.queue.put_nowait(None) + self.workers.discard(self) + try: + self.idle_workers.remove(self) + except ValueError: + pass + + +_threadpool_idle_workers: RunVar[deque[WorkerThread]] = RunVar( + "_threadpool_idle_workers" +) +_threadpool_workers: RunVar[set[WorkerThread]] = RunVar("_threadpool_workers") + + +# +# Subprocesses +# + + +@dataclass(eq=False) +class StreamReaderWrapper(abc.ByteReceiveStream): + _stream: asyncio.StreamReader + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + data = await self._stream.read(max_bytes) + if data: + return data + else: + raise EndOfStream + + async def aclose(self) -> None: + self._stream.set_exception(ClosedResourceError()) + await AsyncIOBackend.checkpoint() + + +@dataclass(eq=False) +class StreamWriterWrapper(abc.ByteSendStream): + _stream: asyncio.StreamWriter + _closed: bool = field(init=False, default=False) + + async def send(self, item: bytes) -> None: + await AsyncIOBackend.checkpoint_if_cancelled() + stream_paused = self._stream._protocol._paused # type: ignore[attr-defined] + try: + self._stream.write(item) + await self._stream.drain() + except (ConnectionResetError, BrokenPipeError, RuntimeError) as exc: + # If closed by us and/or the peer: + # * on stdlib, drain() raises ConnectionResetError or BrokenPipeError + # * on uvloop and Winloop, write() eventually starts raising RuntimeError + if self._closed: + raise ClosedResourceError from exc + elif self._stream.is_closing(): + raise BrokenResourceError from exc + + raise + + if not stream_paused: + await AsyncIOBackend.cancel_shielded_checkpoint() + + async def aclose(self) -> None: + self._closed = True + self._stream.close() + await AsyncIOBackend.checkpoint() + + +@dataclass(eq=False) +class Process(abc.Process): + _process: asyncio.subprocess.Process + _stdin: StreamWriterWrapper | None + _stdout: StreamReaderWrapper | None + _stderr: StreamReaderWrapper | None + _exited: asyncio.Event + _transport: asyncio.SubprocessTransport + + async def aclose(self) -> None: + with CancelScope(shield=True) as scope: + # We need to close the underlying pipe_transports as well to allow a + # process blocking on full buffers to receive SIGPIPE and exit. + if self._stdin: + await self._stdin.aclose() + if pipe := self._transport.get_pipe_transport(0): + pipe.close() + if self._stdout: + await self._stdout.aclose() + if pipe := self._transport.get_pipe_transport(1): + pipe.close() + if self._stderr: + await self._stderr.aclose() + if pipe := self._transport.get_pipe_transport(2): + pipe.close() + + scope.shield = False + try: + await self.wait() + except BaseException: + scope.shield = True + # Closing the transport on asyncio also handles sending kill + self._transport.close() + await self.wait() + raise + + async def wait(self) -> int: + await self._exited.wait() + assert self._process.returncode is not None + return self._process.returncode + + def terminate(self) -> None: + self._process.terminate() + + def kill(self) -> None: + self._process.kill() + + def send_signal(self, signal: int) -> None: + self._process.send_signal(signal) + + @property + def pid(self) -> int: + return self._process.pid + + @property + def returncode(self) -> int | None: + return self._process.returncode + + @property + def stdin(self) -> abc.ByteSendStream | None: + return self._stdin + + @property + def stdout(self) -> abc.ByteReceiveStream | None: + return self._stdout + + @property + def stderr(self) -> abc.ByteReceiveStream | None: + return self._stderr + + +def _forcibly_shutdown_process_pool_on_exit( + workers: set[Process], _task: object +) -> None: + """ + Forcibly shuts down worker processes belonging to this event loop.""" + child_watcher: asyncio.AbstractChildWatcher | None = None # type: ignore[name-defined] + if sys.version_info < (3, 12): + try: + child_watcher = asyncio.get_event_loop_policy().get_child_watcher() + except NotImplementedError: + pass + + # Close as much as possible (w/o async/await) to avoid warnings + for process in workers.copy(): + if process.returncode is not None: + continue + + process._stdin._stream._transport.close() # type: ignore[union-attr] + process._stdout._stream._transport.close() # type: ignore[union-attr] + process._stderr._stream._transport.close() # type: ignore[union-attr] + process.kill() + if child_watcher: + child_watcher.remove_child_handler(process.pid) + + +async def _shutdown_process_pool_on_exit(workers: set[abc.Process]) -> None: + """ + Shuts down worker processes belonging to this event loop. + + NOTE: this only works when the event loop was started using asyncio.run() or + anyio.run(). + + """ + process: abc.Process + try: + await sleep(math.inf) + except asyncio.CancelledError: + workers = workers.copy() + for process in workers: + if process.returncode is None: + process.kill() + + for process in workers: + await process.aclose() + + +# +# Sockets and networking +# + + +class StreamProtocol(asyncio.Protocol): + read_queue: deque[bytes] + read_event: asyncio.Event + write_event: asyncio.Event + exception: Exception | None = None + is_at_eof: bool = False + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + self.read_queue = deque() + self.read_event = asyncio.Event() + self.write_event = asyncio.Event() + self.write_event.set() + cast(asyncio.Transport, transport).set_write_buffer_limits(0) + + def connection_lost(self, exc: Exception | None) -> None: + if exc: + self.exception = exc + + self.read_event.set() + self.write_event.set() + + def data_received(self, data: bytes) -> None: + # ProactorEventloop sometimes sends bytearray instead of bytes + self.read_queue.append(bytes(data)) + self.read_event.set() + + def eof_received(self) -> bool | None: + self.is_at_eof = True + self.read_event.set() + return True + + def pause_writing(self) -> None: + self.write_event = asyncio.Event() + + def resume_writing(self) -> None: + self.write_event.set() + + +class DatagramProtocol(asyncio.DatagramProtocol): + read_queue: deque[tuple[bytes, IPSockAddrType]] + read_event: asyncio.Event + write_event: asyncio.Event + closed_event: asyncio.Event + exception: Exception | None = None + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + self.read_queue = deque(maxlen=100) # arbitrary value + self.read_event = asyncio.Event() + self.write_event = asyncio.Event() + self.closed_event = asyncio.Event() + self.write_event.set() + + def connection_lost(self, exc: Exception | None) -> None: + self.read_event.set() + self.write_event.set() + self.closed_event.set() + + def datagram_received(self, data: bytes, addr: IPSockAddrType) -> None: + addr = convert_ipv6_sockaddr(addr) + self.read_queue.append((data, addr)) + self.read_event.set() + + def error_received(self, exc: Exception) -> None: + self.exception = exc + + def pause_writing(self) -> None: + self.write_event.clear() + + def resume_writing(self) -> None: + self.write_event.set() + + +class SocketStream(abc.SocketStream): + def __init__(self, transport: asyncio.Transport, protocol: StreamProtocol): + self._transport = transport + self._protocol = protocol + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + self._closed = False + + @property + def _raw_socket(self) -> socket.socket: + return self._transport.get_extra_info("socket") + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + with self._receive_guard: + if ( + not self._protocol.read_event.is_set() + and not self._transport.is_closing() + and not self._protocol.is_at_eof + ): + self._transport.resume_reading() + await self._protocol.read_event.wait() + self._transport.pause_reading() + else: + await AsyncIOBackend.checkpoint() + + try: + chunk = self._protocol.read_queue.popleft() + except IndexError: + if self._closed: + raise ClosedResourceError from None + elif self._protocol.exception: + raise BrokenResourceError from self._protocol.exception + else: + raise EndOfStream from None + + if len(chunk) > max_bytes: + # Split the oversized chunk + chunk, leftover = chunk[:max_bytes], chunk[max_bytes:] + self._protocol.read_queue.appendleft(leftover) + + # If the read queue is empty, clear the flag so that the next call will + # block until data is available + if not self._protocol.read_queue: + self._protocol.read_event.clear() + + return chunk + + async def send(self, item: bytes) -> None: + with self._send_guard: + await AsyncIOBackend.checkpoint() + + if self._closed: + raise ClosedResourceError + elif self._protocol.exception is not None: + raise BrokenResourceError from self._protocol.exception + + try: + self._transport.write(item) + except RuntimeError as exc: + if self._transport.is_closing(): + raise BrokenResourceError from exc + else: + raise + + await self._protocol.write_event.wait() + + async def send_eof(self) -> None: + try: + self._transport.write_eof() + except OSError: + pass + + async def aclose(self) -> None: + self._closed = True + if not self._transport.is_closing(): + try: + self._transport.write_eof() + except OSError: + pass + + self._transport.close() + await sleep(0) + self._transport.abort() + + +class _RawSocketMixin: + _receive_future: asyncio.Future | None = None + _send_future: asyncio.Future | None = None + _closing = False + + def __init__(self, raw_socket: socket.socket): + self.__raw_socket = raw_socket + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + @property + def _raw_socket(self) -> socket.socket: + return self.__raw_socket + + def _wait_until_readable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future: + def callback(f: object) -> None: + del self._receive_future + loop.remove_reader(self.__raw_socket) + + f = self._receive_future = asyncio.Future() + loop.add_reader(self.__raw_socket, f.set_result, None) + f.add_done_callback(callback) + return f + + def _wait_until_writable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future: + def callback(f: object) -> None: + del self._send_future + loop.remove_writer(self.__raw_socket) + + f = self._send_future = asyncio.Future() + loop.add_writer(self.__raw_socket, f.set_result, None) + f.add_done_callback(callback) + return f + + async def aclose(self) -> None: + if not self._closing: + self._closing = True + if self.__raw_socket.fileno() != -1: + self.__raw_socket.close() + + if self._receive_future and not self._receive_future.done(): + self._receive_future.set_result(None) + if self._send_future and not self._send_future.done(): + self._send_future.set_result(None) + + +class UNIXSocketStream(_RawSocketMixin, abc.UNIXSocketStream): + async def send_eof(self) -> None: + with self._send_guard: + self._raw_socket.shutdown(socket.SHUT_WR) + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + data = self._raw_socket.recv(max_bytes) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + if not data: + raise EndOfStream + + return data + + async def send(self, item: bytes) -> None: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._send_guard: + view = memoryview(item) + while view: + try: + bytes_sent = self._raw_socket.send(view) + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + view = view[bytes_sent:] + + async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: + if not isinstance(msglen, int) or msglen < 0: + raise ValueError("msglen must be a non-negative integer") + if not isinstance(maxfds, int) or maxfds < 1: + raise ValueError("maxfds must be a positive integer") + + loop = get_running_loop() + fds = array.array("i") + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + message, ancdata, _flags, _addr = self._raw_socket.recvmsg( + msglen, socket.CMSG_LEN(maxfds * fds.itemsize) + ) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + if not message and not ancdata: + raise EndOfStream + + break + + for cmsg_level, cmsg_type, cmsg_data in ancdata: + if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS: + raise RuntimeError( + f"Received unexpected ancillary data; message = {message!r}, " + f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}" + ) + + fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) + + return message, list(fds) + + async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: + if not message: + raise ValueError("message must not be empty") + if not fds: + raise ValueError("fds must not be empty") + + loop = get_running_loop() + filenos: list[int] = [] + for fd in fds: + if isinstance(fd, int): + filenos.append(fd) + elif isinstance(fd, IOBase): + filenos.append(fd.fileno()) + + fdarray = array.array("i", filenos) + await AsyncIOBackend.checkpoint() + with self._send_guard: + while True: + try: + # The ignore can be removed after mypy picks up + # https://github.com/python/typeshed/pull/5545 + self._raw_socket.sendmsg( + [message], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fdarray)] + ) + break + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + + +class TCPSocketListener(abc.SocketListener): + _accept_scope: CancelScope | None = None + _closed = False + + def __init__(self, raw_socket: socket.socket): + self.__raw_socket = raw_socket + self._loop = cast(asyncio.BaseEventLoop, get_running_loop()) + self._accept_guard = ResourceGuard("accepting connections from") + + @property + def _raw_socket(self) -> socket.socket: + return self.__raw_socket + + async def accept(self) -> abc.SocketStream: + if self._closed: + raise ClosedResourceError + + with self._accept_guard: + await AsyncIOBackend.checkpoint() + with CancelScope() as self._accept_scope: + try: + client_sock, _addr = await self._loop.sock_accept(self._raw_socket) + except asyncio.CancelledError: + # Workaround for https://bugs.python.org/issue41317 + try: + self._loop.remove_reader(self._raw_socket) + except (ValueError, NotImplementedError): + pass + + if self._closed: + raise ClosedResourceError from None + + raise + finally: + self._accept_scope = None + + client_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + transport, protocol = await self._loop.connect_accepted_socket( + StreamProtocol, client_sock + ) + return SocketStream(transport, protocol) + + async def aclose(self) -> None: + if self._closed: + return + + self._closed = True + if self._accept_scope: + # Workaround for https://bugs.python.org/issue41317 + try: + self._loop.remove_reader(self._raw_socket) + except (ValueError, NotImplementedError): + pass + + self._accept_scope.cancel() + await sleep(0) + + self._raw_socket.close() + + +class UNIXSocketListener(abc.SocketListener): + def __init__(self, raw_socket: socket.socket): + self.__raw_socket = raw_socket + self._loop = get_running_loop() + self._accept_guard = ResourceGuard("accepting connections from") + self._closed = False + + async def accept(self) -> abc.SocketStream: + await AsyncIOBackend.checkpoint() + with self._accept_guard: + while True: + try: + client_sock, _ = self.__raw_socket.accept() + client_sock.setblocking(False) + return UNIXSocketStream(client_sock) + except BlockingIOError: + f: asyncio.Future = asyncio.Future() + self._loop.add_reader(self.__raw_socket, f.set_result, None) + f.add_done_callback( + lambda _: self._loop.remove_reader(self.__raw_socket) + ) + await f + except OSError as exc: + if self._closed: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + + async def aclose(self) -> None: + self._closed = True + self.__raw_socket.close() + + @property + def _raw_socket(self) -> socket.socket: + return self.__raw_socket + + +class UDPSocket(abc.UDPSocket): + def __init__( + self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol + ): + self._transport = transport + self._protocol = protocol + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + self._closed = False + + @property + def _raw_socket(self) -> socket.socket: + return self._transport.get_extra_info("socket") + + async def aclose(self) -> None: + self._closed = True + if not self._transport.is_closing(): + self._transport.close() + + await self._protocol.closed_event.wait() + + async def receive(self) -> tuple[bytes, IPSockAddrType]: + with self._receive_guard: + await AsyncIOBackend.checkpoint() + + # If the buffer is empty, ask for more data + if not self._protocol.read_queue and not self._transport.is_closing(): + self._protocol.read_event.clear() + await self._protocol.read_event.wait() + + try: + return self._protocol.read_queue.popleft() + except IndexError: + if self._closed: + raise ClosedResourceError from None + else: + raise BrokenResourceError from None + + async def send(self, item: UDPPacketType) -> None: + with self._send_guard: + await AsyncIOBackend.checkpoint() + await self._protocol.write_event.wait() + if self._closed: + raise ClosedResourceError + elif self._transport.is_closing(): + raise BrokenResourceError + else: + self._transport.sendto(*item) + + +class ConnectedUDPSocket(abc.ConnectedUDPSocket): + def __init__( + self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol + ): + self._transport = transport + self._protocol = protocol + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + self._closed = False + + @property + def _raw_socket(self) -> socket.socket: + return self._transport.get_extra_info("socket") + + async def aclose(self) -> None: + self._closed = True + if not self._transport.is_closing(): + self._transport.close() + + await self._protocol.closed_event.wait() + + async def receive(self) -> bytes: + with self._receive_guard: + await AsyncIOBackend.checkpoint() + + # If the buffer is empty, ask for more data + if not self._protocol.read_queue and not self._transport.is_closing(): + self._protocol.read_event.clear() + await self._protocol.read_event.wait() + + try: + packet = self._protocol.read_queue.popleft() + except IndexError: + if self._closed: + raise ClosedResourceError from None + else: + raise BrokenResourceError from None + + return packet[0] + + async def send(self, item: bytes) -> None: + with self._send_guard: + await AsyncIOBackend.checkpoint() + await self._protocol.write_event.wait() + if self._closed: + raise ClosedResourceError + elif self._transport.is_closing(): + raise BrokenResourceError + else: + self._transport.sendto(item) + + +class UNIXDatagramSocket(_RawSocketMixin, abc.UNIXDatagramSocket): + async def receive(self) -> UNIXDatagramPacketType: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + data = self._raw_socket.recvfrom(65536) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return data + + async def send(self, item: UNIXDatagramPacketType) -> None: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._send_guard: + while True: + try: + self._raw_socket.sendto(*item) + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return + + +class ConnectedUNIXDatagramSocket(_RawSocketMixin, abc.ConnectedUNIXDatagramSocket): + async def receive(self) -> bytes: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + data = self._raw_socket.recv(65536) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return data + + async def send(self, item: bytes) -> None: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._send_guard: + while True: + try: + self._raw_socket.send(item) + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return + + +_read_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("read_events") +_write_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("write_events") + + +# +# Synchronization +# + + +class Event(BaseEvent): + __slots__ = ("_event",) + + def __new__(cls) -> Self: + return object.__new__(cls) + + def __init__(self) -> None: + self._event = asyncio.Event() + + def set(self) -> None: + self._event.set() + + def is_set(self) -> bool: + return self._event.is_set() + + async def wait(self) -> None: + if self.is_set(): + await AsyncIOBackend.checkpoint() + else: + await self._event.wait() + + def statistics(self) -> EventStatistics: + return EventStatistics(len(self._event._waiters)) + + +class Lock(BaseLock): + __slots__ = "_fast_acquire", "_owner_task", "_waiters" + + def __new__(cls, *, fast_acquire: bool = False) -> Self: + return object.__new__(cls) + + def __init__(self, *, fast_acquire: bool = False) -> None: + self._fast_acquire = fast_acquire + self._owner_task: asyncio.Task | None = None + self._waiters: deque[tuple[asyncio.Task, asyncio.Future]] = deque() + + async def acquire(self) -> None: + task = cast(asyncio.Task, current_task()) + if self._owner_task is None and not self._waiters: + await AsyncIOBackend.checkpoint_if_cancelled() + self._owner_task = task + + # Unless on the "fast path", yield control of the event loop so that other + # tasks can run too + if not self._fast_acquire: + try: + await AsyncIOBackend.cancel_shielded_checkpoint() + except CancelledError: + self.release() + raise + + return + + if self._owner_task == task: + raise RuntimeError("Attempted to acquire an already held Lock") + + fut: asyncio.Future[None] = asyncio.Future() + item = task, fut + self._waiters.append(item) + try: + await fut + except CancelledError: + if fut.cancelled(): + try: + self._waiters.remove(item) + except ValueError: + pass + else: + self.release() + + raise + + def acquire_nowait(self) -> None: + task = cast(asyncio.Task, current_task()) + if self._owner_task is None and not self._waiters: + self._owner_task = task + return + + if self._owner_task is task: + raise RuntimeError("Attempted to acquire an already held Lock") + + raise WouldBlock + + def locked(self) -> bool: + return self._owner_task is not None + + def release(self) -> None: + if self._owner_task != current_task(): + raise RuntimeError("The current task is not holding this lock") + + # A cancelled waiter that already received ownership removes itself from + # _waiters before calling release(); any cancelled waiter still queued here + # was cancelled before being woken, so drop it. + while self._waiters: + task, fut = self._waiters.popleft() + if fut.cancelled(): + continue + + self._owner_task = task + fut.set_result(None) + return + + self._owner_task = None + + def statistics(self) -> LockStatistics: + task_info = AsyncIOTaskInfo(self._owner_task) if self._owner_task else None + return LockStatistics(self.locked(), task_info, len(self._waiters)) + + +class Semaphore(BaseSemaphore): + __slots__ = "_fast_acquire", "_max_value", "_value", "_waiters" + + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Self: + return object.__new__(cls) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ): + super().__init__(initial_value, max_value=max_value) + self._value = initial_value + self._max_value = max_value + self._fast_acquire = fast_acquire + self._waiters: deque[asyncio.Future[None]] = deque() + + async def acquire(self) -> None: + if self._value > 0 and not self._waiters: + await AsyncIOBackend.checkpoint_if_cancelled() + self._value -= 1 + + # Unless on the "fast path", yield control of the event loop so that other + # tasks can run too + if not self._fast_acquire: + try: + await AsyncIOBackend.cancel_shielded_checkpoint() + except CancelledError: + self.release() + raise + + return + + fut: asyncio.Future[None] = asyncio.Future() + self._waiters.append(fut) + try: + await fut + except CancelledError: + if fut.cancelled(): + try: + self._waiters.remove(fut) + except ValueError: + pass + else: + self.release() + + raise + + def acquire_nowait(self) -> None: + if self._value == 0: + raise WouldBlock + + self._value -= 1 + + def release(self) -> None: + if self._max_value is not None and self._value == self._max_value: + raise ValueError("semaphore released too many times") + + while self._waiters: + fut = self._waiters.popleft() + if fut.cancelled(): + continue + + fut.set_result(None) + return + + self._value += 1 + + @property + def value(self) -> int: + return self._value + + @property + def max_value(self) -> int | None: + return self._max_value + + def statistics(self) -> SemaphoreStatistics: + return SemaphoreStatistics(len(self._waiters)) + + +class CapacityLimiter(BaseCapacityLimiter): + __slots__ = "_borrowers", "_total_tokens", "_wait_queue" + + def __new__(cls, total_tokens: float) -> Self: + return object.__new__(cls) + + def __init__(self, total_tokens: float): + self._total_tokens: float = 0 + self._borrowers: set[Any] = set() + self._wait_queue: OrderedDict[Any, asyncio.Event] = OrderedDict() + self.total_tokens = total_tokens + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + @property + def total_tokens(self) -> float: + return self._total_tokens + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + if not isinstance(value, int) and not math.isinf(value): + raise TypeError("total_tokens must be an int or math.inf") + + if value < 0: + raise ValueError("total_tokens must be >= 0") + + self._total_tokens = value + + # Notify waiting tasks that they have acquired the limiter while + # there is spare capacity. + while self._wait_queue and len(self._borrowers) < self._total_tokens: + borrower, event = self._wait_queue.popitem(last=False) + self._borrowers.add(borrower) + event.set() + + @property + def borrowed_tokens(self) -> int: + return len(self._borrowers) + + @property + def available_tokens(self) -> float: + return self._total_tokens - len(self._borrowers) + + def _notify_next_waiter(self) -> None: + """Hand a free token to the next task in line, if any.""" + if self._wait_queue and len(self._borrowers) < self._total_tokens: + borrower, event = self._wait_queue.popitem(last=False) + self._borrowers.add(borrower) + event.set() + + def acquire_nowait(self) -> None: + self.acquire_on_behalf_of_nowait(current_task()) + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + if borrower in self._borrowers: + raise RuntimeError( + "this borrower is already holding one of this CapacityLimiter's tokens" + ) + + if self._wait_queue or len(self._borrowers) >= self._total_tokens: + raise WouldBlock + + self._borrowers.add(borrower) + + async def acquire(self) -> None: + return await self.acquire_on_behalf_of(current_task()) + + async def acquire_on_behalf_of(self, borrower: object) -> None: + await AsyncIOBackend.checkpoint_if_cancelled() + try: + self.acquire_on_behalf_of_nowait(borrower) + except WouldBlock: + event = asyncio.Event() + self._wait_queue[borrower] = event + try: + await event.wait() + except BaseException: + self._wait_queue.pop(borrower, None) + if event.is_set(): + self._borrowers.discard(borrower) + self._notify_next_waiter() + + raise + else: + try: + await AsyncIOBackend.cancel_shielded_checkpoint() + except BaseException: + self.release() + raise + + def release(self) -> None: + self.release_on_behalf_of(current_task()) + + def release_on_behalf_of(self, borrower: object) -> None: + try: + self._borrowers.remove(borrower) + except KeyError: + raise RuntimeError( + "this borrower isn't holding any of this CapacityLimiter's tokens" + ) from None + + self._notify_next_waiter() + + def statistics(self) -> CapacityLimiterStatistics: + return CapacityLimiterStatistics( + self.borrowed_tokens, + self.total_tokens, + tuple(self._borrowers), + len(self._wait_queue), + ) + + +_default_thread_limiter: RunVar[CapacityLimiter] = RunVar("_default_thread_limiter") + + +# +# Operating system signals +# + + +class _SignalReceiver: + def __init__(self, signals: tuple[Signals, ...]): + self._signals = signals + self._loop = get_running_loop() + self._signal_queue: deque[Signals] = deque() + self._future: asyncio.Future = asyncio.Future() + self._handled_signals: set[Signals] = set() + + def _deliver(self, signum: Signals) -> None: + self._signal_queue.append(signum) + if not self._future.done(): + self._future.set_result(None) + + def __enter__(self) -> Self: + for sig in set(self._signals): + self._loop.add_signal_handler(sig, self._deliver, sig) + self._handled_signals.add(sig) + + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + for sig in self._handled_signals: + self._loop.remove_signal_handler(sig) + + def __aiter__(self) -> _SignalReceiver: + return self + + async def __anext__(self) -> Signals: + await AsyncIOBackend.checkpoint() + if not self._signal_queue: + self._future = asyncio.Future() + await self._future + + return self._signal_queue.popleft() + + +# +# Testing and debugging +# + + +class AsyncIOTaskInfo(TaskInfo): + def __init__(self, task: asyncio.Task): + task_state = _task_states.get(task) + if task_state is None: + parent_id = None + else: + parent_id = task_state.parent_id + + coro = task.get_coro() + assert coro is not None, "created TaskInfo from a completed Task" + super().__init__(id(task), parent_id, task.get_name(), coro) + self._task = weakref.ref(task) + + def has_pending_cancellation(self) -> bool: + if not (task := self._task()): + # If the task isn't around anymore, it won't have a pending cancellation + return False + + if task._must_cancel or ( # type: ignore[attr-defined] + isinstance(task._fut_waiter, asyncio.Future) # type: ignore[attr-defined] + and task._fut_waiter.cancelled() # type: ignore[attr-defined] + ): + return True + + if task_state := _task_states.get(task): + if cancel_scope := task_state.cancel_scope: + return cancel_scope._effectively_cancelled + + return False + + +class TestRunner(abc.TestRunner): + _send_stream: MemoryObjectSendStream[tuple[Awaitable[Any], asyncio.Future[Any]]] + + def __init__( + self, + *, + debug: bool | None = None, + use_uvloop: bool = False, + loop_factory: Callable[[], AbstractEventLoop] | None = None, + ) -> None: + if use_uvloop and loop_factory is None: + if sys.platform != "win32": + import uvloop + + loop_factory = uvloop.new_event_loop + else: + import winloop + + loop_factory = winloop.new_event_loop + + self._runner = Runner(debug=debug, loop_factory=loop_factory) + self._exceptions: list[BaseException] = [] + self._runner_task: asyncio.Task | None = None + + def __enter__(self) -> Self: + self._runner.__enter__() + self.get_loop().set_exception_handler(self._exception_handler) + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self._runner.__exit__(exc_type, exc_val, exc_tb) + + def get_loop(self) -> AbstractEventLoop: + return self._runner.get_loop() + + def is_running(self) -> bool: + try: + asyncio.get_running_loop() + return True + except RuntimeError: + return False + + def _exception_handler( + self, loop: asyncio.AbstractEventLoop, context: dict[str, Any] + ) -> None: + if isinstance(context.get("exception"), Exception): + self._exceptions.append(context["exception"]) + else: + loop.default_exception_handler(context) + + def _raise_async_exceptions(self) -> None: + # Re-raise any exceptions raised in asynchronous callbacks + if self._exceptions: + exceptions, self._exceptions = self._exceptions, [] + if len(exceptions) == 1: + raise exceptions[0] + elif exceptions: + raise BaseExceptionGroup( + "Multiple exceptions occurred in asynchronous callbacks", exceptions + ) + + async def _run_tests_and_fixtures( + self, + receive_stream: MemoryObjectReceiveStream[ + tuple[Awaitable[T_Retval], asyncio.Future[T_Retval]] + ], + ) -> None: + from _pytest.outcomes import OutcomeException + + with receive_stream, self._send_stream: + async for coro, future in receive_stream: + try: + retval = await coro + except CancelledError as exc: + if not future.cancelled(): + future.cancel(*exc.args) + + raise + except BaseException as exc: + if not future.cancelled(): + future.set_exception(exc) + + if not isinstance(exc, (Exception, OutcomeException)): + raise + else: + if not future.cancelled(): + future.set_result(retval) + + async def _call_in_runner_task( + self, + func: Callable[P, Awaitable[T_Retval]], + /, + *args: P.args, + **kwargs: P.kwargs, + ) -> T_Retval: + if not self._runner_task: + self._send_stream, receive_stream = create_memory_object_stream[ + tuple[Awaitable[Any], asyncio.Future] + ](1) + self._runner_task = self.get_loop().create_task( + self._run_tests_and_fixtures(receive_stream) + ) + + coro = func(*args, **kwargs) + future: asyncio.Future[T_Retval] = self.get_loop().create_future() + self._send_stream.send_nowait((coro, future)) + return await future + + def run_asyncgen_fixture( + self, + fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]], + kwargs: dict[str, Any], + ) -> Iterable[T_Retval]: + asyncgen = fixture_func(**kwargs) + fixturevalue: T_Retval = self.get_loop().run_until_complete( + self._call_in_runner_task(asyncgen.asend, None) + ) + self._raise_async_exceptions() + + yield fixturevalue + + try: + self.get_loop().run_until_complete( + self._call_in_runner_task(asyncgen.asend, None) + ) + except StopAsyncIteration: + self._raise_async_exceptions() + else: + self.get_loop().run_until_complete(asyncgen.aclose()) + raise RuntimeError("Async generator fixture did not stop") + + def run_fixture( + self, + fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]], + kwargs: dict[str, Any], + ) -> T_Retval: + retval = self.get_loop().run_until_complete( + self._call_in_runner_task(fixture_func, **kwargs) + ) + self._raise_async_exceptions() + return retval + + def run_test( + self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] + ) -> None: + from _pytest.outcomes import OutcomeException + + try: + self.get_loop().run_until_complete( + self._call_in_runner_task(test_func, **kwargs) + ) + except Exception as exc: + self._exceptions.append(exc) + except OutcomeException: + raise + except BaseException: + # A BaseException (e.g. KeyboardInterrupt, SystemExit) interrupted the event loop before + # the test completed. Cancel _runner_task so it does not resume when the event + # loop is re-entered during async generator fixture teardown. + if self._runner_task is not None and not self._runner_task.done(): + self._runner_task.cancel() + self._send_stream.close() + try: + self.get_loop().run_until_complete(self._runner_task) + except CancelledError: + pass + finally: + self._runner_task = None + raise + self._raise_async_exceptions() + + +class _ProcessStreamProtocol(asyncio.subprocess.SubprocessStreamProtocol): + """ + A subprocess protocol that allows us to be notified of ``process_exited`` + + asyncio's own ``Process.wait()`` only resolves once every pipe transport has + disconnected so to get same semantics as on trio and uvloop we need this. + """ + + def __init__(self) -> None: + # Match the standard factory for asyncio.create_process + super().__init__(limit=2**16, loop=asyncio.get_running_loop()) + self.exited = asyncio.Event() + + def process_exited(self) -> None: + super().process_exited() + self.exited.set() + + +class AsyncIOBackend(AsyncBackend): + @classmethod + def run( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + options: dict[str, Any], + ) -> T_Retval: + @wraps(func) + async def wrapper() -> T_Retval: + task = cast(asyncio.Task, current_task()) + task.set_name(get_callable_name(func)) + _task_states[task] = TaskState(None, None) + + try: + return await func(*args) + finally: + del _task_states[task] + + debug = options.get("debug", None) + loop_factory = options.get("loop_factory", None) + if loop_factory is None and options.get("use_uvloop", False): + if sys.platform != "win32": + import uvloop + + loop_factory = uvloop.new_event_loop + else: + import winloop + + loop_factory = winloop.new_event_loop + + with Runner(debug=debug, loop_factory=loop_factory) as runner: + return runner.run(wrapper()) + + @classmethod + def current_token(cls) -> object: + return get_running_loop() + + @classmethod + def current_time(cls) -> float: + return get_running_loop().time() + + @classmethod + def cancelled_exception_class(cls) -> type[BaseException]: + return CancelledError + + @classmethod + async def checkpoint(cls) -> None: + await sleep(0) + + @classmethod + async def checkpoint_if_cancelled(cls) -> None: + task = current_task() + if task is None: + return + + try: + cancel_scope = _task_states[task].cancel_scope + except KeyError: + return + + while cancel_scope: + if cancel_scope.cancel_called: + await sleep(0) + elif cancel_scope.shield: + break + else: + cancel_scope = cancel_scope._parent_scope + + @classmethod + async def cancel_shielded_checkpoint(cls) -> None: + with CancelScope(shield=True): + await sleep(0) + + @classmethod + async def sleep(cls, delay: float) -> None: + await sleep(delay) + + @classmethod + def create_cancel_scope( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + return CancelScope(deadline=deadline, shield=shield) + + @classmethod + def current_effective_deadline(cls) -> float: + if (task := current_task()) is None: + return math.inf + + try: + cancel_scope = _task_states[task].cancel_scope + except KeyError: + return math.inf + + deadline = math.inf + while cancel_scope: + deadline = min(deadline, cancel_scope.deadline) + if cancel_scope._cancel_called: + deadline = -math.inf + break + elif cancel_scope.shield: + break + else: + cancel_scope = cancel_scope._parent_scope + + return deadline + + @classmethod + def create_task_group(cls) -> abc.TaskGroup: + return TaskGroup() + + @classmethod + def create_event(cls) -> BaseEvent: + return Event() + + @classmethod + def create_lock(cls, *, fast_acquire: bool) -> BaseLock: + return Lock(fast_acquire=fast_acquire) + + @classmethod + def create_semaphore( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> BaseSemaphore: + return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire) + + @classmethod + def create_capacity_limiter(cls, total_tokens: float) -> BaseCapacityLimiter: + return CapacityLimiter(total_tokens) + + @classmethod + async def run_sync_in_worker_thread( # type: ignore[return] + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + abandon_on_cancel: bool = False, + limiter: BaseCapacityLimiter | None = None, + ) -> T_Retval: + await cls.checkpoint() + + # If this is the first run in this event loop thread, set up the necessary + # variables + try: + idle_workers = _threadpool_idle_workers.get() + workers = _threadpool_workers.get() + except LookupError: + idle_workers = deque() + workers = set() + _threadpool_idle_workers.set(idle_workers) + _threadpool_workers.set(workers) + + async with limiter or cls.current_default_thread_limiter(): + with CancelScope(shield=not abandon_on_cancel) as scope: + future = asyncio.Future[T_Retval]() + root_task = find_root_task() + if not idle_workers: + worker = WorkerThread(root_task, workers, idle_workers) + worker.start() + workers.add(worker) + root_task.add_done_callback(worker.stop, context=Context()) + else: + worker = idle_workers.pop() + + # Prune any other workers that have been idle for MAX_IDLE_TIME + # seconds or longer + now = cls.current_time() + while idle_workers: + if ( + now - idle_workers[0].idle_since + < WorkerThread.MAX_IDLE_TIME + ): + break + + expired_worker = idle_workers.popleft() + expired_worker.root_task.remove_done_callback( + expired_worker.stop + ) + expired_worker.stop() + + context = copy_context() + context.run(set_current_async_library, None) + if abandon_on_cancel or scope._parent_scope is None: + worker_scope = scope + else: + worker_scope = scope._parent_scope + + worker.queue.put_nowait((context, func, args, future, worker_scope)) + return await future + + @classmethod + def check_cancelled(cls) -> None: + scope: CancelScope | None = threadlocals.current_cancel_scope + while scope is not None: + if scope.cancel_called: + raise CancelledError(f"Cancelled via cancel scope {id(scope):x}") + + if scope.shield: + return + + scope = scope._parent_scope + + @classmethod + def run_async_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_co: + async def task_wrapper() -> T_co: + __tracebackhide__ = True + if scope is not None: + task = cast(asyncio.Task, current_task()) + _task_states[task] = TaskState(None, scope) + scope._tasks.add(task) + try: + return await func(*args) + except CancelledError as exc: + raise concurrent.futures.CancelledError(str(exc)) from None + finally: + if scope is not None: + scope._tasks.discard(task) + + loop = cast( + "AbstractEventLoop", token or threadlocals.current_token.native_token + ) + if loop.is_closed(): + raise RunFinishedError + + context = copy_context() + context.run(set_current_async_library, "asyncio") + scope = getattr(threadlocals, "current_cancel_scope", None) + f: concurrent.futures.Future[T_co] = context.run( + asyncio.run_coroutine_threadsafe, task_wrapper(), loop=loop + ) + return f.result() + + @classmethod + def run_sync_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + @wraps(func) + def wrapper() -> None: + try: + set_current_async_library("asyncio") + f.set_result(func(*args)) + except BaseException as exc: + f.set_exception(exc) + if not isinstance(exc, Exception): + raise + + loop = cast( + "AbstractEventLoop", token or threadlocals.current_token.native_token + ) + if loop.is_closed(): + raise RunFinishedError + + f: concurrent.futures.Future[T_Retval] = Future() + loop.call_soon_threadsafe(wrapper) + return f.result() + + @classmethod + async def open_process( + cls, + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None, + stdout: int | IO[Any] | None, + stderr: int | IO[Any] | None, + **kwargs: Any, + ) -> Process: + await cls.checkpoint() + if isinstance(command, PathLike): + command = os.fspath(command) + + # Use loop.subprocess_shell()/subprocess_exec() rather than their + # asyncio.create_subprocess_*() counterparts to get access to + # transport/protocol. + loop = asyncio.get_running_loop() + if isinstance(command, (str, bytes)): + transport, protocol = await loop.subprocess_shell( + _ProcessStreamProtocol, + command, + stdin=stdin, + stdout=stdout, + stderr=stderr, + **kwargs, + ) + else: + transport, protocol = await loop.subprocess_exec( + _ProcessStreamProtocol, + *command, + stdin=stdin, + stdout=stdout, + stderr=stderr, + **kwargs, + ) + + process = asyncio.subprocess.Process(transport, protocol, loop) + stdin_stream = StreamWriterWrapper(process.stdin) if process.stdin else None + stdout_stream = StreamReaderWrapper(process.stdout) if process.stdout else None + stderr_stream = StreamReaderWrapper(process.stderr) if process.stderr else None + return Process( + process, + stdin_stream, + stdout_stream, + stderr_stream, + protocol.exited, + transport, + ) + + @classmethod + def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None: + create_task( + _shutdown_process_pool_on_exit(workers), + name="AnyIO process pool shutdown task", + ) + find_root_task().add_done_callback( + partial(_forcibly_shutdown_process_pool_on_exit, workers) # type:ignore[arg-type] + ) + + @classmethod + async def connect_tcp( + cls, host: str, port: int, local_address: IPSockAddrType | None = None + ) -> abc.SocketStream: + transport, protocol = cast( + tuple[asyncio.Transport, StreamProtocol], + await get_running_loop().create_connection( + StreamProtocol, host, port, local_addr=local_address + ), + ) + transport.pause_reading() + return SocketStream(transport, protocol) + + @classmethod + async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream: + await cls.checkpoint() + loop = get_running_loop() + raw_socket = socket.socket(socket.AF_UNIX) + raw_socket.setblocking(False) + while True: + try: + raw_socket.connect(path) + except BlockingIOError: + f: asyncio.Future = asyncio.Future() + loop.add_writer(raw_socket, f.set_result, None) + f.add_done_callback(lambda _: loop.remove_writer(raw_socket)) + await f + except BaseException: + raw_socket.close() + raise + else: + return UNIXSocketStream(raw_socket) + + @classmethod + def create_tcp_listener(cls, sock: socket.socket) -> SocketListener: + return TCPSocketListener(sock) + + @classmethod + def create_unix_listener(cls, sock: socket.socket) -> SocketListener: + return UNIXSocketListener(sock) + + @classmethod + async def create_udp_socket( + cls, + family: AddressFamily, + local_address: IPSockAddrType | None, + remote_address: IPSockAddrType | None, + reuse_port: bool, + ) -> UDPSocket | ConnectedUDPSocket: + transport, protocol = await get_running_loop().create_datagram_endpoint( + DatagramProtocol, + local_addr=local_address, + remote_addr=remote_address, + family=family, + reuse_port=reuse_port, + ) + if protocol.exception: + transport.close() + raise protocol.exception + + if not remote_address: + return UDPSocket(transport, protocol) + else: + return ConnectedUDPSocket(transport, protocol) + + @classmethod + async def create_unix_datagram_socket( # type: ignore[override] + cls, raw_socket: socket.socket, remote_path: str | bytes | None + ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket: + await cls.checkpoint() + loop = get_running_loop() + + if remote_path: + while True: + try: + raw_socket.connect(remote_path) + except BlockingIOError: + f: asyncio.Future = asyncio.Future() + loop.add_writer(raw_socket, f.set_result, None) + f.add_done_callback(lambda _: loop.remove_writer(raw_socket)) + await f + except BaseException: + raw_socket.close() + raise + else: + return ConnectedUNIXDatagramSocket(raw_socket) + else: + return UNIXDatagramSocket(raw_socket) + + @classmethod + async def getaddrinfo( + cls, + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, + ) -> Sequence[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], + ] + ]: + return await get_running_loop().getaddrinfo( + host, port, family=family, type=type, proto=proto, flags=flags + ) + + @classmethod + async def getnameinfo( + cls, sockaddr: IPSockAddrType, flags: int = 0 + ) -> tuple[str, str]: + return await get_running_loop().getnameinfo(sockaddr, flags) + + @classmethod + async def wait_readable(cls, obj: FileDescriptorLike) -> None: + try: + read_events = _read_events.get() + except LookupError: + read_events = {} + _read_events.set(read_events) + + fd = obj if isinstance(obj, int) else obj.fileno() + if read_events.get(fd): + raise BusyResourceError("reading from") + + loop = get_running_loop() + fut: asyncio.Future[bool] = loop.create_future() + + def cb() -> None: + try: + del read_events[fd] + except KeyError: + pass + else: + remove_reader(fd) + + try: + fut.set_result(True) + except asyncio.InvalidStateError: + pass + + try: + loop.add_reader(fd, cb) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + selector = get_selector() + selector.add_reader(fd, cb) + remove_reader = selector.remove_reader + else: + remove_reader = loop.remove_reader + + read_events[fd] = fut + try: + success = await fut + finally: + try: + del read_events[fd] + except KeyError: + pass + else: + remove_reader(fd) + + if not success: + raise ClosedResourceError + + @classmethod + async def wait_writable(cls, obj: FileDescriptorLike) -> None: + try: + write_events = _write_events.get() + except LookupError: + write_events = {} + _write_events.set(write_events) + + fd = obj if isinstance(obj, int) else obj.fileno() + if write_events.get(fd): + raise BusyResourceError("writing to") + + loop = get_running_loop() + fut: asyncio.Future[bool] = loop.create_future() + + def cb() -> None: + try: + del write_events[fd] + except KeyError: + pass + else: + remove_writer(fd) + + try: + fut.set_result(True) + except asyncio.InvalidStateError: + pass + + try: + loop.add_writer(fd, cb) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + selector = get_selector() + selector.add_writer(fd, cb) + remove_writer = selector.remove_writer + else: + remove_writer = loop.remove_writer + + write_events[fd] = fut + try: + success = await fut + finally: + try: + del write_events[fd] + except KeyError: + pass + else: + remove_writer(fd) + + if not success: + raise ClosedResourceError + + @classmethod + def notify_closing(cls, obj: FileDescriptorLike) -> None: + fd = obj if isinstance(obj, int) else obj.fileno() + loop = get_running_loop() + + try: + write_events = _write_events.get() + except LookupError: + pass + else: + try: + fut = write_events.pop(fd) + except KeyError: + pass + else: + try: + fut.set_result(False) + except asyncio.InvalidStateError: + pass + + try: + loop.remove_writer(fd) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + get_selector().remove_writer(fd) + + try: + read_events = _read_events.get() + except LookupError: + pass + else: + try: + fut = read_events.pop(fd) + except KeyError: + pass + else: + try: + fut.set_result(False) + except asyncio.InvalidStateError: + pass + + try: + loop.remove_reader(fd) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + get_selector().remove_reader(fd) + + @classmethod + async def wrap_listener_socket(cls, sock: socket.socket) -> SocketListener: + if hasattr(socket, "AF_UNIX") and sock.family == socket.AF_UNIX: + return UNIXSocketListener(sock) + + return TCPSocketListener(sock) + + @classmethod + async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream: + transport, protocol = await get_running_loop().create_connection( + StreamProtocol, sock=sock + ) + return SocketStream(transport, protocol) + + @classmethod + async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream: + return UNIXSocketStream(sock) + + @classmethod + async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket: + transport, protocol = await get_running_loop().create_datagram_endpoint( + DatagramProtocol, sock=sock + ) + return UDPSocket(transport, protocol) + + @classmethod + async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket: + transport, protocol = await get_running_loop().create_datagram_endpoint( + DatagramProtocol, sock=sock + ) + return ConnectedUDPSocket(transport, protocol) + + @classmethod + async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket: + return UNIXDatagramSocket(sock) + + @classmethod + async def wrap_connected_unix_datagram_socket( + cls, sock: socket.socket + ) -> ConnectedUNIXDatagramSocket: + return ConnectedUNIXDatagramSocket(sock) + + @classmethod + def current_default_thread_limiter(cls) -> CapacityLimiter: + try: + return _default_thread_limiter.get() + except LookupError: + limiter = CapacityLimiter(40) + _default_thread_limiter.set(limiter) + return limiter + + @classmethod + def open_signal_receiver( + cls, *signals: Signals + ) -> AbstractContextManager[AsyncIterator[Signals]]: + return _SignalReceiver(signals) + + @classmethod + def get_current_task(cls) -> TaskInfo: + return AsyncIOTaskInfo(current_task()) # type: ignore[arg-type] + + @classmethod + def get_running_tasks(cls) -> Sequence[TaskInfo]: + return [AsyncIOTaskInfo(task) for task in all_tasks() if not task.done()] + + @classmethod + async def wait_all_tasks_blocked(cls) -> None: + await cls.checkpoint() + this_task = current_task() + while True: + for task in all_tasks(): + if task is this_task: + continue + + waiter = task._fut_waiter # type: ignore[attr-defined] + if waiter is None or waiter.done(): + await sleep(0.1) + break + else: + return + + @classmethod + def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: + return TestRunner(**options) + + +backend_class = AsyncIOBackend diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_backends/_trio.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_backends/_trio.py new file mode 100644 index 0000000000000000000000000000000000000000..2e67786e81a6f41976ad2467f818a205ba5637e9 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_backends/_trio.py @@ -0,0 +1,1473 @@ +from __future__ import annotations + +import array +import math +import os +import socket +import sys +import types +import weakref +from collections.abc import ( + AsyncGenerator, + AsyncIterator, + Awaitable, + Callable, + Collection, + Coroutine, + Iterable, + Sequence, +) +from contextlib import AbstractContextManager +from contextvars import Context +from dataclasses import dataclass +from functools import partial, wraps +from io import IOBase +from os import PathLike +from signal import Signals +from socket import AddressFamily, SocketKind +from types import TracebackType +from typing import ( + IO, + TYPE_CHECKING, + Any, + Generic, + Literal, + NoReturn, + ParamSpec, + TypeVar, + cast, + overload, +) + +import trio.from_thread +import trio.lowlevel +from outcome import Error, Outcome, Value +from trio.lowlevel import ( + current_root_task, + current_task, + notify_closing, + wait_readable, + wait_writable, +) +from trio.socket import SocketType as TrioSocketType +from trio.to_thread import run_sync + +from .. import ( + CapacityLimiterStatistics, + EventStatistics, + LockStatistics, + RunFinishedError, + TaskInfo, + WouldBlock, + abc, +) +from .._core._eventloop import claim_worker_thread +from .._core._exceptions import ( + BrokenResourceError, + BusyResourceError, + ClosedResourceError, + EndOfStream, +) +from .._core._sockets import convert_ipv6_sockaddr +from .._core._streams import create_memory_object_stream +from .._core._synchronization import ( + CapacityLimiter as BaseCapacityLimiter, +) +from .._core._synchronization import Event as BaseEvent +from .._core._synchronization import Lock as BaseLock +from .._core._synchronization import ( + ResourceGuard, + SemaphoreStatistics, +) +from .._core._synchronization import Semaphore as BaseSemaphore +from .._core._tasks import CancelScope as BaseCancelScope +from .._core._tasks import TaskHandle +from ..abc import IPSockAddrType, UDPPacketType, UNIXDatagramPacketType +from ..abc._eventloop import AsyncBackend, StrOrBytesPath +from ..abc._tasks import T_contra, call_for_coroutine, get_callable_name, get_coro_name + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike + + from ..streams.memory import MemoryObjectSendStream + +if sys.version_info < (3, 15): + from typing_extensions import sentinel + +if sys.version_info >= (3, 11): + from typing import Self, TypeVarTuple, Unpack +else: + from exceptiongroup import BaseExceptionGroup + from typing_extensions import Self, TypeVarTuple, Unpack + +T = TypeVar("T") +T_Retval = TypeVar("T_Retval") +T_co = TypeVar("T_co", covariant=True) +T_SockAddr = TypeVar("T_SockAddr", str, IPSockAddrType) +PosArgsT = TypeVarTuple("PosArgsT") +P = ParamSpec("P") + + +def ensure_returns_coro( + func: Callable[P, Awaitable[T_Retval]], +) -> Callable[P, Coroutine[Any, Any, T_Retval]]: + @wraps(func) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, T_Retval]: + awaitable = func(*args, **kwargs) + # Check the common case first. + if isinstance(awaitable, Coroutine): + return awaitable + elif not isinstance(awaitable, Awaitable): + # The user violated the type annotations. Still, we should pass this on to + # Trio so it can raise with an appropriate message. + return awaitable + else: + + @wraps(func) + async def inner_wrapper() -> T_Retval: + return await awaitable + + return inner_wrapper() + + return wrapper + + +# +# Event loop +# + +RunVar = trio.lowlevel.RunVar + + +# +# Timeouts and cancellation +# + + +class CancelScope(BaseCancelScope): + __slots__ = ("__original",) + + def __new__( + cls, original: trio.CancelScope | None = None, **kwargs: object + ) -> Self: + return object.__new__(cls) + + def __init__(self, original: trio.CancelScope | None = None, **kwargs: Any) -> None: + self.__original = original or trio.CancelScope(**kwargs) + + def __enter__(self) -> Self: + self.__original.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + return self.__original.__exit__(exc_type, exc_val, exc_tb) + + def cancel(self, reason: str | None = None) -> None: + self.__original.cancel(reason) + + @property + def deadline(self) -> float: + return self.__original.deadline + + @deadline.setter + def deadline(self, value: float) -> None: + self.__original.deadline = value + + @property + def cancel_called(self) -> bool: + return self.__original.cancel_called + + @property + def cancelled_caught(self) -> bool: + return self.__original.cancelled_caught + + @property + def shield(self) -> bool: + return self.__original.shield + + @shield.setter + def shield(self, value: bool) -> None: + self.__original.shield = value + + +# +# Task groups +# + +empty_start_value = sentinel("empty_start_value") + + +class _TrioTaskStatus(abc.TaskStatus[T_contra], Generic[T_contra]): + early_start_value: T_contra | object = empty_start_value + real_task_status: trio.TaskStatus[T_contra | None] | None = None + + def started(self, value: T_contra | None = None) -> None: + if self.real_task_status is None: + if self.early_start_value is not empty_start_value: + raise RuntimeError("called 'started' twice on the same task status") + + self.early_start_value = value + else: + self.real_task_status.started(value) + + +class TaskGroup(abc.TaskGroup): + def __init__(self) -> None: + self._entered = False + self._active = False + self._nursery_manager = trio.open_nursery(strict_exception_groups=True) + self.cancel_scope = None # type: ignore[assignment] + + async def __aenter__(self) -> Self: + if self._entered: + raise RuntimeError("TaskGroup cannot be entered more than once") + + self._entered = True + self._active = True + self._nursery = await self._nursery_manager.__aenter__() + self.cancel_scope = CancelScope(self._nursery.cancel_scope) + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + try: + # trio.Nursery.__exit__ returns bool; .open_nursery has wrong type + return await self._nursery_manager.__aexit__(exc_type, exc_val, exc_tb) # type: ignore[return-value] + except BaseExceptionGroup as exc: + if not exc.split(trio.Cancelled)[1]: + raise trio.Cancelled._create() from exc + + raise + finally: + del exc_val, exc_tb + self._active = False + + def _check_active(self, coro: Coroutine | None = None) -> None: + if not self._active: + if coro is not None: + coro.close() + + raise RuntimeError( + "This task group is not active; no new tasks can be started." + ) + + def create_task( + self, + coro: Coroutine[Any, Any, T_co], + *, + name: object = None, + context: Context | None = None, + ) -> TaskHandle[T_co]: + if not isinstance(coro, Coroutine): + raise TypeError(f"expected a coroutine, got {coro.__class__.__qualname__}") + + self._check_active(coro) + final_name = get_coro_name(coro, name) + handle = TaskHandle(coro, final_name) + if context is not None: + context.run( + partial(self._nursery.start_soon, handle._run_coro, name=handle.name) + ) + else: + self._nursery.start_soon(handle._run_coro, name=handle.name) + + return handle + + async def start( + self, + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + *args: Unpack[PosArgsT], + name: object = None, + return_handle: Literal[False, True] = False, + ) -> Any: + handle: TaskHandle[T_co] + + async def run_coro_with_task_status( + *, task_status: trio.TaskStatus[Any] + ) -> None: + nonlocal handle + wrapper_task_status = _TrioTaskStatus() + coro = call_for_coroutine(func, args, task_status=wrapper_task_status) + if wrapper_task_status.early_start_value is not empty_start_value: + task_status.started(wrapper_task_status.early_start_value) + else: + wrapper_task_status.real_task_status = task_status + + handle = TaskHandle(coro, final_name) + await handle._run_coro() + + self._check_active() + final_name = get_callable_name(func, name) + start_value = await self._nursery.start( + run_coro_with_task_status, name=final_name + ) + if return_handle: + handle._start_value = start_value + return handle + else: + return start_value + + +# +# Subprocesses +# + + +@dataclass(eq=False) +class ReceiveStreamWrapper(abc.ByteReceiveStream): + _stream: trio.abc.ReceiveStream + + async def receive(self, max_bytes: int | None = None) -> bytes: + if max_bytes is not None and max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + try: + data = await self._stream.receive_some(max_bytes) + except trio.ClosedResourceError as exc: + raise ClosedResourceError from exc.__cause__ + except trio.BrokenResourceError as exc: + raise BrokenResourceError from exc.__cause__ + + if data: + return bytes(data) + else: + raise EndOfStream + + async def aclose(self) -> None: + await self._stream.aclose() + + +@dataclass(eq=False) +class SendStreamWrapper(abc.ByteSendStream): + _stream: trio.abc.SendStream + + async def send(self, item: bytes) -> None: + try: + await self._stream.send_all(item) + except trio.ClosedResourceError as exc: + raise ClosedResourceError from exc.__cause__ + except trio.BrokenResourceError as exc: + raise BrokenResourceError from exc.__cause__ + + async def aclose(self) -> None: + await self._stream.aclose() + + +@dataclass(eq=False) +class Process(abc.Process): + _process: trio.Process + _stdin: abc.ByteSendStream | None + _stdout: abc.ByteReceiveStream | None + _stderr: abc.ByteReceiveStream | None + + async def aclose(self) -> None: + with CancelScope(shield=True): + if self._stdin: + await self._stdin.aclose() + if self._stdout: + await self._stdout.aclose() + if self._stderr: + await self._stderr.aclose() + + try: + await self.wait() + except BaseException: + self.kill() + with CancelScope(shield=True): + await self.wait() + raise + + async def wait(self) -> int: + return await self._process.wait() + + def terminate(self) -> None: + self._process.terminate() + + def kill(self) -> None: + self._process.kill() + + def send_signal(self, signal: Signals) -> None: + self._process.send_signal(signal) + + @property + def pid(self) -> int: + return self._process.pid + + @property + def returncode(self) -> int | None: + return self._process.returncode + + @property + def stdin(self) -> abc.ByteSendStream | None: + return self._stdin + + @property + def stdout(self) -> abc.ByteReceiveStream | None: + return self._stdout + + @property + def stderr(self) -> abc.ByteReceiveStream | None: + return self._stderr + + +class _ProcessPoolShutdownInstrument(trio.abc.Instrument): + def after_run(self) -> None: + super().after_run() + + +current_default_worker_process_limiter: trio.lowlevel.RunVar = RunVar( + "current_default_worker_process_limiter" +) + + +async def _shutdown_process_pool(workers: set[abc.Process]) -> None: + try: + await trio.sleep(math.inf) + except trio.Cancelled: + for process in workers: + if process.returncode is None: + process.kill() + + with CancelScope(shield=True): + for process in workers: + await process.aclose() + + +# +# Sockets and networking +# + + +class _TrioSocketMixin(Generic[T_SockAddr]): + def __init__(self, trio_socket: TrioSocketType) -> None: + self._trio_socket = trio_socket + self._closed = False + + def _check_closed(self) -> None: + if self._closed: + raise ClosedResourceError + if self._trio_socket.fileno() < 0: + raise BrokenResourceError + + @property + def _raw_socket(self) -> socket.socket: + return self._trio_socket._sock # type: ignore[attr-defined] + + async def aclose(self) -> None: + if self._trio_socket.fileno() >= 0: + self._closed = True + self._trio_socket.close() + + def _convert_socket_error(self, exc: BaseException) -> NoReturn: + if isinstance(exc, trio.ClosedResourceError): + raise ClosedResourceError from exc + elif self._trio_socket.fileno() < 0 and self._closed: + raise ClosedResourceError from None + elif isinstance(exc, OSError): + raise BrokenResourceError from exc + else: + raise exc + + +class SocketStream(_TrioSocketMixin, abc.SocketStream): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + with self._receive_guard: + try: + data = await self._trio_socket.recv(max_bytes) + except BaseException as exc: + self._convert_socket_error(exc) + + if data: + return data + else: + raise EndOfStream + + async def send(self, item: bytes) -> None: + with self._send_guard: + view = memoryview(item) + while view: + try: + bytes_sent = await self._trio_socket.send(view) + except BaseException as exc: + self._convert_socket_error(exc) + + view = view[bytes_sent:] + + async def send_eof(self) -> None: + self._trio_socket.shutdown(socket.SHUT_WR) + + +class UNIXSocketStream(SocketStream, abc.UNIXSocketStream): + async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: + if not isinstance(msglen, int) or msglen < 0: + raise ValueError("msglen must be a non-negative integer") + if not isinstance(maxfds, int) or maxfds < 1: + raise ValueError("maxfds must be a positive integer") + + fds = array.array("i") + await trio.lowlevel.checkpoint() + with self._receive_guard: + while True: + try: + message, ancdata, _flags, _addr = await self._trio_socket.recvmsg( + msglen, socket.CMSG_LEN(maxfds * fds.itemsize) + ) + except BaseException as exc: + self._convert_socket_error(exc) + else: + if not message and not ancdata: + raise EndOfStream + + break + + for cmsg_level, cmsg_type, cmsg_data in ancdata: + if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS: + raise RuntimeError( + f"Received unexpected ancillary data; message = {message!r}, " + f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}" + ) + + fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) + + return message, list(fds) + + async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: + if not message: + raise ValueError("message must not be empty") + if not fds: + raise ValueError("fds must not be empty") + + filenos: list[int] = [] + for fd in fds: + if isinstance(fd, int): + filenos.append(fd) + elif isinstance(fd, IOBase): + filenos.append(fd.fileno()) + + fdarray = array.array("i", filenos) + await trio.lowlevel.checkpoint() + with self._send_guard: + while True: + try: + await self._trio_socket.sendmsg( + [message], + [ + ( + socket.SOL_SOCKET, + socket.SCM_RIGHTS, + fdarray, + ) + ], + ) + break + except BaseException as exc: + self._convert_socket_error(exc) + + +class TCPSocketListener(_TrioSocketMixin, abc.SocketListener): + def __init__(self, raw_socket: socket.socket): + super().__init__(trio.socket.from_stdlib_socket(raw_socket)) + self._accept_guard = ResourceGuard("accepting connections from") + + async def accept(self) -> SocketStream: + with self._accept_guard: + try: + trio_socket, _addr = await self._trio_socket.accept() + except BaseException as exc: + self._convert_socket_error(exc) + + trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + return SocketStream(trio_socket) + + +class UNIXSocketListener(_TrioSocketMixin, abc.SocketListener): + def __init__(self, raw_socket: socket.socket): + super().__init__(trio.socket.from_stdlib_socket(raw_socket)) + self._accept_guard = ResourceGuard("accepting connections from") + + async def accept(self) -> UNIXSocketStream: + with self._accept_guard: + try: + trio_socket, _addr = await self._trio_socket.accept() + except BaseException as exc: + self._convert_socket_error(exc) + + return UNIXSocketStream(trio_socket) + + +class UDPSocket(_TrioSocketMixin[IPSockAddrType], abc.UDPSocket): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> tuple[bytes, IPSockAddrType]: + with self._receive_guard: + try: + data, addr = await self._trio_socket.recvfrom(65536) + return data, convert_ipv6_sockaddr(addr) + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: UDPPacketType) -> None: + with self._send_guard: + try: + await self._trio_socket.sendto(*item) + except BaseException as exc: + self._convert_socket_error(exc) + + +class ConnectedUDPSocket(_TrioSocketMixin[IPSockAddrType], abc.ConnectedUDPSocket): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> bytes: + with self._receive_guard: + try: + return await self._trio_socket.recv(65536) + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: bytes) -> None: + with self._send_guard: + try: + await self._trio_socket.send(item) + except BaseException as exc: + self._convert_socket_error(exc) + + +class UNIXDatagramSocket(_TrioSocketMixin[str], abc.UNIXDatagramSocket): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> UNIXDatagramPacketType: + with self._receive_guard: + try: + data, addr = await self._trio_socket.recvfrom(65536) + return data, addr + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: UNIXDatagramPacketType) -> None: + with self._send_guard: + try: + await self._trio_socket.sendto(*item) + except BaseException as exc: + self._convert_socket_error(exc) + + +class ConnectedUNIXDatagramSocket( + _TrioSocketMixin[str], abc.ConnectedUNIXDatagramSocket +): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> bytes: + with self._receive_guard: + try: + return await self._trio_socket.recv(65536) + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: bytes) -> None: + with self._send_guard: + try: + await self._trio_socket.send(item) + except BaseException as exc: + self._convert_socket_error(exc) + + +# +# Synchronization +# + + +class Event(BaseEvent): + __slots__ = ("__original",) + + def __new__(cls) -> Self: + return object.__new__(cls) + + def __init__(self) -> None: + self.__original = trio.Event() + + def is_set(self) -> bool: + return self.__original.is_set() + + async def wait(self) -> None: + return await self.__original.wait() + + def statistics(self) -> EventStatistics: + orig_statistics = self.__original.statistics() + return EventStatistics(tasks_waiting=orig_statistics.tasks_waiting) + + def set(self) -> None: + self.__original.set() + + +class Lock(BaseLock): + __slots__ = "__original", "_fast_acquire" + + def __new__(cls, *, fast_acquire: bool = False) -> Self: + return object.__new__(cls) + + def __init__(self, *, fast_acquire: bool = False) -> None: + self._fast_acquire = fast_acquire + self.__original = trio.Lock() + + @staticmethod + def _convert_runtime_error_msg(exc: RuntimeError) -> None: + if exc.args == ("attempt to re-acquire an already held Lock",): + exc.args = ("Attempted to acquire an already held Lock",) + + async def acquire(self) -> None: + if not self._fast_acquire: + try: + await self.__original.acquire() + except RuntimeError as exc: + self._convert_runtime_error_msg(exc) + raise + + return + + # This is the "fast path" where we don't let other tasks run + await trio.lowlevel.checkpoint_if_cancelled() + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + await self.__original._lot.park() + except RuntimeError as exc: + self._convert_runtime_error_msg(exc) + raise + + def acquire_nowait(self) -> None: + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + raise WouldBlock from None + except RuntimeError as exc: + self._convert_runtime_error_msg(exc) + raise + + def locked(self) -> bool: + return self.__original.locked() + + def release(self) -> None: + self.__original.release() + + def statistics(self) -> LockStatistics: + orig_statistics = self.__original.statistics() + owner = TrioTaskInfo(orig_statistics.owner) if orig_statistics.owner else None + return LockStatistics( + orig_statistics.locked, owner, orig_statistics.tasks_waiting + ) + + +class Semaphore(BaseSemaphore): + __slots__ = ("__original",) + + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Self: + return object.__new__(cls) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> None: + super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire) + self.__original = trio.Semaphore(initial_value, max_value=max_value) + + async def acquire(self) -> None: + if not self._fast_acquire: + await self.__original.acquire() + return + + # This is the "fast path" where we don't let other tasks run + await trio.lowlevel.checkpoint_if_cancelled() + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + await self.__original._lot.park() + + def acquire_nowait(self) -> None: + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + raise WouldBlock from None + + @property + def max_value(self) -> int | None: + return self.__original.max_value + + @property + def value(self) -> int: + return self.__original.value + + def release(self) -> None: + self.__original.release() + + def statistics(self) -> SemaphoreStatistics: + orig_statistics = self.__original.statistics() + return SemaphoreStatistics(orig_statistics.tasks_waiting) + + +class CapacityLimiter(BaseCapacityLimiter): + __slots__ = ("__original",) + + def __new__( + cls, + total_tokens: float | None = None, + *, + original: trio.CapacityLimiter | None = None, + ) -> Self: + return object.__new__(cls) + + def __init__( + self, + total_tokens: float | None = None, + *, + original: trio.CapacityLimiter | None = None, + ) -> None: + if original is not None: + self.__original = original + else: + assert total_tokens is not None + self.__original = trio.CapacityLimiter(total_tokens) + + async def __aenter__(self) -> None: + return await self.__original.__aenter__() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.__original.__aexit__(exc_type, exc_val, exc_tb) + + @property + def total_tokens(self) -> float: + return self.__original.total_tokens + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + self.__original.total_tokens = value + + @property + def borrowed_tokens(self) -> int: + return self.__original.borrowed_tokens + + @property + def available_tokens(self) -> float: + return self.__original.available_tokens + + def acquire_nowait(self) -> None: + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + raise WouldBlock from None + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + try: + self.__original.acquire_on_behalf_of_nowait(borrower) + except trio.WouldBlock: + raise WouldBlock from None + + async def acquire(self) -> None: + await self.__original.acquire() + + async def acquire_on_behalf_of(self, borrower: object) -> None: + await self.__original.acquire_on_behalf_of(borrower) + + def release(self) -> None: + return self.__original.release() + + def release_on_behalf_of(self, borrower: object) -> None: + return self.__original.release_on_behalf_of(borrower) + + def statistics(self) -> CapacityLimiterStatistics: + orig = self.__original.statistics() + return CapacityLimiterStatistics( + borrowed_tokens=orig.borrowed_tokens, + total_tokens=orig.total_tokens, + borrowers=tuple(orig.borrowers), + tasks_waiting=orig.tasks_waiting, + ) + + +_capacity_limiter_wrapper: trio.lowlevel.RunVar = RunVar("_capacity_limiter_wrapper") + + +# +# Signal handling +# + + +class _SignalReceiver: + _iterator: AsyncIterator[int] + + def __init__(self, signals: tuple[Signals, ...]): + self._signals = signals + + def __enter__(self) -> Self: + self._cm = trio.open_signal_receiver(*self._signals) + self._iterator = self._cm.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool | None: + return self._cm.__exit__(exc_type, exc_val, exc_tb) + + def __aiter__(self) -> _SignalReceiver: + return self + + async def __anext__(self) -> Signals: + signum = await self._iterator.__anext__() + return Signals(signum) + + +# +# Testing and debugging +# + + +class TestRunner(abc.TestRunner): + def __init__(self, **options: Any) -> None: + from queue import Queue + + self._call_queue: Queue[Callable[[], object]] = Queue() + self._send_stream: ( + MemoryObjectSendStream[tuple[Awaitable[Any], list[Outcome]]] | None + ) = None + self._options = options + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> None: + if self._send_stream: + self._send_stream.close() + while self._send_stream is not None: + self._call_queue.get()() + + def is_running(self) -> bool: + return trio.lowlevel.in_trio_task() + + async def _run_tests_and_fixtures(self) -> None: + self._send_stream, receive_stream = create_memory_object_stream[ + tuple[Awaitable[Any], list[Outcome]] + ](1) + with receive_stream: + async for awaitable, outcome_holder in receive_stream: + try: + retval = await awaitable + except BaseException as exc: + outcome_holder.append(Error(exc)) + else: + outcome_holder.append(Value(retval)) + + def _main_task_finished(self, outcome: object) -> None: + self._send_stream = None + + def _call_in_runner_task( + self, + func: Callable[P, Awaitable[T_Retval]], + /, + *args: P.args, + **kwargs: P.kwargs, + ) -> T_Retval: + if self._send_stream is None: + trio.lowlevel.start_guest_run( + self._run_tests_and_fixtures, + run_sync_soon_threadsafe=self._call_queue.put, + done_callback=self._main_task_finished, + **self._options, + ) + while self._send_stream is None: + self._call_queue.get()() + + outcome_holder: list[Outcome] = [] + self._send_stream.send_nowait((func(*args, **kwargs), outcome_holder)) + while not outcome_holder: + self._call_queue.get()() + + return outcome_holder[0].unwrap() + + def run_asyncgen_fixture( + self, + fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]], + kwargs: dict[str, Any], + ) -> Iterable[T_Retval]: + asyncgen = fixture_func(**kwargs) + fixturevalue: T_Retval = self._call_in_runner_task(asyncgen.asend, None) + + yield fixturevalue + + try: + self._call_in_runner_task(asyncgen.asend, None) + except StopAsyncIteration: + pass + else: + self._call_in_runner_task(asyncgen.aclose) + raise RuntimeError("Async generator fixture did not stop") + + def run_fixture( + self, + fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]], + kwargs: dict[str, Any], + ) -> T_Retval: + return self._call_in_runner_task(fixture_func, **kwargs) + + def run_test( + self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] + ) -> None: + self._call_in_runner_task(test_func, **kwargs) + + +class TrioTaskInfo(TaskInfo): + def __init__(self, task: trio.lowlevel.Task): + parent_id = None + if task.parent_nursery and task.parent_nursery.parent_task: + parent_id = id(task.parent_nursery.parent_task) + + super().__init__(id(task), parent_id, task.name, task.coro) + self._task = weakref.proxy(task) + + def has_pending_cancellation(self) -> bool: + try: + return self._task._cancel_status.effectively_cancelled + except ReferenceError: + # If the task is no longer around, it surely doesn't have a cancellation + # pending + return False + + +class TrioBackend(AsyncBackend): + @classmethod + def run( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + options: dict[str, Any], + ) -> T_Retval: + assert not kwargs, "unreachable, and not supported by Trio" + return trio.run(ensure_returns_coro(func), *args, **options) + + @classmethod + def current_token(cls) -> object: + return trio.lowlevel.current_trio_token() + + @classmethod + def current_time(cls) -> float: + return trio.current_time() + + @classmethod + def cancelled_exception_class(cls) -> type[BaseException]: + return trio.Cancelled + + @classmethod + async def checkpoint(cls) -> None: + await trio.lowlevel.checkpoint() + + @classmethod + async def checkpoint_if_cancelled(cls) -> None: + await trio.lowlevel.checkpoint_if_cancelled() + + @classmethod + async def cancel_shielded_checkpoint(cls) -> None: + await trio.lowlevel.cancel_shielded_checkpoint() + + @classmethod + async def sleep(cls, delay: float) -> None: + await trio.sleep(delay) + + @classmethod + def create_cancel_scope( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> BaseCancelScope: + return CancelScope(deadline=deadline, shield=shield) + + @classmethod + def current_effective_deadline(cls) -> float: + return trio.current_effective_deadline() + + @classmethod + def create_task_group(cls) -> abc.TaskGroup: + return TaskGroup() + + @classmethod + def create_event(cls) -> BaseEvent: + return Event() + + @classmethod + def create_lock(cls, *, fast_acquire: bool) -> Lock: + return Lock(fast_acquire=fast_acquire) + + @classmethod + def create_semaphore( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> BaseSemaphore: + return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire) + + @classmethod + def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter: + return CapacityLimiter(total_tokens) + + @classmethod + async def run_sync_in_worker_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + abandon_on_cancel: bool = False, + limiter: BaseCapacityLimiter | None = None, + ) -> T_Retval: + def wrapper() -> T_Retval: + with claim_worker_thread(TrioBackend, token): + return func(*args) + + token = TrioBackend.current_token() + return await run_sync( + wrapper, + abandon_on_cancel=abandon_on_cancel, + limiter=cast(trio.CapacityLimiter, limiter), + ) + + @classmethod + def check_cancelled(cls) -> None: + trio.from_thread.check_cancelled() + + @classmethod + def run_async_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_co: + trio_token = cast("trio.lowlevel.TrioToken | None", token) + try: + return trio.from_thread.run(func, *args, trio_token=trio_token) + except trio.RunFinishedError: + raise RunFinishedError from None + + @classmethod + def run_sync_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + trio_token = cast("trio.lowlevel.TrioToken | None", token) + try: + return trio.from_thread.run_sync(func, *args, trio_token=trio_token) + except trio.RunFinishedError: + raise RunFinishedError from None + + @classmethod + async def open_process( + cls, + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None, + stdout: int | IO[Any] | None, + stderr: int | IO[Any] | None, + **kwargs: Any, + ) -> Process: + def convert_item(item: StrOrBytesPath) -> str: + str_or_bytes = os.fspath(item) + if isinstance(str_or_bytes, str): + return str_or_bytes + else: + return os.fsdecode(str_or_bytes) + + if isinstance(command, (str, bytes, PathLike)): + process = await trio.lowlevel.open_process( + convert_item(command), + stdin=stdin, + stdout=stdout, + stderr=stderr, + shell=True, + **kwargs, + ) + else: + process = await trio.lowlevel.open_process( + [convert_item(item) for item in command], + stdin=stdin, + stdout=stdout, + stderr=stderr, + shell=False, + **kwargs, + ) + + stdin_stream = SendStreamWrapper(process.stdin) if process.stdin else None + stdout_stream = ReceiveStreamWrapper(process.stdout) if process.stdout else None + stderr_stream = ReceiveStreamWrapper(process.stderr) if process.stderr else None + return Process(process, stdin_stream, stdout_stream, stderr_stream) + + @classmethod + def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None: + trio.lowlevel.spawn_system_task(_shutdown_process_pool, workers) + + @classmethod + async def connect_tcp( + cls, host: str, port: int, local_address: IPSockAddrType | None = None + ) -> SocketStream: + family = socket.AF_INET6 if ":" in host else socket.AF_INET + trio_socket = trio.socket.socket(family) + trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + if local_address: + await trio_socket.bind(local_address) + + try: + await trio_socket.connect((host, port)) + except BaseException: + trio_socket.close() + raise + + return SocketStream(trio_socket) + + @classmethod + async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream: + trio_socket = trio.socket.socket(socket.AF_UNIX) + try: + await trio_socket.connect(path) + except BaseException: + trio_socket.close() + raise + + return UNIXSocketStream(trio_socket) + + @classmethod + def create_tcp_listener(cls, sock: socket.socket) -> abc.SocketListener: + return TCPSocketListener(sock) + + @classmethod + def create_unix_listener(cls, sock: socket.socket) -> abc.SocketListener: + return UNIXSocketListener(sock) + + @classmethod + async def create_udp_socket( + cls, + family: socket.AddressFamily, + local_address: IPSockAddrType | None, + remote_address: IPSockAddrType | None, + reuse_port: bool, + ) -> UDPSocket | ConnectedUDPSocket: + trio_socket = trio.socket.socket(family=family, type=socket.SOCK_DGRAM) + + if reuse_port: + trio_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + + if local_address: + await trio_socket.bind(local_address) + + if remote_address: + await trio_socket.connect(remote_address) + return ConnectedUDPSocket(trio_socket) + else: + return UDPSocket(trio_socket) + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket.socket, remote_path: None + ) -> abc.UNIXDatagramSocket: ... + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket.socket, remote_path: str | bytes + ) -> abc.ConnectedUNIXDatagramSocket: ... + + @classmethod + async def create_unix_datagram_socket( + cls, raw_socket: socket.socket, remote_path: str | bytes | None + ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket: + trio_socket = trio.socket.from_stdlib_socket(raw_socket) + + if remote_path: + await trio_socket.connect(remote_path) + return ConnectedUNIXDatagramSocket(trio_socket) + else: + return UNIXDatagramSocket(trio_socket) + + @classmethod + async def getaddrinfo( + cls, + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, + ) -> Sequence[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], + ] + ]: + return await trio.socket.getaddrinfo(host, port, family, type, proto, flags) + + @classmethod + async def getnameinfo( + cls, sockaddr: IPSockAddrType, flags: int = 0 + ) -> tuple[str, str]: + return await trio.socket.getnameinfo(sockaddr, flags) + + @classmethod + async def wait_readable(cls, obj: FileDescriptorLike) -> None: + try: + await wait_readable(obj) + except trio.ClosedResourceError as exc: + raise ClosedResourceError().with_traceback(exc.__traceback__) from None + except trio.BusyResourceError: + raise BusyResourceError("reading from") from None + + @classmethod + async def wait_writable(cls, obj: FileDescriptorLike) -> None: + try: + await wait_writable(obj) + except trio.ClosedResourceError as exc: + raise ClosedResourceError().with_traceback(exc.__traceback__) from None + except trio.BusyResourceError: + raise BusyResourceError("writing to") from None + + @classmethod + def notify_closing(cls, obj: FileDescriptorLike) -> None: + notify_closing(obj) + + @classmethod + async def wrap_listener_socket(cls, sock: socket.socket) -> abc.SocketListener: + if hasattr(socket, "AF_UNIX") and sock.family == socket.AF_UNIX: + return UNIXSocketListener(sock) + + return TCPSocketListener(sock) + + @classmethod + async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream: + trio_sock = trio.socket.from_stdlib_socket(sock) + return SocketStream(trio_sock) + + @classmethod + async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream: + trio_sock = trio.socket.from_stdlib_socket(sock) + return UNIXSocketStream(trio_sock) + + @classmethod + async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket: + trio_sock = trio.socket.from_stdlib_socket(sock) + return UDPSocket(trio_sock) + + @classmethod + async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket: + trio_sock = trio.socket.from_stdlib_socket(sock) + return ConnectedUDPSocket(trio_sock) + + @classmethod + async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket: + trio_sock = trio.socket.from_stdlib_socket(sock) + return UNIXDatagramSocket(trio_sock) + + @classmethod + async def wrap_connected_unix_datagram_socket( + cls, sock: socket.socket + ) -> ConnectedUNIXDatagramSocket: + trio_sock = trio.socket.from_stdlib_socket(sock) + return ConnectedUNIXDatagramSocket(trio_sock) + + @classmethod + def current_default_thread_limiter(cls) -> CapacityLimiter: + try: + return _capacity_limiter_wrapper.get() + except LookupError: + limiter = CapacityLimiter( + original=trio.to_thread.current_default_thread_limiter() + ) + _capacity_limiter_wrapper.set(limiter) + return limiter + + @classmethod + def open_signal_receiver( + cls, *signals: Signals + ) -> AbstractContextManager[AsyncIterator[Signals]]: + return _SignalReceiver(signals) + + @classmethod + def get_current_task(cls) -> TaskInfo: + task = current_task() + return TrioTaskInfo(task) + + @classmethod + def get_running_tasks(cls) -> Sequence[TaskInfo]: + root_task = current_root_task() + assert root_task + task_infos = [TrioTaskInfo(root_task)] + nurseries = root_task.child_nurseries + while nurseries: + new_nurseries: list[trio.Nursery] = [] + for nursery in nurseries: + for task in nursery.child_tasks: + task_infos.append(TrioTaskInfo(task)) + new_nurseries.extend(task.child_nurseries) + + nurseries = new_nurseries + + return task_infos + + @classmethod + async def wait_all_tasks_blocked(cls) -> None: + from trio.testing import wait_all_tasks_blocked + + await wait_all_tasks_blocked() + + @classmethod + def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: + return TestRunner(**options) + + +backend_class = TrioBackend diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/__init__.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_asyncio_selector_thread.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_asyncio_selector_thread.py new file mode 100644 index 0000000000000000000000000000000000000000..9f35bae568e33e6a9e1219761c83cc8350fa0532 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_asyncio_selector_thread.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +import asyncio +import socket +import threading +from collections.abc import Callable +from selectors import EVENT_READ, EVENT_WRITE, DefaultSelector +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike + +_selector_lock = threading.Lock() +_selector: Selector | None = None + + +class Selector: + def __init__(self) -> None: + self._thread = threading.Thread(target=self.run, name="AnyIO socket selector") + self._selector = DefaultSelector() + self._send, self._receive = socket.socketpair() + self._send.setblocking(False) + self._receive.setblocking(False) + # This somewhat reduces the amount of memory wasted queueing up data + # for wakeups. With these settings, maximum number of 1-byte sends + # before getting BlockingIOError: + # Linux 4.8: 6 + # macOS (darwin 15.5): 1 + # Windows 10: 525347 + # Windows you're weird. (And on Windows setting SNDBUF to 0 makes send + # blocking, even on non-blocking sockets, so don't do that.) + self._receive.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1) + self._send.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1) + # On Windows this is a TCP socket so this might matter. On other + # platforms this fails b/c AF_UNIX sockets aren't actually TCP. + try: + self._send.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except OSError: + pass + + self._selector.register(self._receive, EVENT_READ) + self._closed = False + + def start(self) -> None: + self._thread.start() + threading._register_atexit(self._stop) # type: ignore[attr-defined] + + def _stop(self) -> None: + global _selector + self._closed = True + self._notify_self() + self._send.close() + self._thread.join() + self._selector.unregister(self._receive) + self._receive.close() + self._selector.close() + _selector = None + assert not self._selector.get_map(), ( + "selector still has registered file descriptors after shutdown" + ) + + def _notify_self(self) -> None: + try: + self._send.send(b"\x00") + except BlockingIOError: + pass + + def add_reader(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None: + loop = asyncio.get_running_loop() + try: + key = self._selector.get_key(fd) + except KeyError: + self._selector.register(fd, EVENT_READ, {EVENT_READ: (loop, callback)}) + else: + if EVENT_READ in key.data: + raise ValueError( + "this file descriptor is already registered for reading" + ) + + key.data[EVENT_READ] = loop, callback + self._selector.modify(fd, key.events | EVENT_READ, key.data) + + self._notify_self() + + def add_writer(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None: + loop = asyncio.get_running_loop() + try: + key = self._selector.get_key(fd) + except KeyError: + self._selector.register(fd, EVENT_WRITE, {EVENT_WRITE: (loop, callback)}) + else: + if EVENT_WRITE in key.data: + raise ValueError( + "this file descriptor is already registered for writing" + ) + + key.data[EVENT_WRITE] = loop, callback + self._selector.modify(fd, key.events | EVENT_WRITE, key.data) + + self._notify_self() + + def remove_reader(self, fd: FileDescriptorLike) -> bool: + try: + key = self._selector.get_key(fd) + except KeyError: + return False + + if new_events := key.events ^ EVENT_READ: + del key.data[EVENT_READ] + self._selector.modify(fd, new_events, key.data) + else: + self._selector.unregister(fd) + + return True + + def remove_writer(self, fd: FileDescriptorLike) -> bool: + try: + key = self._selector.get_key(fd) + except KeyError: + return False + + if new_events := key.events ^ EVENT_WRITE: + del key.data[EVENT_WRITE] + self._selector.modify(fd, new_events, key.data) + else: + self._selector.unregister(fd) + + return True + + def run(self) -> None: + while not self._closed: + for key, events in self._selector.select(): + if key.fileobj is self._receive: + try: + while self._receive.recv(4096): + pass + except BlockingIOError: + pass + + continue + + if events & EVENT_READ: + loop, callback = key.data[EVENT_READ] + self.remove_reader(key.fd) + try: + loop.call_soon_threadsafe(callback) + except RuntimeError: + pass # the loop was already closed + + if events & EVENT_WRITE: + loop, callback = key.data[EVENT_WRITE] + self.remove_writer(key.fd) + try: + loop.call_soon_threadsafe(callback) + except RuntimeError: + pass # the loop was already closed + + +def get_selector() -> Selector: + global _selector + + with _selector_lock: + if _selector is None: + _selector = Selector() + _selector.start() + + return _selector diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_concurrency_utils.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_concurrency_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..f66229a57d7b0d5f793bdfe568a751770939c924 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_concurrency_utils.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +from inspect import iscoroutine + +__all__ = ("amap", "as_completed", "gather") + +from collections.abc import AsyncGenerator, Awaitable, Callable, Coroutine, Iterable +from contextlib import asynccontextmanager +from typing import TYPE_CHECKING, Any, TypeVar, overload + +from ..abc._tasks import get_coro_name +from ._exceptions import BrokenResourceError +from ._streams import create_memory_object_stream +from ._tasks import TaskHandle, create_task_group + +if TYPE_CHECKING: + from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream + +R = TypeVar("R") +S = TypeVar("S") +T = TypeVar("T") +U = TypeVar("U") +V = TypeVar("V") +W = TypeVar("W") + + +@overload +async def gather( + coro1: Coroutine[Any, Any, R], coro2: Coroutine[Any, Any, S], / +) -> tuple[R, S]: ... + + +@overload +async def gather( + coro1: Coroutine[Any, Any, R], + coro2: Coroutine[Any, Any, S], + coro3: Coroutine[Any, Any, T], + /, +) -> tuple[R, S, T]: ... + + +@overload +async def gather( + coro1: Coroutine[Any, Any, R], + coro2: Coroutine[Any, Any, S], + coro3: Coroutine[Any, Any, T], + coro4: Coroutine[Any, Any, U], + /, +) -> tuple[R, S, T, U]: ... + + +@overload +async def gather( + coro1: Coroutine[Any, Any, R], + coro2: Coroutine[Any, Any, S], + coro3: Coroutine[Any, Any, T], + coro4: Coroutine[Any, Any, U], + coro5: Coroutine[Any, Any, V], + /, +) -> tuple[R, S, T, U, V]: ... + + +@overload +async def gather( + coro1: Coroutine[Any, Any, R], + coro2: Coroutine[Any, Any, S], + coro3: Coroutine[Any, Any, T], + coro4: Coroutine[Any, Any, U], + coro5: Coroutine[Any, Any, V], + coro6: Coroutine[Any, Any, W], + /, +) -> tuple[R, S, T, U, V, W]: ... + + +# handle arbitrary length if awaitables are all of the same type +@overload +async def gather(*coros: Coroutine[Any, Any, R]) -> tuple[R, ...]: ... + + +async def gather(*coros: Coroutine[Any, Any, Any]) -> tuple[Any, ...]: + """ + Run coroutines concurrently in a task group. The order of result values corresponds + to the order of coroutines passed. + + :param coros: coroutine objects to run as tasks + :return: task results for each argument in the same order as they were passed + + """ + handles: list[TaskHandle[Any]] = [] + async with create_task_group() as tg: + handles.extend(tg.create_task(coro) for coro in coros) + + return tuple(h.return_value for h in handles) + + +@asynccontextmanager +async def as_completed( + *awaitables: Awaitable[R], +) -> AsyncGenerator[MemoryObjectReceiveStream[TaskHandle[R]]]: + """ + Run awaitable objects concurrently in a task group, returning an iterator which can + be used to get finished task handles in the order they complete. + + :param awaitables: awaitable objects to run as tasks + :return: MemoryObjectReceiveStream for iterating over task handles as tasks complete. + + """ + if not awaitables: + raise ValueError("as_completed() takes at least one awaitable") + + send, recv = create_memory_object_stream[TaskHandle[R]](len(awaitables)) + task_handles: list[TaskHandle[R]] = [] + + async def runner( + awaitable: Awaitable[R], + index: int, + _send: MemoryObjectSendStream[TaskHandle[R]], + ) -> R: + async with _send: + try: + return await awaitable + finally: + try: + _send.send_nowait(task_handles[index]) + except BrokenResourceError: + pass + + async with recv, create_task_group() as tg: + async with send: + for i, awaitable in enumerate(awaitables): + name = get_coro_name(awaitable) if iscoroutine(awaitable) else None + task_handles.append( + tg.start_soon(runner, awaitable, i, send.clone(), name=name) + ) + try: + yield recv + finally: + tg.cancel_scope.cancel() + + +async def amap( + func: Callable[[T], Coroutine[Any, Any, R]], args: Iterable[T] +) -> list[R]: + """ + Run the given coroutine function concurrently for multiple argument values. + + :param func: a coroutine function that takes a single argument + :param args: a sequence of argument values to pass to ``func`` + :return: task results for each argument in the same order as they were passed + + """ + handles: list[TaskHandle[R]] = [] + async with create_task_group() as tg: + handles.extend(tg.start_soon(func, arg) for arg in args) + + return [h.return_value for h in handles] diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_contextmanagers.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_contextmanagers.py new file mode 100644 index 0000000000000000000000000000000000000000..302f32b0c78a7071605b195c55054cfdb0b55f37 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_contextmanagers.py @@ -0,0 +1,200 @@ +from __future__ import annotations + +from abc import abstractmethod +from contextlib import AbstractAsyncContextManager, AbstractContextManager +from inspect import isasyncgen, iscoroutine, isgenerator +from types import TracebackType +from typing import Protocol, TypeVar, cast, final + +_T_co = TypeVar("_T_co", covariant=True) +_ExitT_co = TypeVar("_ExitT_co", covariant=True, bound="bool | None") + + +class _SupportsCtxMgr(Protocol[_T_co, _ExitT_co]): + def __contextmanager__(self) -> AbstractContextManager[_T_co, _ExitT_co]: ... + + +class _SupportsAsyncCtxMgr(Protocol[_T_co, _ExitT_co]): + def __asynccontextmanager__( + self, + ) -> AbstractAsyncContextManager[_T_co, _ExitT_co]: ... + + +class ContextManagerMixin: + """ + Mixin class providing context manager functionality via a generator-based + implementation. + + This class allows you to implement a context manager via :meth:`__contextmanager__` + which should return a generator. The mechanics are meant to mirror those of + :func:`@contextmanager `. + + .. note:: Classes using this mix-in are not reentrant as context managers, meaning + that once you enter it, you can't re-enter before first exiting it. + + .. seealso:: :doc:`contextmanagers` + """ + + __cm: AbstractContextManager[object, bool | None] | None = None + + @final + def __enter__(self: _SupportsCtxMgr[_T_co, bool | None]) -> _T_co: + # Needed for mypy to assume self still has the __cm member + assert isinstance(self, ContextManagerMixin) + if self.__cm is not None: + raise RuntimeError( + f"this {self.__class__.__qualname__} has already been entered" + ) + + cm = self.__contextmanager__() + if not isinstance(cm, AbstractContextManager): + if isgenerator(cm): + raise TypeError( + "__contextmanager__() returned a generator object instead of " + "a context manager. Did you forget to add the @contextmanager " + "decorator?" + ) + + raise TypeError( + f"__contextmanager__() did not return a context manager object, " + f"but {cm.__class__!r}" + ) + + if cm is self: + raise TypeError( + f"{self.__class__.__qualname__}.__contextmanager__() returned " + f"self. Did you forget to add the @contextmanager decorator and a " + f"'yield' statement?" + ) + + value = cm.__enter__() + self.__cm = cm + return value + + @final + def __exit__( + self: _SupportsCtxMgr[object, _ExitT_co], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> _ExitT_co: + # Needed for mypy to assume self still has the __cm member + assert isinstance(self, ContextManagerMixin) + if self.__cm is None: + raise RuntimeError( + f"this {self.__class__.__qualname__} has not been entered yet" + ) + + # Prevent circular references + cm = self.__cm + del self.__cm + + return cast(_ExitT_co, cm.__exit__(exc_type, exc_val, exc_tb)) + + @abstractmethod + def __contextmanager__(self) -> AbstractContextManager[object, bool | None]: + """ + Implement your context manager logic here. + + This method **must** be decorated with + :func:`@contextmanager `. + + .. note:: Remember that the ``yield`` will raise any exception raised in the + enclosed context block, so use a ``finally:`` block to clean up resources! + + :return: a context manager object + """ + + +class AsyncContextManagerMixin: + """ + Mixin class providing async context manager functionality via a generator-based + implementation. + + This class allows you to implement a context manager via + :meth:`__asynccontextmanager__`. The mechanics are meant to mirror those of + :func:`@asynccontextmanager `. + + .. note:: Classes using this mix-in are not reentrant as context managers, meaning + that once you enter it, you can't re-enter before first exiting it. + + .. seealso:: :doc:`contextmanagers` + """ + + __cm: AbstractAsyncContextManager[object, bool | None] | None = None + + @final + async def __aenter__(self: _SupportsAsyncCtxMgr[_T_co, bool | None]) -> _T_co: + # Needed for mypy to assume self still has the __cm member + assert isinstance(self, AsyncContextManagerMixin) + if self.__cm is not None: + raise RuntimeError( + f"this {self.__class__.__qualname__} has already been entered" + ) + + cm = self.__asynccontextmanager__() + if not isinstance(cm, AbstractAsyncContextManager): + if isasyncgen(cm): + raise TypeError( + "__asynccontextmanager__() returned an async generator instead of " + "an async context manager. Did you forget to add the " + "@asynccontextmanager decorator?" + ) + elif iscoroutine(cm): + cm.close() + raise TypeError( + "__asynccontextmanager__() returned a coroutine object instead of " + "an async context manager. Did you forget to add the " + "@asynccontextmanager decorator and a 'yield' statement?" + ) + + raise TypeError( + f"__asynccontextmanager__() did not return an async context manager, " + f"but {cm.__class__!r}" + ) + + if cm is self: + raise TypeError( + f"{self.__class__.__qualname__}.__asynccontextmanager__() returned " + f"self. Did you forget to add the @asynccontextmanager decorator and a " + f"'yield' statement?" + ) + + value = await cm.__aenter__() + self.__cm = cm + return value + + @final + async def __aexit__( + self: _SupportsAsyncCtxMgr[object, _ExitT_co], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> _ExitT_co: + assert isinstance(self, AsyncContextManagerMixin) + if self.__cm is None: + raise RuntimeError( + f"this {self.__class__.__qualname__} has not been entered yet" + ) + + # Prevent circular references + cm = self.__cm + del self.__cm + + return cast(_ExitT_co, await cm.__aexit__(exc_type, exc_val, exc_tb)) + + @abstractmethod + def __asynccontextmanager__( + self, + ) -> AbstractAsyncContextManager[object, bool | None]: + """ + Implement your async context manager logic here. + + This method **must** be decorated with + :func:`@asynccontextmanager `. + + .. note:: Remember that the ``yield`` will raise any exception raised in the + enclosed context block, so use a ``finally:`` block to clean up resources! + + :return: an async context manager object + """ diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_eventloop.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_eventloop.py new file mode 100644 index 0000000000000000000000000000000000000000..a3e2ab1cc172f2fbd94f244ab110cb16aa7eb8eb --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_eventloop.py @@ -0,0 +1,240 @@ +from __future__ import annotations + +import math +import sys +import threading +from collections.abc import Awaitable, Callable, Generator +from contextlib import contextmanager +from contextvars import Token +from importlib import import_module +from typing import TYPE_CHECKING, Any, TypeVar + +from ._exceptions import NoEventLoopError + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +sniffio: Any +try: + import sniffio +except ModuleNotFoundError: + sniffio = None + +if TYPE_CHECKING: + from ..abc import AsyncBackend + +# This must be updated when new backends are introduced +BACKENDS = "asyncio", "trio" + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + +threadlocals = threading.local() +loaded_backends: dict[str, type[AsyncBackend]] = {} + + +def run( + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + *args: Unpack[PosArgsT], + backend: str = "asyncio", + backend_options: dict[str, Any] | None = None, +) -> T_Retval: + """ + Run the given coroutine function in an asynchronous event loop. + + The current thread must not be already running an event loop. + + :param func: a coroutine function + :param args: positional arguments to ``func`` + :param backend: name of the asynchronous event loop implementation – currently + either ``asyncio`` or ``trio`` + :param backend_options: keyword arguments to call the backend ``run()`` + implementation with (documented :ref:`here `) + :return: the return value of the coroutine function + :raises RuntimeError: if an asynchronous event loop is already running in this + thread + :raises LookupError: if the named backend is not found + + """ + if asynclib_name := current_async_library(): + raise RuntimeError(f"Already running {asynclib_name} in this thread") + + try: + async_backend = get_async_backend(backend) + except ImportError as exc: + if backend in BACKENDS: + raise LookupError( + f"Backend {backend!r} is not available. " + f"Install it with: pip install anyio[{backend}]" + ) from exc + + raise LookupError(f"No such backend: {backend}") from exc + + token = None + if asynclib_name is None: + # Since we're in control of the event loop, we can cache the name of the async + # library + token = set_current_async_library(backend) + + try: + backend_options = backend_options or {} + return async_backend.run(func, args, {}, backend_options) + finally: + reset_current_async_library(token) + + +async def sleep(delay: float) -> None: + """ + Pause the current task for the specified duration. + + :param delay: the duration, in seconds + + """ + return await get_async_backend().sleep(delay) + + +async def sleep_forever() -> None: + """ + Pause the current task until it's cancelled. + + This is a shortcut for ``sleep(math.inf)``. + + .. versionadded:: 3.1 + + """ + await sleep(math.inf) + + +async def sleep_until(deadline: float) -> None: + """ + Pause the current task until the given time. + + :param deadline: the absolute time to wake up at (according to the internal + monotonic clock of the event loop) + + .. versionadded:: 3.1 + + """ + now = current_time() + await sleep(max(deadline - now, 0)) + + +def current_time() -> float: + """ + Return the current value of the event loop's internal clock. + + :return: the clock value (seconds) + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().current_time() + + +def get_all_backends() -> tuple[str, ...]: + """Return a tuple of the names of all built-in backends.""" + return BACKENDS + + +def get_available_backends() -> tuple[str, ...]: + """ + Test for the availability of built-in backends. + + :return a tuple of the built-in backend names that were successfully imported + + .. versionadded:: 4.12 + + """ + available_backends: list[str] = [] + for backend_name in get_all_backends(): + try: + get_async_backend(backend_name) + except ImportError: + continue + + available_backends.append(backend_name) + + return tuple(available_backends) + + +def get_cancelled_exc_class() -> type[BaseException]: + """ + Return the current async library's cancellation exception class. + + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().cancelled_exception_class() + + +# +# Private API +# + + +@contextmanager +def claim_worker_thread( + backend_class: type[AsyncBackend], token: object +) -> Generator[Any, None, None]: + from ..lowlevel import EventLoopToken + + threadlocals.current_token = EventLoopToken(backend_class, token) + try: + yield + finally: + del threadlocals.current_token + + +def get_async_backend(asynclib_name: str | None = None) -> type[AsyncBackend]: + if asynclib_name is None: + asynclib_name = current_async_library() + if not asynclib_name: + raise NoEventLoopError( + f"Not currently running on any asynchronous event loop. " + f"Available async backends: {', '.join(get_all_backends())}" + ) + + # We use our own dict instead of sys.modules to get the already imported back-end + # class because the appropriate modules in sys.modules could potentially be only + # partially initialized + try: + return loaded_backends[asynclib_name] + except KeyError: + module = import_module(f"anyio._backends._{asynclib_name}") + loaded_backends[asynclib_name] = module.backend_class + return module.backend_class + + +def current_async_library() -> str | None: + if sniffio is None: + # If sniffio is not installed, we assume we're either running asyncio or nothing + import asyncio + + try: + asyncio.get_running_loop() + return "asyncio" + except RuntimeError: + pass + else: + try: + return sniffio.current_async_library() + except sniffio.AsyncLibraryNotFoundError: + pass + + return None + + +def set_current_async_library(asynclib_name: str | None) -> Token | None: + # no-op if sniffio is not installed + if sniffio is None: + return None + + return sniffio.current_async_library_cvar.set(asynclib_name) + + +def reset_current_async_library(token: Token | None) -> None: + if token is not None: + sniffio.current_async_library_cvar.reset(token) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_exceptions.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..7d9165f53ab239182f9158f8eeec7ea27b417c92 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_exceptions.py @@ -0,0 +1,205 @@ +from __future__ import annotations + +import sys +from collections.abc import Generator +from textwrap import dedent +from typing import Any + +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup + + +class BrokenResourceError(Exception): + """ + Raised when trying to use a resource that has been rendered unusable due to external + causes (e.g. a send stream whose peer has disconnected). + """ + + +class BrokenWorkerProcess(Exception): + """ + Raised by :meth:`~anyio.to_process.run_sync` if the worker process terminates abruptly or + otherwise misbehaves. + """ + + +class BrokenWorkerInterpreter(Exception): + """ + Raised by :meth:`~anyio.to_interpreter.run_sync` if an unexpected exception is + raised in the subinterpreter. + """ + + def __init__(self, excinfo: Any): + # This was adapted from concurrent.futures.interpreter.ExecutionFailed + msg = excinfo.formatted + if not msg: + if excinfo.type and excinfo.msg: + msg = f"{excinfo.type.__name__}: {excinfo.msg}" + else: + msg = excinfo.type.__name__ or excinfo.msg + + super().__init__(msg) + self.excinfo = excinfo + + def __str__(self) -> str: + try: + formatted = self.excinfo.errdisplay + except Exception: + return super().__str__() + else: + return dedent( + f""" + {super().__str__()} + + Uncaught in the interpreter: + + {formatted} + """.strip() + ) + + +class BusyResourceError(Exception): + """ + Raised when two tasks are trying to read from or write to the same resource + concurrently. + """ + + def __init__(self, action: str): + super().__init__(f"Another task is already {action} this resource") + + +class ClosedResourceError(Exception): + """Raised when trying to use a resource that has been closed.""" + + +class ConnectionFailed(OSError): + """ + Raised when a connection attempt fails. + + .. note:: This class inherits from :exc:`OSError` for backwards compatibility. + """ + + +def iterate_exceptions( + exception: BaseException, +) -> Generator[BaseException, None, None]: + if isinstance(exception, BaseExceptionGroup): + for exc in exception.exceptions: + yield from iterate_exceptions(exc) + else: + yield exception + + +class DelimiterNotFound(Exception): + """ + Raised during + :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the + maximum number of bytes has been read without the delimiter being found. + """ + + def __init__(self, max_bytes: int) -> None: + super().__init__( + f"The delimiter was not found among the first {max_bytes} bytes" + ) + + +class EndOfStream(Exception): + """ + Raised when trying to read from a stream that has been closed from the other end. + """ + + +class IncompleteRead(Exception): + """ + Raised during + :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_exactly` or + :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the + connection is closed before the requested amount of bytes has been read. + """ + + def __init__(self) -> None: + super().__init__( + "The stream was closed before the read operation could be completed" + ) + + +class TypedAttributeLookupError(LookupError): + """ + Raised by :meth:`~anyio.TypedAttributeProvider.extra` when the given typed attribute + is not found and no default value has been given. + """ + + +class WouldBlock(Exception): + """Raised by ``X_nowait`` functions if ``X()`` would block.""" + + +class NoEventLoopError(RuntimeError): + """ + Raised by several functions that require an event loop to be running in the current + thread when there is no running event loop. + + This is also raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync` + if not calling from an AnyIO worker thread, and no ``token`` was passed. + """ + + +class RunFinishedError(RuntimeError): + """ + Raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync` if the event + loop associated with the explicitly passed token has already finished. + """ + + def __init__(self) -> None: + super().__init__( + "The event loop associated with the given token has already finished" + ) + + +class TaskFailed(Exception): + """ + Raised when awaiting on, or attempting to access the return value of, a + :class:`.TaskHandle` that raised an exception. + """ + + +class TaskCancelled(TaskFailed): + """ + Raised when awaiting on, or attempting to access the return value of, a + :class:`.TaskHandle` that was cancelled. + """ + + +class TaskNotFinished(Exception): + """ + Raised when attempting to access the return value or exception of a + :class:`.TaskHandle` that is still pending completion. + """ + + +class FutureFailed(Exception): + """ + Raised when awaiting on, or attempting to access the return value of, a + :class:`.Future` that raised an exception. + """ + + +class FutureCancelled(FutureFailed): + """ + Raised when attempting to access the return value or exception of a + :class:`.Future` that was cancelled. + """ + + +class FutureNotFinished(Exception): + """ + Raised when attempting to access the return value or exception of a + :class:`.Future` that is still pending completion. + """ + + +class FutureAlreadyFinished(Exception): + """ + Raised when attempting set a result of or await a + :class:`.Future` that has already completed. + """ diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_fileio.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_fileio.py new file mode 100644 index 0000000000000000000000000000000000000000..498eef41d19fde3991f05472e0bc385c520f49ec --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_fileio.py @@ -0,0 +1,1045 @@ +from __future__ import annotations + +import os +import pathlib +import sys +from collections.abc import ( + AsyncIterator, + Callable, + Iterable, + Iterator, + Sequence, +) +from dataclasses import dataclass +from functools import partial +from os import PathLike +from typing import ( + IO, + TYPE_CHECKING, + Any, + AnyStr, + ClassVar, + Final, + Generic, + TypeVar, + overload, +) + +from .. import to_thread +from ..abc import AsyncResource +from ._synchronization import CapacityLimiter + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + +if sys.version_info >= (3, 14): + from pathlib.types import PathInfo + +if TYPE_CHECKING: + from types import ModuleType + + from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer +else: + ReadableBuffer = OpenBinaryMode = OpenTextMode = WriteableBuffer = object + + +T = TypeVar("T", bound="Path") + + +class AsyncFile(AsyncResource, Generic[AnyStr]): + """ + An asynchronous file object. + + This class wraps a standard file object and provides async friendly versions of the + following blocking methods (where available on the original file object): + + * read + * read1 + * readline + * readlines + * readinto + * readinto1 + * write + * writelines + * truncate + * seek + * tell + * flush + + All other methods are directly passed through. + + This class supports the asynchronous context manager protocol which closes the + underlying file at the end of the context block. + + This class also supports asynchronous iteration:: + + async with await open_file(...) as f: + async for line in f: + print(line) + """ + + def __init__( + self, fp: IO[AnyStr], *, limiter: CapacityLimiter | None = None + ) -> None: + if limiter is not None and not isinstance(limiter, CapacityLimiter): + raise TypeError( + f"limiter must be a CapacityLimiter or None, not " + f"{limiter.__class__.__name__}" + ) + + self._fp: Any = fp + self._limiter = limiter + + def __getattr__(self, name: str) -> object: + return getattr(self._fp, name) + + @property + def limiter(self) -> CapacityLimiter | None: + """The capacity limiter used by this file object, if not the global limiter.""" + return self._limiter + + @property + def wrapped(self) -> IO[AnyStr]: + """The wrapped file object.""" + return self._fp + + async def __aiter__(self) -> AsyncIterator[AnyStr]: + while True: + line = await self.readline() + if line: + yield line + else: + break + + async def aclose(self) -> None: + return await to_thread.run_sync(self._fp.close, limiter=self._limiter) + + async def read(self, size: int = -1) -> AnyStr: + return await to_thread.run_sync(self._fp.read, size, limiter=self._limiter) + + async def read1(self: AsyncFile[bytes], size: int = -1) -> bytes: + return await to_thread.run_sync(self._fp.read1, size, limiter=self._limiter) + + async def readline(self) -> AnyStr: + return await to_thread.run_sync(self._fp.readline, limiter=self._limiter) + + async def readlines(self) -> list[AnyStr]: + return await to_thread.run_sync(self._fp.readlines, limiter=self._limiter) + + async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> int: + return await to_thread.run_sync(self._fp.readinto, b, limiter=self._limiter) + + async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> int: + return await to_thread.run_sync(self._fp.readinto1, b, limiter=self._limiter) + + @overload + async def write(self: AsyncFile[bytes], b: ReadableBuffer) -> int: ... + + @overload + async def write(self: AsyncFile[str], b: str) -> int: ... + + async def write(self, b: ReadableBuffer | str) -> int: + return await to_thread.run_sync(self._fp.write, b, limiter=self._limiter) + + @overload + async def writelines( + self: AsyncFile[bytes], lines: Iterable[ReadableBuffer] + ) -> None: ... + + @overload + async def writelines(self: AsyncFile[str], lines: Iterable[str]) -> None: ... + + async def writelines(self, lines: Iterable[ReadableBuffer] | Iterable[str]) -> None: + return await to_thread.run_sync( + self._fp.writelines, lines, limiter=self._limiter + ) + + async def truncate(self, size: int | None = None) -> int: + return await to_thread.run_sync(self._fp.truncate, size, limiter=self._limiter) + + async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int: + return await to_thread.run_sync( + self._fp.seek, offset, whence, limiter=self._limiter + ) + + async def tell(self) -> int: + return await to_thread.run_sync(self._fp.tell, limiter=self._limiter) + + async def flush(self) -> None: + return await to_thread.run_sync(self._fp.flush, limiter=self._limiter) + + +@overload +async def open_file( + file: str | PathLike[str] | int, + mode: OpenBinaryMode, + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + closefd: bool = ..., + opener: Callable[[str, int], int] | None = ..., + *, + limiter: CapacityLimiter | None = ..., +) -> AsyncFile[bytes]: ... + + +@overload +async def open_file( + file: str | PathLike[str] | int, + mode: OpenTextMode = ..., + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + closefd: bool = ..., + opener: Callable[[str, int], int] | None = ..., + *, + limiter: CapacityLimiter | None = ..., +) -> AsyncFile[str]: ... + + +async def open_file( + file: str | PathLike[str] | int, + mode: str = "r", + buffering: int = -1, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + closefd: bool = True, + opener: Callable[[str, int], int] | None = None, + *, + limiter: CapacityLimiter | None = None, +) -> AsyncFile[Any]: + """ + Open a file asynchronously. + + Except for ``limiter``, the arguments are exactly the same as for the builtin :func:`open`. + + :param limiter: an optional capacity limiter to use with the file + instead of the default one + :return: an asynchronous file object + + .. versionchanged:: 4.14.0 + Added the ``limiter`` keyword argument. + + """ + fp = await to_thread.run_sync( + open, + file, + mode, + buffering, + encoding, + errors, + newline, + closefd, + opener, + limiter=limiter, + ) + return AsyncFile(fp, limiter=limiter) + + +def wrap_file( + file: IO[AnyStr], *, limiter: CapacityLimiter | None = None +) -> AsyncFile[AnyStr]: + """ + Wrap an existing file as an asynchronous file. + + :param file: an existing file-like object + :param limiter: an optional capacity limiter to use with the file + instead of the default one + :return: an asynchronous file object + + .. versionchanged:: 4.14.0 + Added the ``limiter`` keyword argument. + + """ + return AsyncFile(file, limiter=limiter) + + +@dataclass(eq=False) +class _PathIterator(AsyncIterator[T]): + iterator: Iterator[PathLike[str]] + limiter: CapacityLimiter | None + # This was added to ensure that iterating over a subclass of Path yields instances + # of that subclass rather than the base Path class. + path_cls: type[T] + + async def __anext__(self) -> T: + nextval = await to_thread.run_sync( + next, self.iterator, None, abandon_on_cancel=True, limiter=self.limiter + ) + if nextval is None: + raise StopAsyncIteration from None + + return self.path_cls(nextval, limiter=self.limiter) + + +class Path: + """ + An asynchronous version of :class:`pathlib.Path`. + + This class cannot be substituted for :class:`pathlib.Path` or + :class:`pathlib.PurePath`, but it is compatible with the :class:`os.PathLike` + interface. + + It implements the Python 3.10 version of :class:`pathlib.Path` interface, except for + the deprecated :meth:`~pathlib.Path.link_to` method. + + Some methods may be unavailable or have limited functionality, based on the Python + version: + + * :meth:`~pathlib.Path.copy` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.copy_into` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.from_uri` (available on Python 3.13 or later) + * :meth:`~pathlib.PurePath.full_match` (available on Python 3.13 or later) + * :attr:`~pathlib.Path.info` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.is_junction` (available on Python 3.12 or later) + * :meth:`~pathlib.PurePath.match` (the ``case_sensitive`` parameter is only + available on Python 3.13 or later) + * :meth:`~pathlib.Path.move` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.move_into` (available on Python 3.14 or later) + * :meth:`~pathlib.PurePath.relative_to` (the ``walk_up`` parameter is only available + on Python 3.12 or later) + * :meth:`~pathlib.Path.walk` (available on Python 3.12 or later) + + Any methods that do disk I/O need to be awaited on. These methods are: + + * :meth:`~pathlib.Path.absolute` + * :meth:`~pathlib.Path.chmod` + * :meth:`~pathlib.Path.cwd` + * :meth:`~pathlib.Path.exists` + * :meth:`~pathlib.Path.expanduser` + * :meth:`~pathlib.Path.group` + * :meth:`~pathlib.Path.hardlink_to` + * :meth:`~pathlib.Path.home` + * :meth:`~pathlib.Path.is_block_device` + * :meth:`~pathlib.Path.is_char_device` + * :meth:`~pathlib.Path.is_dir` + * :meth:`~pathlib.Path.is_fifo` + * :meth:`~pathlib.Path.is_file` + * :meth:`~pathlib.Path.is_junction` + * :meth:`~pathlib.Path.is_mount` + * :meth:`~pathlib.Path.is_socket` + * :meth:`~pathlib.Path.is_symlink` + * :meth:`~pathlib.Path.lchmod` + * :meth:`~pathlib.Path.lstat` + * :meth:`~pathlib.Path.mkdir` + * :meth:`~pathlib.Path.open` + * :meth:`~pathlib.Path.owner` + * :meth:`~pathlib.Path.read_bytes` + * :meth:`~pathlib.Path.read_text` + * :meth:`~pathlib.Path.readlink` + * :meth:`~pathlib.Path.rename` + * :meth:`~pathlib.Path.replace` + * :meth:`~pathlib.Path.resolve` + * :meth:`~pathlib.Path.rmdir` + * :meth:`~pathlib.Path.samefile` + * :meth:`~pathlib.Path.stat` + * :meth:`~pathlib.Path.symlink_to` + * :meth:`~pathlib.Path.touch` + * :meth:`~pathlib.Path.unlink` + * :meth:`~pathlib.Path.walk` + * :meth:`~pathlib.Path.write_bytes` + * :meth:`~pathlib.Path.write_text` + + Additionally, the following methods return an async iterator yielding + :class:`~.Path` objects: + + * :meth:`~pathlib.Path.glob` + * :meth:`~pathlib.Path.iterdir` + * :meth:`~pathlib.Path.rglob` + + .. versionchanged:: 4.14.0 + Added the ``limiter`` keyword argument. + """ + + __slots__ = "__weakref__", "_limiter", "_path" + + __weakref__: Any + + def __init__( + self, *args: str | PathLike[str], limiter: CapacityLimiter | None = None + ) -> None: + if limiter is not None and not isinstance(limiter, CapacityLimiter): + raise TypeError( + f"limiter must be a CapacityLimiter or None, not " + f"{limiter.__class__.__name__}" + ) + + self._path: Final[pathlib.Path] = pathlib.Path(*args) + self._limiter = limiter + + def __fspath__(self) -> str: + return self._path.__fspath__() + + if sys.version_info >= (3, 15): + + def __vfspath__(self) -> str: + return self._path.__vfspath__() + + def __str__(self) -> str: + return self._path.__str__() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.as_posix()!r})" + + def __bytes__(self) -> bytes: + return self._path.__bytes__() + + def __hash__(self) -> int: + return self._path.__hash__() + + def __eq__(self, other: object) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__eq__(target) + + def __lt__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__lt__(target) + + def __le__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__le__(target) + + def __gt__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__gt__(target) + + def __ge__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__ge__(target) + + def __truediv__(self, other: str | PathLike[str]) -> Self: + return type(self)(self._path / other, limiter=self._limiter) + + def __rtruediv__(self, other: str | PathLike[str]) -> Self: + return type(self)(other, limiter=self._limiter) / self + + @property + def limiter(self) -> CapacityLimiter | None: + """The capacity limiter used by this path, if not the global limiter.""" + return self._limiter + + @property + def parts(self) -> tuple[str, ...]: + return self._path.parts + + @property + def drive(self) -> str: + return self._path.drive + + @property + def root(self) -> str: + return self._path.root + + @property + def anchor(self) -> str: + return self._path.anchor + + @property + def parents(self) -> Sequence[Self]: + return tuple(type(self)(p, limiter=self._limiter) for p in self._path.parents) + + @property + def parent(self) -> Self: + return type(self)(self._path.parent, limiter=self._limiter) + + @property + def name(self) -> str: + return self._path.name + + @property + def suffix(self) -> str: + return self._path.suffix + + @property + def suffixes(self) -> list[str]: + return self._path.suffixes + + @property + def stem(self) -> str: + return self._path.stem + + async def absolute(self) -> Self: + path = await to_thread.run_sync(self._path.absolute, limiter=self._limiter) + return type(self)(path, limiter=self._limiter) + + def as_posix(self) -> str: + return self._path.as_posix() + + def as_uri(self) -> str: + return self._path.as_uri() + + if sys.version_info >= (3, 13): + parser: ClassVar[ModuleType] = pathlib.Path.parser + + @classmethod + def from_uri(cls, uri: str, *, limiter: CapacityLimiter | None = None) -> Self: + return cls(pathlib.Path.from_uri(uri), limiter=limiter) + + def full_match( + self, path_pattern: str, *, case_sensitive: bool | None = None + ) -> bool: + return self._path.full_match(path_pattern, case_sensitive=case_sensitive) + + def match( + self, path_pattern: str, *, case_sensitive: bool | None = None + ) -> bool: + return self._path.match(path_pattern, case_sensitive=case_sensitive) + else: + + def match(self, path_pattern: str) -> bool: + return self._path.match(path_pattern) + + if sys.version_info >= (3, 14): + + @property + def info(self) -> PathInfo: + return self._path.info + + async def copy( + self, + target: str | os.PathLike[str], + *, + follow_symlinks: bool = True, + preserve_metadata: bool = False, + ) -> Self: + func = partial( + self._path.copy, + follow_symlinks=follow_symlinks, + preserve_metadata=preserve_metadata, + ) + return type(self)( + await to_thread.run_sync( + func, pathlib.Path(target), limiter=self._limiter + ), + limiter=self._limiter, + ) + + async def copy_into( + self, + target_dir: str | os.PathLike[str], + *, + follow_symlinks: bool = True, + preserve_metadata: bool = False, + ) -> Self: + func = partial( + self._path.copy_into, + follow_symlinks=follow_symlinks, + preserve_metadata=preserve_metadata, + ) + return type(self)( + await to_thread.run_sync( + func, pathlib.Path(target_dir), limiter=self._limiter + ), + limiter=self._limiter, + ) + + async def move(self, target: str | os.PathLike[str]) -> Self: + # Upstream does not handle anyio.Path properly as a PathLike + target = pathlib.Path(target) + return type(self)( + await to_thread.run_sync( + self._path.move, target, limiter=self._limiter + ), + limiter=self._limiter, + ) + + async def move_into( + self, + target_dir: str | os.PathLike[str], + ) -> Self: + return type(self)( + await to_thread.run_sync( + self._path.move_into, target_dir, limiter=self._limiter + ), + limiter=self._limiter, + ) + + def is_relative_to(self, other: str | PathLike[str]) -> bool: + try: + self.relative_to(other) + return True + except ValueError: + return False + + async def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None: + func = partial(os.chmod, follow_symlinks=follow_symlinks) + return await to_thread.run_sync(func, self._path, mode, limiter=self._limiter) + + @classmethod + async def cwd(cls, *, limiter: CapacityLimiter | None = None) -> Self: + path = await to_thread.run_sync(pathlib.Path.cwd, limiter=limiter) + return cls(path, limiter=limiter) + + if sys.version_info >= (3, 12): + + async def exists(self, *, follow_symlinks: bool = True) -> bool: + return await to_thread.run_sync( + partial(self._path.exists, follow_symlinks=follow_symlinks), + abandon_on_cancel=True, + limiter=self._limiter, + ) + + else: + + async def exists(self) -> bool: + return await to_thread.run_sync( + self._path.exists, abandon_on_cancel=True, limiter=self._limiter + ) + + async def expanduser(self) -> Self: + return type(self)( + await to_thread.run_sync( + self._path.expanduser, abandon_on_cancel=True, limiter=self._limiter + ), + limiter=self._limiter, + ) + + if sys.version_info < (3, 12): + # Python 3.11 and earlier + def glob(self, pattern: str) -> AsyncIterator[Self]: + gen = self._path.glob(pattern) + return _PathIterator(gen, self._limiter, type(self)) + elif (3, 12) <= sys.version_info < (3, 13): + # changed in Python 3.12: + # - The case_sensitive parameter was added. + def glob( + self, + pattern: str, + *, + case_sensitive: bool | None = None, + ) -> AsyncIterator[Self]: + gen = self._path.glob(pattern, case_sensitive=case_sensitive) + return _PathIterator(gen, self._limiter, type(self)) + elif sys.version_info >= (3, 13): + # Changed in Python 3.13: + # - The recurse_symlinks parameter was added. + # - The pattern parameter accepts a path-like object. + def glob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block + self, + pattern: str | PathLike[str], + *, + case_sensitive: bool | None = None, + recurse_symlinks: bool = False, + ) -> AsyncIterator[Self]: + gen = self._path.glob( + pattern, # type: ignore[arg-type] + case_sensitive=case_sensitive, + recurse_symlinks=recurse_symlinks, + ) + return _PathIterator(gen, self._limiter, type(self)) + + if sys.version_info >= (3, 13): + + async def group(self, *, follow_symlinks: bool = True) -> str: + return await to_thread.run_sync( + partial(self._path.group, follow_symlinks=follow_symlinks), + abandon_on_cancel=True, + limiter=self._limiter, + ) + + else: + + async def group(self) -> str: + return await to_thread.run_sync( + self._path.group, abandon_on_cancel=True, limiter=self._limiter + ) + + async def hardlink_to( + self, target: str | bytes | PathLike[str] | PathLike[bytes] + ) -> None: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(os.link, target, self, limiter=self._limiter) + + @classmethod + async def home(cls, *, limiter: CapacityLimiter | None = None) -> Self: + home_path = await to_thread.run_sync(pathlib.Path.home, limiter=limiter) + return cls(home_path, limiter=limiter) + + def is_absolute(self) -> bool: + return self._path.is_absolute() + + async def is_block_device(self) -> bool: + return await to_thread.run_sync( + self._path.is_block_device, abandon_on_cancel=True, limiter=self._limiter + ) + + async def is_char_device(self) -> bool: + return await to_thread.run_sync( + self._path.is_char_device, abandon_on_cancel=True, limiter=self._limiter + ) + + if sys.version_info >= (3, 13): + + async def is_dir(self, *, follow_symlinks: bool = True) -> bool: + return await to_thread.run_sync( + partial(self._path.is_dir, follow_symlinks=follow_symlinks), + abandon_on_cancel=True, + limiter=self._limiter, + ) + + else: + + async def is_dir(self) -> bool: + return await to_thread.run_sync( + self._path.is_dir, abandon_on_cancel=True, limiter=self._limiter + ) + + async def is_fifo(self) -> bool: + return await to_thread.run_sync( + self._path.is_fifo, abandon_on_cancel=True, limiter=self._limiter + ) + + if sys.version_info >= (3, 13): + + async def is_file(self, *, follow_symlinks: bool = True) -> bool: + return await to_thread.run_sync( + partial(self._path.is_file, follow_symlinks=follow_symlinks), + abandon_on_cancel=True, + limiter=self._limiter, + ) + + else: + + async def is_file(self) -> bool: + return await to_thread.run_sync( + self._path.is_file, abandon_on_cancel=True, limiter=self._limiter + ) + + if sys.version_info >= (3, 12): + + async def is_junction(self) -> bool: + return await to_thread.run_sync( + self._path.is_junction, limiter=self._limiter + ) + + async def is_mount(self) -> bool: + return await to_thread.run_sync( + os.path.ismount, self._path, abandon_on_cancel=True, limiter=self._limiter + ) + + if sys.version_info < (3, 15): + + def is_reserved(self) -> bool: + return self._path.is_reserved() + + async def is_socket(self) -> bool: + return await to_thread.run_sync( + self._path.is_socket, abandon_on_cancel=True, limiter=self._limiter + ) + + async def is_symlink(self) -> bool: + return await to_thread.run_sync( + self._path.is_symlink, abandon_on_cancel=True, limiter=self._limiter + ) + + async def iterdir(self) -> AsyncIterator[Self]: + gen = ( + self._path.iterdir() + if sys.version_info < (3, 13) + else await to_thread.run_sync( + self._path.iterdir, abandon_on_cancel=True, limiter=self._limiter + ) + ) + async for path in _PathIterator(gen, self._limiter, type(self)): + yield path + + def joinpath(self, *args: str | PathLike[str]) -> Self: + return type(self)(self._path.joinpath(*args), limiter=self._limiter) + + async def lchmod(self, mode: int) -> None: + await to_thread.run_sync(self._path.lchmod, mode, limiter=self._limiter) + + async def lstat(self) -> os.stat_result: + return await to_thread.run_sync( + self._path.lstat, abandon_on_cancel=True, limiter=self._limiter + ) + + async def mkdir( + self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False + ) -> None: + await to_thread.run_sync( + self._path.mkdir, mode, parents, exist_ok, limiter=self._limiter + ) + + @overload + async def open( + self, + mode: OpenBinaryMode, + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + ) -> AsyncFile[bytes]: ... + + @overload + async def open( + self, + mode: OpenTextMode = ..., + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + ) -> AsyncFile[str]: ... + + async def open( + self, + mode: str = "r", + buffering: int = -1, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> AsyncFile[Any]: + fp = await to_thread.run_sync( + self._path.open, + mode, + buffering, + encoding, + errors, + newline, + limiter=self._limiter, + ) + return AsyncFile(fp, limiter=self._limiter) + + if sys.version_info >= (3, 13): + + async def owner(self, *, follow_symlinks: bool = True) -> str: + return await to_thread.run_sync( + partial(self._path.owner, follow_symlinks=follow_symlinks), + abandon_on_cancel=True, + limiter=self._limiter, + ) + + else: + + async def owner(self) -> str: + return await to_thread.run_sync( + self._path.owner, abandon_on_cancel=True, limiter=self._limiter + ) + + async def read_bytes(self) -> bytes: + return await to_thread.run_sync(self._path.read_bytes, limiter=self._limiter) + + if sys.version_info >= (3, 13): + + async def read_text( + self, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> str: + return await to_thread.run_sync( + self._path.read_text, + encoding, + errors, + newline, + limiter=self._limiter, + ) + + else: + + async def read_text( + self, encoding: str | None = None, errors: str | None = None + ) -> str: + return await to_thread.run_sync( + self._path.read_text, encoding, errors, limiter=self._limiter + ) + + if sys.version_info >= (3, 12): + + def relative_to( + self, *other: str | PathLike[str], walk_up: bool = False + ) -> Self: + # relative_to() should work with any PathLike but it doesn't + others = [pathlib.Path(other) for other in other] + return type(self)( + self._path.relative_to(*others, walk_up=walk_up), limiter=self._limiter + ) + + else: + + def relative_to(self, *other: str | PathLike[str]) -> Self: + return type(self)(self._path.relative_to(*other), limiter=self._limiter) + + async def readlink(self) -> Self: + target = await to_thread.run_sync( + os.readlink, self._path, limiter=self._limiter + ) + return type(self)(target, limiter=self._limiter) + + async def rename(self, target: str | pathlib.PurePath | Path) -> Self: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(self._path.rename, target, limiter=self._limiter) + return type(self)(target, limiter=self._limiter) + + async def replace(self, target: str | pathlib.PurePath | Path) -> Self: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(self._path.replace, target, limiter=self._limiter) + return type(self)(target, limiter=self._limiter) + + async def resolve(self, strict: bool = False) -> Self: + func = partial(self._path.resolve, strict=strict) + return type(self)( + await to_thread.run_sync( + func, abandon_on_cancel=True, limiter=self._limiter + ), + limiter=self._limiter, + ) + + if sys.version_info < (3, 12): + # Pre Python 3.12 + def rglob(self, pattern: str) -> AsyncIterator[Self]: + gen = self._path.rglob(pattern) + return _PathIterator(gen, self._limiter, type(self)) + elif (3, 12) <= sys.version_info < (3, 13): + # Changed in Python 3.12: + # - The case_sensitive parameter was added. + def rglob( + self, pattern: str, *, case_sensitive: bool | None = None + ) -> AsyncIterator[Self]: + gen = self._path.rglob(pattern, case_sensitive=case_sensitive) + return _PathIterator(gen, self._limiter, type(self)) + elif sys.version_info >= (3, 13): + # Changed in Python 3.13: + # - The recurse_symlinks parameter was added. + # - The pattern parameter accepts a path-like object. + def rglob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block + self, + pattern: str | PathLike[str], + *, + case_sensitive: bool | None = None, + recurse_symlinks: bool = False, + ) -> AsyncIterator[Self]: + gen = self._path.rglob( + pattern, # type: ignore[arg-type] + case_sensitive=case_sensitive, + recurse_symlinks=recurse_symlinks, + ) + return _PathIterator(gen, self._limiter, type(self)) + + async def rmdir(self) -> None: + await to_thread.run_sync(self._path.rmdir, limiter=self._limiter) + + async def samefile(self, other_path: str | PathLike[str]) -> bool: + if isinstance(other_path, Path): + other_path = other_path._path + + return await to_thread.run_sync( + self._path.samefile, + other_path, + abandon_on_cancel=True, + limiter=self._limiter, + ) + + async def stat(self, *, follow_symlinks: bool = True) -> os.stat_result: + func = partial(os.stat, follow_symlinks=follow_symlinks) + return await to_thread.run_sync( + func, self._path, abandon_on_cancel=True, limiter=self._limiter + ) + + async def symlink_to( + self, + target: str | bytes | PathLike[str] | PathLike[bytes], + target_is_directory: bool = False, + ) -> None: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync( + self._path.symlink_to, target, target_is_directory, limiter=self._limiter + ) + + async def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None: + await to_thread.run_sync( + self._path.touch, mode, exist_ok, limiter=self._limiter + ) + + async def unlink(self, missing_ok: bool = False) -> None: + try: + await to_thread.run_sync(self._path.unlink, limiter=self._limiter) + except FileNotFoundError: + if not missing_ok: + raise + + if sys.version_info >= (3, 12): + + async def walk( + self, + top_down: bool = True, + on_error: Callable[[OSError], object] | None = None, + follow_symlinks: bool = False, + ) -> AsyncIterator[tuple[Self, list[str], list[str]]]: + def get_next_value() -> tuple[pathlib.Path, list[str], list[str]] | None: + try: + return next(gen) + except StopIteration: + return None + + gen = self._path.walk(top_down, on_error, follow_symlinks) + while True: + value = await to_thread.run_sync(get_next_value, limiter=self._limiter) + if value is None: + return + + root, dirs, paths = value + yield type(self)(root, limiter=self._limiter), dirs, paths + + def with_name(self, name: str) -> Self: + return type(self)(self._path.with_name(name), limiter=self._limiter) + + if sys.version_info < (3, 13): + # Backport pathlib's Python>=3.13 behavior for empty stems on paths with non-empty suffixes. + # See: https://github.com/python/cpython/pull/114612 + def with_stem(self, stem: str) -> Self: + suffix = self._path.suffix + if not suffix: + return self.with_name(stem) + elif not stem: + # If the suffix is non-empty, we can't make the stem empty. + raise ValueError(f"{self!r} has a non-empty suffix") + else: + return self.with_name(stem + suffix) + else: + + def with_stem(self, stem: str) -> Self: + return type(self)(self._path.with_stem(stem), limiter=self._limiter) + + def with_suffix(self, suffix: str) -> Self: + return type(self)(self._path.with_suffix(suffix), limiter=self._limiter) + + def with_segments(self, *pathsegments: str | PathLike[str]) -> Self: + return type(self)(*pathsegments, limiter=self._limiter) + + async def write_bytes(self, data: ReadableBuffer) -> int: + return await to_thread.run_sync( + self._path.write_bytes, data, limiter=self._limiter + ) + + async def write_text( + self, + data: str, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> int: + return await to_thread.run_sync( + self._path.write_text, + data, + encoding, + errors, + newline, + limiter=self._limiter, + ) + + +PathLike.register(Path) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_futures.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_futures.py new file mode 100644 index 0000000000000000000000000000000000000000..7730c53c7f9bf55742396fa26fa7dd054cdfcb18 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_futures.py @@ -0,0 +1,191 @@ +from __future__ import annotations + +from collections.abc import Generator +from enum import Enum, auto +from typing import Any, Generic, TypeVar + +from ._exceptions import ( + FutureAlreadyFinished, + FutureCancelled, + FutureFailed, + FutureNotFinished, +) +from ._synchronization import Event + +T = TypeVar("T") + + +class Future(Generic[T]): + """ + An awaitable object that works similar to a :class:`asyncio.Future` but + with similar characteristics to a :class:`.TaskHandle`. + """ + + class Status(Enum): + """ + The status of a future handle. + + .. attribute:: PENDING + + The future has not finished yet. + .. attribute:: FINISHED + + The future has finished with a return value. + .. attribute:: CANCELLED + + The future was cancelled and has finished since. + .. attribute:: FAILED + + The future raised an exception. + """ + + PENDING = auto() + FINISHED = auto() + CANCELLED = auto() + FAILED = auto() + + __slots__ = ( + "_cancelled", + "_exception", + "_finished_event", + "_name", + "_return_value", + ) + _return_value: T + + def __init__(self, *, name: str | None = None) -> None: + self._finished_event = Event() + self._exception: BaseException | None = None + self._cancelled: bool = False + self._name = name + + def _check_pending(self) -> None: + """Shortcut for checking if a Future is pending + + :raises FutureAlreadyFinished: if future was already given a result or exception. + :raises FutureCancelled: if future has been cancelled previously. + """ + match self.status: + case Future.Status.PENDING: + return + case Future.Status.FINISHED: + raise FutureAlreadyFinished("future has already finished") + case Future.Status.FAILED: + raise FutureAlreadyFinished("future already failed") + case Future.Status.CANCELLED: + raise FutureCancelled("future was cancelled") + + async def wait(self) -> None: + """ + Waits for the future to finish. + + This method will attempt to wait for a result or exception + """ + await self._finished_event.wait() + + def cancel(self) -> None: + """Cancels a pending `.Future` object + + Does nothing if the Future was already finished. + """ + if self.status is Future.Status.PENDING: + self._cancelled = True + self._finished_event.set() + + @property + def exception(self) -> BaseException | None: + """ + The exception value of a `.Future` + + :raises FutureNotFinished: if future is still pending + :raises FutureCancelled: if future was cancelled + :returns: None if future succeeds with a result sent instead + otherwise this will be an exception + """ + + match self.status: + case Future.Status.PENDING: + raise FutureNotFinished("the future has not finished yet") + case Future.Status.FINISHED: + return None + case Future.Status.CANCELLED: + raise FutureCancelled("the future was cancelled") + case Future.Status.FAILED: + return self._exception + + @exception.setter + def exception(self, exception: BaseException) -> None: + """Send exception for a `.Future` object + + :raises FutureAlreadyFinished: if future was already given a result or exception. + :raises FutureCancelled: if future has been cancelled previously. + """ + self._check_pending() + self._exception = exception + self._finished_event.set() + + @property + def return_value(self) -> T: + """ + The return value of the future. + + :raises FutureNotFinished: if the future has not finished yet + :raises FutureCancelled: if the future was cancelled + :raises FutureFailed: if the future raised an exception + + """ + match self.status: + case Future.Status.PENDING: + raise FutureNotFinished("the future has not finished yet") + case Future.Status.FINISHED: + return self._return_value + case Future.Status.CANCELLED: + raise FutureCancelled("the future was cancelled") + case Future.Status.FAILED: + raise FutureFailed( + "the future raised an exception" + ) from self._exception + + @return_value.setter + def return_value(self, value: T) -> None: + """ + Send pending result for a `.Future` object + + :raises FutureAlreadyFinished: if future was already given a result or exception. + :raises FutureCancelled: if future has been cancelled previously. + """ + self._check_pending() + self._return_value = value + self._finished_event.set() + + @property + def status(self) -> Future.Status: + """ + The current status of a future. + + Every future starts in the :attr:`~Future.Status.PENDING` state. + If a future is cancelled while in this state, it will transition to the + :attr:`Future.Status.CANCELLED` state. When the task finishes, it will + transition to one of the three final states ( + :attr:`Future.Status.FINISHED`, :attr:`Future.Status.FAILED`, or + :attr:`Future.Status.CANCELLED`) depending on the exception the task + raised, if any. No other status transitions will happen. + """ + if not self._finished_event.is_set(): + return Future.Status.PENDING + elif self._cancelled: + return Future.Status.CANCELLED + elif self._exception is not None: + return Future.Status.FAILED + else: + return Future.Status.FINISHED + + def __await__(self) -> Generator[Any, Any, T]: + yield from self.wait().__await__() + return self.return_value + + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__} {self.status.name.lower()} " + f"name={self._name!r}>" + ) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_resources.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_resources.py new file mode 100644 index 0000000000000000000000000000000000000000..6ca35d94a930661c4c976c204cfb6b31cc15be37 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_resources.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from ..abc import AsyncResource # noqa: TC001 +from ._tasks import CancelScope + + +async def aclose_forcefully(resource: AsyncResource) -> None: + """ + Close an asynchronous resource in a cancelled scope. + + Doing this closes the resource without waiting on anything. + + :param resource: the resource to close + + """ + with CancelScope() as scope: + scope.cancel() + await resource.aclose() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_signals.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_signals.py new file mode 100644 index 0000000000000000000000000000000000000000..e24c79e10d4b76775679f7dd0dbe3f5860150451 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_signals.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from collections.abc import AsyncIterator +from contextlib import AbstractContextManager +from signal import Signals + +from ._eventloop import get_async_backend + + +def open_signal_receiver( + *signals: Signals, +) -> AbstractContextManager[AsyncIterator[Signals]]: + """ + Start receiving operating system signals. + + :param signals: signals to receive (e.g. ``signal.SIGINT``) + :return: an asynchronous context manager for an asynchronous iterator which yields + signal numbers + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. warning:: Windows does not support signals natively so it is best to avoid + relying on this in cross-platform applications. + + .. warning:: On asyncio, this permanently replaces any previous signal handler for + the given signals, as set via :meth:`~asyncio.loop.add_signal_handler`. + + """ + return get_async_backend().open_signal_receiver(*signals) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_sockets.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_sockets.py new file mode 100644 index 0000000000000000000000000000000000000000..14ee6e41e79f5a839b7bcf97dd3ca5e3ae656f49 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_sockets.py @@ -0,0 +1,1011 @@ +from __future__ import annotations + +import errno +import os +import socket +import ssl +import stat +import sys +from collections.abc import Awaitable +from dataclasses import dataclass +from ipaddress import IPv4Address, IPv6Address, ip_address +from os import PathLike, chmod +from socket import AddressFamily, SocketKind +from typing import TYPE_CHECKING, Any, Literal, cast, overload + +from .. import ConnectionFailed, to_thread +from ..abc import ( + ByteStreamConnectable, + ConnectedUDPSocket, + ConnectedUNIXDatagramSocket, + IPAddressType, + IPSockAddrType, + SocketListener, + SocketStream, + UDPSocket, + UNIXDatagramSocket, + UNIXSocketStream, +) +from ..streams.stapled import MultiListener +from ..streams.tls import TLSConnectable, TLSStream +from ._eventloop import get_async_backend +from ._resources import aclose_forcefully +from ._synchronization import Event +from ._tasks import create_task_group, move_on_after + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike +else: + FileDescriptorLike = object + +if sys.version_info < (3, 11): + from exceptiongroup import ExceptionGroup + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + +if sys.version_info < (3, 13): + from typing_extensions import deprecated +else: + from warnings import deprecated + +IPPROTO_IPV6 = getattr(socket, "IPPROTO_IPV6", 41) # https://bugs.python.org/issue29515 + +AnyIPAddressFamily = Literal[ + AddressFamily.AF_UNSPEC, AddressFamily.AF_INET, AddressFamily.AF_INET6 +] +IPAddressFamily = Literal[AddressFamily.AF_INET, AddressFamily.AF_INET6] + + +def idna2008_resolve(host: str) -> bytes: + try: + return host.encode("ascii") + except UnicodeEncodeError: + import idna + + return idna.encode(host, uts46=True) + + +# tls_hostname given +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + local_port: int | None = ..., + ssl_context: ssl.SSLContext | None = ..., + tls_standard_compatible: bool = ..., + tls_hostname: str, + happy_eyeballs_delay: float = ..., +) -> TLSStream: ... + + +# ssl_context given +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + local_port: int | None = ..., + ssl_context: ssl.SSLContext, + tls_standard_compatible: bool = ..., + tls_hostname: str | None = ..., + happy_eyeballs_delay: float = ..., +) -> TLSStream: ... + + +# tls=True +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + local_port: int | None = ..., + tls: Literal[True], + ssl_context: ssl.SSLContext | None = ..., + tls_standard_compatible: bool = ..., + tls_hostname: str | None = ..., + happy_eyeballs_delay: float = ..., +) -> TLSStream: ... + + +# tls=False +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + local_port: int | None = ..., + tls: Literal[False], + ssl_context: ssl.SSLContext | None = ..., + tls_standard_compatible: bool = ..., + tls_hostname: str | None = ..., + happy_eyeballs_delay: float = ..., +) -> SocketStream: ... + + +# No TLS arguments +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + local_port: int | None = ..., + happy_eyeballs_delay: float = ..., +) -> SocketStream: ... + + +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = None, + local_port: int | None = None, + tls: bool = False, + ssl_context: ssl.SSLContext | None = None, + tls_standard_compatible: bool = True, + tls_hostname: str | None = None, + happy_eyeballs_delay: float = 0.25, +) -> SocketStream | TLSStream: + """ + Connect to a host using the TCP protocol. + + This function implements the stateless version of the Happy Eyeballs algorithm (RFC + 6555). If ``remote_host`` is a host name that resolves to multiple IP addresses, + each one is tried until one connection attempt succeeds. If the first attempt does + not connected within 250 milliseconds, a second attempt is started using the next + address in the list, and so on. On IPv6 enabled systems, an IPv6 address (if + available) is tried first. + + When the connection has been established, a TLS handshake will be done if either + ``ssl_context`` or ``tls_hostname`` is not ``None``, or if ``tls`` is ``True``. + + :param remote_host: the IP address or host name to connect to + :param remote_port: port on the target host to connect to + :param local_host: the interface address or name to bind the socket to before + connecting + :param local_port: the local port to bind to (requires ``local_host`` to also be + set) + :param tls: ``True`` to do a TLS handshake with the connected stream and return a + :class:`~anyio.streams.tls.TLSStream` instead + :param ssl_context: the SSL context object to use (if omitted, a default context is + created) + :param tls_standard_compatible: If ``True``, performs the TLS shutdown handshake + before closing the stream and requires that the server does this as well. + Otherwise, :exc:`~ssl.SSLEOFError` may be raised during reads from the stream. + Some protocols, such as HTTP, require this option to be ``False``. + See :meth:`~ssl.SSLContext.wrap_socket` for details. + :param tls_hostname: host name to check the server certificate against (defaults to + the value of ``remote_host``) + :param happy_eyeballs_delay: delay (in seconds) before starting the next connection + attempt + :return: a socket stream object if no TLS handshake was done, otherwise a TLS stream + :raises ConnectionFailed: if the connection fails + + """ + # Placed here due to https://github.com/python/mypy/issues/7057 + connected_stream: SocketStream | None = None + + async def try_connect(remote_host: str, event: Event) -> None: + nonlocal connected_stream + try: + stream = await asynclib.connect_tcp(remote_host, remote_port, local_address) + except OSError as exc: + oserrors.append(exc) + return + else: + if connected_stream is None: + connected_stream = stream + tg.cancel_scope.cancel() + else: + await stream.aclose() + finally: + event.set() + + asynclib = get_async_backend() + local_address: IPSockAddrType | None = None + family = socket.AF_UNSPEC + if local_host: + gai_res = await getaddrinfo(str(local_host), local_port) + family, *_, local_address = gai_res[0] + + target_host = str(remote_host) + try: + addr_obj = ip_address(remote_host) + except ValueError: + addr_obj = None + + if addr_obj is not None: + if isinstance(addr_obj, IPv6Address): + target_addrs = [(socket.AF_INET6, addr_obj.compressed)] + else: + target_addrs = [(socket.AF_INET, addr_obj.compressed)] + else: + # getaddrinfo() will raise an exception if name resolution fails + gai_res = await getaddrinfo( + target_host, remote_port, family=family, type=socket.SOCK_STREAM + ) + + # Organize the list so that the first address is an IPv6 address (if available) + # and the second one is an IPv4 addresses. The rest can be in whatever order. + v6_found = v4_found = False + target_addrs = [] + for af, *_, sa in gai_res: + if af == socket.AF_INET6 and not v6_found: + v6_found = True + target_addrs.insert(0, (af, sa[0])) + elif af == socket.AF_INET and not v4_found and v6_found: + v4_found = True + target_addrs.insert(1, (af, sa[0])) + else: + target_addrs.append((af, sa[0])) + + oserrors: list[OSError] = [] + try: + async with create_task_group() as tg: + for _af, addr in target_addrs: + event = Event() + tg.start_soon(try_connect, addr, event) + with move_on_after(happy_eyeballs_delay): + await event.wait() + + if connected_stream is None: + cause = ( + oserrors[0] + if len(oserrors) == 1 + else ExceptionGroup("multiple connection attempts failed", oserrors) + ) + raise OSError("All connection attempts failed") from cause + finally: + oserrors.clear() + + if tls or tls_hostname or ssl_context: + try: + return await TLSStream.wrap( + connected_stream, + server_side=False, + hostname=tls_hostname or str(remote_host), + ssl_context=ssl_context, + standard_compatible=tls_standard_compatible, + ) + except BaseException: + await aclose_forcefully(connected_stream) + raise + + return connected_stream + + +async def connect_unix(path: str | bytes | PathLike[Any]) -> UNIXSocketStream: + """ + Connect to the given UNIX socket. + + Not available on Windows. + + :param path: path to the socket + :return: a socket stream object + :raises ConnectionFailed: if the connection fails + + """ + path = os.fspath(path) + return await get_async_backend().connect_unix(path) + + +async def create_tcp_listener( + *, + local_host: IPAddressType | None = None, + local_port: int = 0, + family: AnyIPAddressFamily = socket.AddressFamily.AF_UNSPEC, + backlog: int = 65536, + reuse_port: bool = False, +) -> MultiListener[SocketStream]: + """ + Create a TCP socket listener. + + :param local_port: port number to listen on + :param local_host: IP address of the interface to listen on. If omitted, listen on + all IPv4 and IPv6 interfaces. To listen on all interfaces on a specific address + family, use ``0.0.0.0`` for IPv4 or ``::`` for IPv6. + :param family: address family (used if ``local_host`` was omitted) + :param backlog: maximum number of queued incoming connections (up to a maximum of + 2**16, or 65536) + :param reuse_port: ``True`` to allow multiple sockets to bind to the same + address/port (not supported on Windows) + :return: a multi-listener object containing one or more socket listeners + :raises OSError: if there's an error creating a socket, or binding to one or more + interfaces failed + + """ + asynclib = get_async_backend() + backlog = min(backlog, 65536) + local_host = str(local_host) if local_host is not None else None + + def setup_raw_socket( + fam: AddressFamily, + bind_addr: tuple[str, int] | tuple[str, int, int, int], + *, + v6only: bool = True, + ) -> socket.socket: + sock = socket.socket(fam) + try: + sock.setblocking(False) + + if fam == AddressFamily.AF_INET6: + sock.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, v6only) + + # For Windows, enable exclusive address use. For others, enable address + # reuse. + if sys.platform == "win32": + sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) + else: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + if reuse_port: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + + # Workaround for #554 + if fam == socket.AF_INET6 and "%" in bind_addr[0]: + addr, scope_id = bind_addr[0].split("%", 1) + bind_addr = (addr, bind_addr[1], 0, int(scope_id)) + + sock.bind(bind_addr) + sock.listen(backlog) + except BaseException: + sock.close() + raise + + return sock + + # We passing type=0 on non-Windows platforms as a workaround for a uvloop bug + # where we don't get the correct scope ID for IPv6 link-local addresses when passing + # type=socket.SOCK_STREAM to getaddrinfo(): + # https://github.com/MagicStack/uvloop/issues/539 + gai_res = await getaddrinfo( + local_host, + local_port, + family=family, + type=socket.SOCK_STREAM if sys.platform == "win32" else 0, + flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, + ) + + # The set comprehension is here to work around a glibc bug: + # https://sourceware.org/bugzilla/show_bug.cgi?id=14969 + sockaddrs = sorted({res for res in gai_res if res[1] == SocketKind.SOCK_STREAM}) + + # Special case for dual-stack binding on the "any" interface + if ( + local_host is None + and family == AddressFamily.AF_UNSPEC + and socket.has_dualstack_ipv6() + and any(fam == AddressFamily.AF_INET6 for fam, *_ in gai_res) + ): + raw_socket = setup_raw_socket( + AddressFamily.AF_INET6, ("::", local_port), v6only=False + ) + listener = asynclib.create_tcp_listener(raw_socket) + return MultiListener([listener]) + + errors: list[OSError] = [] + try: + for _ in range(len(sockaddrs)): + listeners: list[SocketListener] = [] + bound_ephemeral_port = local_port + try: + for fam, *_, sockaddr in sockaddrs: + sockaddr = sockaddr[0], bound_ephemeral_port, *sockaddr[2:] + raw_socket = setup_raw_socket(fam, sockaddr) + + # Store the assigned port if an ephemeral port was requested, so + # we'll bind to the same port on all interfaces + if local_port == 0 and len(gai_res) > 1: + bound_ephemeral_port = raw_socket.getsockname()[1] + + listeners.append(asynclib.create_tcp_listener(raw_socket)) + except BaseException as exc: + for listener in listeners: + await listener.aclose() + + # If an ephemeral port was requested but binding the assigned port + # failed for another interface, rotate the address list and try again + if ( + isinstance(exc, OSError) + and exc.errno == errno.EADDRINUSE + and local_port == 0 + and bound_ephemeral_port + ): + errors.append(exc) + sockaddrs.append(sockaddrs.pop(0)) + continue + + raise + + return MultiListener(listeners) + + raise OSError( + f"Could not create {len(sockaddrs)} listeners with a consistent port" + ) from ExceptionGroup("Several bind attempts failed", errors) + finally: + del errors # Prevent reference cycles + + +async def create_unix_listener( + path: str | bytes | PathLike[Any], + *, + mode: int | None = None, + backlog: int = 65536, +) -> SocketListener: + """ + Create a UNIX socket listener. + + Not available on Windows. + + :param path: path of the socket + :param mode: permissions to set on the socket + :param backlog: maximum number of queued incoming connections (up to a maximum of + 2**16, or 65536) + :return: a listener object + + .. versionchanged:: 3.0 + If a socket already exists on the file system in the given path, it will be + removed first. + + """ + backlog = min(backlog, 65536) + raw_socket = await setup_unix_local_socket(path, mode, socket.SOCK_STREAM) + try: + raw_socket.listen(backlog) + return get_async_backend().create_unix_listener(raw_socket) + except BaseException: + raw_socket.close() + raise + + +async def create_udp_socket( + family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC, + *, + local_host: IPAddressType | None = None, + local_port: int = 0, + reuse_port: bool = False, +) -> UDPSocket: + """ + Create a UDP socket. + + If ``port`` has been given, the socket will be bound to this port on the local + machine, making this socket suitable for providing UDP based services. + + :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically + determined from ``local_host`` if omitted + :param local_host: IP address or host name of the local interface to bind to + :param local_port: local port to bind to + :param reuse_port: ``True`` to allow multiple sockets to bind to the same + address/port (not supported on Windows) + :return: a UDP socket + + """ + if family is AddressFamily.AF_UNSPEC and not local_host: + raise ValueError('Either "family" or "local_host" must be given') + + if local_host: + gai_res = await getaddrinfo( + str(local_host), + local_port, + family=family, + type=socket.SOCK_DGRAM, + flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, + ) + family = cast(AnyIPAddressFamily, gai_res[0][0]) + local_address = gai_res[0][-1] + elif family is AddressFamily.AF_INET6: + local_address = ("::", 0) + else: + local_address = ("0.0.0.0", 0) + + sock = await get_async_backend().create_udp_socket( + family, local_address, None, reuse_port + ) + return cast(UDPSocket, sock) + + +async def create_connected_udp_socket( + remote_host: IPAddressType, + remote_port: int, + *, + family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC, + local_host: IPAddressType | None = None, + local_port: int = 0, + reuse_port: bool = False, +) -> ConnectedUDPSocket: + """ + Create a connected UDP socket. + + Connected UDP sockets can only communicate with the specified remote host/port, an + any packets sent from other sources are dropped. + + :param remote_host: remote host to set as the default target + :param remote_port: port on the remote host to set as the default target + :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically + determined from ``local_host`` or ``remote_host`` if omitted + :param local_host: IP address or host name of the local interface to bind to + :param local_port: local port to bind to + :param reuse_port: ``True`` to allow multiple sockets to bind to the same + address/port (not supported on Windows) + :return: a connected UDP socket + + """ + local_address = None + if local_host: + gai_res = await getaddrinfo( + str(local_host), + local_port, + family=family, + type=socket.SOCK_DGRAM, + flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, + ) + family = cast(AnyIPAddressFamily, gai_res[0][0]) + local_address = gai_res[0][-1] + + gai_res = await getaddrinfo( + str(remote_host), remote_port, family=family, type=socket.SOCK_DGRAM + ) + family = cast(AnyIPAddressFamily, gai_res[0][0]) + remote_address = gai_res[0][-1] + + sock = await get_async_backend().create_udp_socket( + family, local_address, remote_address, reuse_port + ) + return cast(ConnectedUDPSocket, sock) + + +async def create_unix_datagram_socket( + *, + local_path: None | str | bytes | PathLike[Any] = None, + local_mode: int | None = None, +) -> UNIXDatagramSocket: + """ + Create a UNIX datagram socket. + + Not available on Windows. + + If ``local_path`` has been given, the socket will be bound to this path, making this + socket suitable for receiving datagrams from other processes. Other processes can + send datagrams to this socket only if ``local_path`` is set. + + If a socket already exists on the file system in the ``local_path``, it will be + removed first. + + :param local_path: the path on which to bind to + :param local_mode: permissions to set on the local socket + :return: a UNIX datagram socket + + """ + raw_socket = await setup_unix_local_socket( + local_path, local_mode, socket.SOCK_DGRAM + ) + return await get_async_backend().create_unix_datagram_socket(raw_socket, None) + + +async def create_connected_unix_datagram_socket( + remote_path: str | bytes | PathLike[Any], + *, + local_path: None | str | bytes | PathLike[Any] = None, + local_mode: int | None = None, +) -> ConnectedUNIXDatagramSocket: + """ + Create a connected UNIX datagram socket. + + Connected datagram sockets can only communicate with the specified remote path. + + If ``local_path`` has been given, the socket will be bound to this path, making + this socket suitable for receiving datagrams from other processes. Other processes + can send datagrams to this socket only if ``local_path`` is set. + + If a socket already exists on the file system in the ``local_path``, it will be + removed first. + + :param remote_path: the path to set as the default target + :param local_path: the path on which to bind to + :param local_mode: permissions to set on the local socket + :return: a connected UNIX datagram socket + + """ + remote_path = os.fspath(remote_path) + raw_socket = await setup_unix_local_socket( + local_path, local_mode, socket.SOCK_DGRAM + ) + return await get_async_backend().create_unix_datagram_socket( + raw_socket, remote_path + ) + + +async def getaddrinfo( + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, +) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int]]]: + """ + Look up a numeric IP address given a host name. + + Internationalized domain names are translated according to the (non-transitional) + IDNA 2008 standard. + + .. note:: 4-tuple IPv6 socket addresses are automatically converted to 2-tuples of + (host, port), unlike what :func:`socket.getaddrinfo` does. + + :param host: host name + :param port: port number + :param family: socket family (`'AF_INET``, ...) + :param type: socket type (``SOCK_STREAM``, ...) + :param proto: protocol number + :param flags: flags to pass to upstream ``getaddrinfo()`` + :return: list of tuples containing (family, type, proto, canonname, sockaddr) + + .. seealso:: :func:`socket.getaddrinfo` + + """ + # Handle unicode hostnames + encoded_host = idna2008_resolve(host) if isinstance(host, str) else host + gai_res = await get_async_backend().getaddrinfo( + encoded_host, port, family=family, type=type, proto=proto, flags=flags + ) + return [ + (family, type, proto, canonname, convert_ipv6_sockaddr(sockaddr)) + for family, type, proto, canonname, sockaddr in gai_res + # filter out IPv6 results when IPv6 is disabled + if not isinstance(sockaddr[0], int) + ] + + +def getnameinfo(sockaddr: IPSockAddrType, flags: int = 0) -> Awaitable[tuple[str, str]]: + """ + Look up the host name of an IP address. + + :param sockaddr: socket address (e.g. (ipaddress, port) for IPv4) + :param flags: flags to pass to upstream ``getnameinfo()`` + :return: a tuple of (host name, service name) + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. seealso:: :func:`socket.getnameinfo` + + """ + return get_async_backend().getnameinfo(sockaddr, flags) + + +@deprecated("This function is deprecated; use `wait_readable` instead") +def wait_socket_readable(sock: socket.socket) -> Awaitable[None]: + """ + .. deprecated:: 4.7.0 + Use :func:`wait_readable` instead. + + Wait until the given socket has data to be read. + + .. warning:: Only use this on raw sockets that have not been wrapped by any higher + level constructs like socket streams! + + :param sock: a socket object + :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the + socket to become readable + :raises ~anyio.BusyResourceError: if another task is already waiting for the socket + to become readable + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().wait_readable(sock.fileno()) + + +@deprecated("This function is deprecated; use `wait_writable` instead") +def wait_socket_writable(sock: socket.socket) -> Awaitable[None]: + """ + .. deprecated:: 4.7.0 + Use :func:`wait_writable` instead. + + Wait until the given socket can be written to. + + This does **NOT** work on Windows when using the asyncio backend with a proactor + event loop (default on py3.8+). + + .. warning:: Only use this on raw sockets that have not been wrapped by any higher + level constructs like socket streams! + + :param sock: a socket object + :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the + socket to become writable + :raises ~anyio.BusyResourceError: if another task is already waiting for the socket + to become writable + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().wait_writable(sock.fileno()) + + +def wait_readable(obj: FileDescriptorLike) -> Awaitable[None]: + """ + Wait until the given object has data to be read. + + On Unix systems, ``obj`` must either be an integer file descriptor, or else an + object with a ``.fileno()`` method which returns an integer file descriptor. Any + kind of file descriptor can be passed, though the exact semantics will depend on + your kernel. For example, this probably won't do anything useful for on-disk files. + + On Windows systems, ``obj`` must either be an integer ``SOCKET`` handle, or else an + object with a ``.fileno()`` method which returns an integer ``SOCKET`` handle. File + descriptors aren't supported, and neither are handles that refer to anything besides + a ``SOCKET``. + + On backends where this functionality is not natively provided (asyncio + ``ProactorEventLoop`` on Windows), it is provided using a separate selector thread + which is set to shut down when the interpreter shuts down. + + .. warning:: Don't use this on raw sockets that have been wrapped by any higher + level constructs like socket streams! + + :param obj: an object with a ``.fileno()`` method or an integer handle + :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the + object to become readable + :raises ~anyio.BusyResourceError: if another task is already waiting for the object + to become readable + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().wait_readable(obj) + + +def wait_writable(obj: FileDescriptorLike) -> Awaitable[None]: + """ + Wait until the given object can be written to. + + :param obj: an object with a ``.fileno()`` method or an integer handle + :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the + object to become writable + :raises ~anyio.BusyResourceError: if another task is already waiting for the object + to become writable + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. seealso:: See the documentation of :func:`wait_readable` for the definition of + ``obj`` and notes on backend compatibility. + + .. warning:: Don't use this on raw sockets that have been wrapped by any higher + level constructs like socket streams! + + """ + return get_async_backend().wait_writable(obj) + + +def notify_closing(obj: FileDescriptorLike) -> None: + """ + Call this before closing a file descriptor (on Unix) or socket (on + Windows). This will cause any `wait_readable` or `wait_writable` + calls on the given object to immediately wake up and raise + `~anyio.ClosedResourceError`. + + This doesn't actually close the object – you still have to do that + yourself afterwards. Also, you want to be careful to make sure no + new tasks start waiting on the object in between when you call this + and when it's actually closed. So to close something properly, you + usually want to do these steps in order: + + 1. Explicitly mark the object as closed, so that any new attempts + to use it will abort before they start. + 2. Call `notify_closing` to wake up any already-existing users. + 3. Actually close the object. + + It's also possible to do them in a different order if that's more + convenient, *but only if* you make sure not to have any checkpoints in + between the steps. This way they all happen in a single atomic + step, so other tasks won't be able to tell what order they happened + in anyway. + + :param obj: an object with a ``.fileno()`` method or an integer handle + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + get_async_backend().notify_closing(obj) + + +# +# Private API +# + + +def convert_ipv6_sockaddr( + sockaddr: tuple[str, int, int, int] | tuple[str, int], +) -> tuple[str, int]: + """ + Convert a 4-tuple IPv6 socket address to a 2-tuple (address, port) format. + + If the scope ID is nonzero, it is added to the address, separated with ``%``. + Otherwise the flow id and scope id are simply cut off from the tuple. + Any other kinds of socket addresses are returned as-is. + + :param sockaddr: the result of :meth:`~socket.socket.getsockname` + :return: the converted socket address + + """ + # This is more complicated than it should be because of MyPy + if isinstance(sockaddr, tuple) and len(sockaddr) == 4: + host, port, _flowinfo, scope_id = sockaddr + if scope_id: + # PyPy (as of v7.3.11) leaves the interface name in the result, so + # we discard it and only get the scope ID from the end + # (https://foss.heptapod.net/pypy/pypy/-/issues/3938) + host = host.split("%")[0] + + # Add scope_id to the address + return f"{host}%{scope_id}", port + else: + return host, port + else: + return sockaddr + + +async def setup_unix_local_socket( + path: None | str | bytes | PathLike[Any], + mode: int | None, + socktype: int, +) -> socket.socket: + """ + Create a UNIX local socket object, deleting the socket at the given path if it + exists. + + Not available on Windows. + + :param path: path of the socket + :param mode: permissions to set on the socket + :param socktype: socket.SOCK_STREAM or socket.SOCK_DGRAM + + """ + path_str: str | None + if path is not None: + path_str = os.fsdecode(path) + + # Linux abstract namespace sockets aren't backed by a concrete file so skip stat call + if not path_str.startswith("\0"): + # Copied from pathlib... + try: + stat_result = os.stat(path) + except OSError as e: + if e.errno not in ( + errno.ENOENT, + errno.ENOTDIR, + errno.EBADF, + errno.ELOOP, + ): + raise + else: + if stat.S_ISSOCK(stat_result.st_mode): + os.unlink(path) + else: + path_str = None + + raw_socket = socket.socket(socket.AF_UNIX, socktype) + raw_socket.setblocking(False) + + if path_str is not None: + try: + await to_thread.run_sync(raw_socket.bind, path_str, abandon_on_cancel=True) + if mode is not None: + await to_thread.run_sync(chmod, path_str, mode, abandon_on_cancel=True) + except BaseException: + raw_socket.close() + raise + + return raw_socket + + +@dataclass +class TCPConnectable(ByteStreamConnectable): + """ + Connects to a TCP server at the given host and port. + + :param host: host name or IP address of the server + :param port: TCP port number of the server + """ + + host: str | IPv4Address | IPv6Address + port: int + + def __post_init__(self) -> None: + if self.port < 1 or self.port > 65535: + raise ValueError("TCP port number out of range") + + @override + async def connect(self) -> SocketStream: + try: + return await connect_tcp(self.host, self.port) + except OSError as exc: + raise ConnectionFailed( + f"error connecting to {self.host}:{self.port}: {exc}" + ) from exc + + +@dataclass +class UNIXConnectable(ByteStreamConnectable): + """ + Connects to a UNIX domain socket at the given path. + + :param path: the file system path of the socket + """ + + path: str | bytes | PathLike[str] | PathLike[bytes] + + @override + async def connect(self) -> UNIXSocketStream: + try: + return await connect_unix(self.path) + except OSError as exc: + raise ConnectionFailed(f"error connecting to {self.path!r}: {exc}") from exc + + +def as_connectable( + remote: ByteStreamConnectable + | tuple[str | IPv4Address | IPv6Address, int] + | str + | bytes + | PathLike[str], + /, + *, + tls: bool = False, + ssl_context: ssl.SSLContext | None = None, + tls_hostname: str | None = None, + tls_standard_compatible: bool = True, +) -> ByteStreamConnectable: + """ + Return a byte stream connectable from the given object. + + If a bytestream connectable is given, it is returned unchanged. + If a tuple of (host, port) is given, a TCP connectable is returned. + If a string or bytes path is given, a UNIX connectable is returned. + + If ``tls=True``, the connectable will be wrapped in a + :class:`~.streams.tls.TLSConnectable`. + + :param remote: a connectable, a tuple of (host, port) or a path to a UNIX socket + :param tls: if ``True``, wrap the plaintext connectable in a + :class:`~.streams.tls.TLSConnectable`, using the provided TLS settings) + :param ssl_context: if ``tls=True``, the SSLContext object to use (if not provided, + a secure default will be created) + :param tls_hostname: if ``tls=True``, host name of the server to use for checking + the server certificate (defaults to the host portion of the address for TCP + connectables) + :param tls_standard_compatible: if ``False`` and ``tls=True``, makes the TLS stream + skip the closing handshake when closing the connection, so it won't raise an + exception if the server does the same + + """ + connectable: TCPConnectable | UNIXConnectable | TLSConnectable + if isinstance(remote, ByteStreamConnectable): + return remote + elif isinstance(remote, tuple) and len(remote) == 2: + connectable = TCPConnectable(*remote) + elif isinstance(remote, (str, bytes, PathLike)): + connectable = UNIXConnectable(remote) + else: + raise TypeError(f"cannot convert {remote!r} to a connectable") + + if tls: + if not tls_hostname and isinstance(connectable, TCPConnectable): + tls_hostname = str(connectable.host) + + connectable = TLSConnectable( + connectable, + ssl_context=ssl_context, + hostname=tls_hostname, + standard_compatible=tls_standard_compatible, + ) + + return connectable diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_streams.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_streams.py new file mode 100644 index 0000000000000000000000000000000000000000..2b9c7df200f9520357503c754bcdea1c047bdda3 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_streams.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import math +from typing import TypeVar +from warnings import warn + +from ..streams.memory import ( + MemoryObjectReceiveStream, + MemoryObjectSendStream, + _MemoryObjectStreamState, +) + +T_Item = TypeVar("T_Item") + + +class create_memory_object_stream( + tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]], +): + """ + Create a memory object stream. + + The stream's item type can be annotated like + :func:`create_memory_object_stream[T_Item]`. + + :param max_buffer_size: number of items held in the buffer until ``send()`` starts + blocking + :param item_type: old way of marking the streams with the right generic type for + static typing (does nothing on AnyIO 4) + + .. deprecated:: 4.0 + Use ``create_memory_object_stream[YourItemType](...)`` instead. + :return: a tuple of (send stream, receive stream) + + """ + + def __new__( # type: ignore[misc] + cls, max_buffer_size: float = 0, item_type: object = None + ) -> tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]]: + if max_buffer_size != math.inf and not isinstance(max_buffer_size, int): + raise ValueError("max_buffer_size must be either an integer or math.inf") + if max_buffer_size < 0: + raise ValueError("max_buffer_size cannot be negative") + if item_type is not None: + warn( + "The item_type argument has been deprecated in AnyIO 4.0. " + "Use create_memory_object_stream[YourItemType](...) instead.", + DeprecationWarning, + stacklevel=2, + ) + + state = _MemoryObjectStreamState[T_Item](max_buffer_size) + return (MemoryObjectSendStream(state), MemoryObjectReceiveStream(state)) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_subprocesses.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_subprocesses.py new file mode 100644 index 0000000000000000000000000000000000000000..53184a2e6cd238fcc56e9eb5b3374b25eeaf7741 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_subprocesses.py @@ -0,0 +1,196 @@ +from __future__ import annotations + +from collections.abc import AsyncIterable, Iterable, Mapping, Sequence +from io import BytesIO +from os import PathLike +from subprocess import PIPE, CalledProcessError, CompletedProcess +from typing import IO, Any, TypeAlias, cast + +from ..abc import Process # noqa: TC001 +from ._eventloop import get_async_backend +from ._tasks import create_task_group + +StrOrBytesPath: TypeAlias = str | bytes | PathLike[str] | PathLike[bytes] + + +async def run_process( + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + input: bytes | None = None, + stdin: int | IO[Any] | None = None, + stdout: int | IO[Any] | None = PIPE, + stderr: int | IO[Any] | None = PIPE, + check: bool = True, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, +) -> CompletedProcess[bytes]: + """ + Run an external command in a subprocess and wait until it completes. + + .. seealso:: :func:`subprocess.run` + + :param command: either a string to pass to the shell, or an iterable of strings + containing the executable name or path and its arguments + :param input: bytes passed to the standard input of the subprocess + :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + a file-like object, or `None`; ``input`` overrides this + :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + a file-like object, or `None` + :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + :data:`subprocess.STDOUT`, a file-like object, or `None` + :param check: if ``True``, raise :exc:`~subprocess.CalledProcessError` if the + process terminates with a return code other than 0 + :param cwd: If not ``None``, change the working directory to this before running the + command + :param env: if not ``None``, this mapping replaces the inherited environment + variables from the parent process + :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used + to specify process startup parameters (Windows only) + :param creationflags: flags that can be used to control the creation of the + subprocess (see :class:`subprocess.Popen` for the specifics) + :param start_new_session: if ``true`` the setsid() system call will be made in the + child process prior to the execution of the subprocess. (POSIX only) + :param pass_fds: sequence of file descriptors to keep open between the parent and + child processes. (POSIX only) + :param user: effective user to run the process as (Python >= 3.9, POSIX only) + :param group: effective group to run the process as (Python >= 3.9, POSIX only) + :param extra_groups: supplementary groups to set in the subprocess (Python >= 3.9, + POSIX only) + :param umask: if not negative, this umask is applied in the child process before + running the given command (Python >= 3.9, POSIX only) + :return: an object representing the completed process + :raises ~subprocess.CalledProcessError: if ``check`` is ``True`` and the process + exits with a nonzero return code + + """ + + async def drain_stream(stream: AsyncIterable[bytes], index: int) -> None: + buffer = BytesIO() + async for chunk in stream: + buffer.write(chunk) + + stream_contents[index] = buffer.getvalue() + + if stdin is not None and input is not None: + raise ValueError("only one of stdin and input is allowed") + + async with await open_process( + command, + stdin=PIPE if input else stdin, + stdout=stdout, + stderr=stderr, + cwd=cwd, + env=env, + startupinfo=startupinfo, + creationflags=creationflags, + start_new_session=start_new_session, + pass_fds=pass_fds, + user=user, + group=group, + extra_groups=extra_groups, + umask=umask, + ) as process: + stream_contents: list[bytes | None] = [None, None] + async with create_task_group() as tg: + if process.stdout: + tg.start_soon(drain_stream, process.stdout, 0) + + if process.stderr: + tg.start_soon(drain_stream, process.stderr, 1) + + if process.stdin and input: + await process.stdin.send(input) + await process.stdin.aclose() + + await process.wait() + + output, errors = stream_contents + if check and process.returncode != 0: + raise CalledProcessError(cast(int, process.returncode), command, output, errors) + + return CompletedProcess(command, cast(int, process.returncode), output, errors) + + +async def open_process( + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None = PIPE, + stdout: int | IO[Any] | None = PIPE, + stderr: int | IO[Any] | None = PIPE, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, +) -> Process: + """ + Start an external command in a subprocess. + + .. seealso:: :class:`subprocess.Popen` + + :param command: either a string to pass to the shell, or an iterable of strings + containing the executable name or path and its arguments + :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, a + file-like object, or ``None`` + :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + a file-like object, or ``None`` + :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + :data:`subprocess.STDOUT`, a file-like object, or ``None`` + :param cwd: If not ``None``, the working directory is changed before executing + :param env: If env is not ``None``, it must be a mapping that defines the + environment variables for the new process + :param creationflags: flags that can be used to control the creation of the + subprocess (see :class:`subprocess.Popen` for the specifics) + :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used + to specify process startup parameters (Windows only) + :param start_new_session: if ``true`` the setsid() system call will be made in the + child process prior to the execution of the subprocess. (POSIX only) + :param pass_fds: sequence of file descriptors to keep open between the parent and + child processes. (POSIX only) + :param user: effective user to run the process as (POSIX only) + :param group: effective group to run the process as (POSIX only) + :param extra_groups: supplementary groups to set in the subprocess (POSIX only) + :param umask: if not negative, this umask is applied in the child process before + running the given command (POSIX only) + :return: an asynchronous process object + + """ + kwargs: dict[str, Any] = {} + if user is not None: + kwargs["user"] = user + + if group is not None: + kwargs["group"] = group + + if extra_groups is not None: + kwargs["extra_groups"] = extra_groups + + if umask >= 0: + kwargs["umask"] = umask + + return await get_async_backend().open_process( + command, + stdin=stdin, + stdout=stdout, + stderr=stderr, + cwd=cwd, + env=env, + startupinfo=startupinfo, + creationflags=creationflags, + start_new_session=start_new_session, + pass_fds=pass_fds, + **kwargs, + ) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_synchronization.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_synchronization.py new file mode 100644 index 0000000000000000000000000000000000000000..cb988c60db56f412ae0e051e08d68380e8b6a413 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_synchronization.py @@ -0,0 +1,778 @@ +from __future__ import annotations + +import math +import sys +from collections import deque +from collections.abc import Callable +from dataclasses import dataclass +from types import TracebackType +from typing import TypeVar + +from ..lowlevel import checkpoint_if_cancelled +from ._eventloop import get_async_backend +from ._exceptions import BusyResourceError, NoEventLoopError +from ._tasks import CancelScope +from ._testing import TaskInfo, get_current_task + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + +T = TypeVar("T") + + +@dataclass(frozen=True) +class EventStatistics: + """ + :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Event.wait` + """ + + tasks_waiting: int + + +@dataclass(frozen=True) +class CapacityLimiterStatistics: + """ + :ivar int borrowed_tokens: number of tokens currently borrowed by tasks + :ivar float total_tokens: total number of available tokens + :ivar tuple borrowers: tasks or other objects currently holding tokens borrowed from + this limiter + :ivar int tasks_waiting: number of tasks waiting on + :meth:`~.CapacityLimiter.acquire` or + :meth:`~.CapacityLimiter.acquire_on_behalf_of` + """ + + borrowed_tokens: int + total_tokens: float + borrowers: tuple[object, ...] + tasks_waiting: int + + +@dataclass(frozen=True) +class LockStatistics: + """ + :ivar bool locked: flag indicating if this lock is locked or not + :ivar ~anyio.TaskInfo owner: task currently holding the lock (or ``None`` if the + lock is not held by any task) + :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Lock.acquire` + """ + + locked: bool + owner: TaskInfo | None + tasks_waiting: int + + +@dataclass(frozen=True) +class ConditionStatistics: + """ + :ivar int tasks_waiting: number of tasks blocked on :meth:`~.Condition.wait` + :ivar ~anyio.LockStatistics lock_statistics: statistics of the underlying + :class:`~.Lock` + """ + + tasks_waiting: int + lock_statistics: LockStatistics + + +@dataclass(frozen=True) +class SemaphoreStatistics: + """ + :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Semaphore.acquire` + + """ + + tasks_waiting: int + + +class Event: + __slots__ = ("__weakref__",) + + def __new__(cls) -> Event: + try: + return get_async_backend().create_event() + except NoEventLoopError: + return EventAdapter() + + def set(self) -> None: + """Set the flag, notifying all listeners.""" + raise NotImplementedError + + def is_set(self) -> bool: + """Return ``True`` if the flag is set, ``False`` if not.""" + raise NotImplementedError + + async def wait(self) -> None: + """ + Wait until the flag has been set. + + If the flag has already been set when this method is called, it returns + immediately. + + """ + raise NotImplementedError + + def statistics(self) -> EventStatistics: + """Return statistics about the current state of this event.""" + raise NotImplementedError + + +class EventAdapter(Event): + __slots__ = "_internal_event", "_is_set" + + def __new__(cls) -> Self: + return object.__new__(cls) + + def __init__(self) -> None: + self._internal_event: Event | None = None + self._is_set = False + + @property + def _event(self) -> Event: + if self._internal_event is None: + self._internal_event = get_async_backend().create_event() + if self._is_set: + self._internal_event.set() + + return self._internal_event + + def set(self) -> None: + if self._internal_event is None: + self._is_set = True + else: + self._event.set() + + def is_set(self) -> bool: + if self._internal_event is None: + return self._is_set + + return self._internal_event.is_set() + + async def wait(self) -> None: + await self._event.wait() + + def statistics(self) -> EventStatistics: + if self._internal_event is None: + return EventStatistics(tasks_waiting=0) + + return self._internal_event.statistics() + + +class Lock: + __slots__ = ("__weakref__",) + + def __new__(cls, *, fast_acquire: bool = False) -> Lock: + try: + return get_async_backend().create_lock(fast_acquire=fast_acquire) + except NoEventLoopError: + return LockAdapter(fast_acquire=fast_acquire) + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + async def acquire(self) -> None: + """Acquire the lock.""" + raise NotImplementedError + + def acquire_nowait(self) -> None: + """ + Acquire the lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + raise NotImplementedError + + def release(self) -> None: + """Release the lock.""" + raise NotImplementedError + + def locked(self) -> bool: + """Return True if the lock is currently held.""" + raise NotImplementedError + + def statistics(self) -> LockStatistics: + """ + Return statistics about the current state of this lock. + + .. versionadded:: 3.0 + """ + raise NotImplementedError + + +class LockAdapter(Lock): + __slots__ = "_fast_acquire", "_internal_lock" + + def __new__(cls, *, fast_acquire: bool = False) -> Self: + return object.__new__(cls) + + def __init__(self, *, fast_acquire: bool = False): + self._internal_lock: Lock | None = None + self._fast_acquire = fast_acquire + + @property + def _lock(self) -> Lock: + if self._internal_lock is None: + self._internal_lock = get_async_backend().create_lock( + fast_acquire=self._fast_acquire + ) + + return self._internal_lock + + async def __aenter__(self) -> None: + await self._lock.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self._internal_lock is not None: + self._internal_lock.release() + + async def acquire(self) -> None: + """Acquire the lock.""" + await self._lock.acquire() + + def acquire_nowait(self) -> None: + """ + Acquire the lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + self._lock.acquire_nowait() + + def release(self) -> None: + """Release the lock.""" + self._lock.release() + + def locked(self) -> bool: + """Return True if the lock is currently held.""" + return self._lock.locked() + + def statistics(self) -> LockStatistics: + """ + Return statistics about the current state of this lock. + + .. versionadded:: 3.0 + + """ + if self._internal_lock is None: + return LockStatistics(False, None, 0) + + return self._internal_lock.statistics() + + +class Condition: + __slots__ = "__weakref__", "_lock", "_owner_task", "_waiters" + + def __init__(self, lock: Lock | None = None): + self._owner_task: TaskInfo | None = None + self._lock = lock or Lock() + self._waiters: deque[Event] = deque() + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + def _check_acquired(self) -> None: + if self._owner_task != get_current_task(): + raise RuntimeError("The current task is not holding the underlying lock") + + async def acquire(self) -> None: + """Acquire the underlying lock.""" + await self._lock.acquire() + self._owner_task = get_current_task() + + def acquire_nowait(self) -> None: + """ + Acquire the underlying lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + self._lock.acquire_nowait() + self._owner_task = get_current_task() + + def release(self) -> None: + """Release the underlying lock.""" + self._lock.release() + + def locked(self) -> bool: + """Return True if the lock is set.""" + return self._lock.locked() + + def notify(self, n: int = 1) -> None: + """Notify exactly n listeners.""" + self._check_acquired() + for _ in range(n): + try: + event = self._waiters.popleft() + except IndexError: + break + + event.set() + + def notify_all(self) -> None: + """Notify all the listeners.""" + self._check_acquired() + for event in self._waiters: + event.set() + + self._waiters.clear() + + async def wait(self) -> None: + """Wait for a notification.""" + await checkpoint_if_cancelled() + self._check_acquired() + event = Event() + self._waiters.append(event) + self.release() + try: + await event.wait() + except BaseException: + if not event.is_set(): + self._waiters.remove(event) + elif self._waiters: + # This task was notified by could not act on it, so pass + # it on to the next task + self._waiters.popleft().set() + + raise + finally: + with CancelScope(shield=True): + await self.acquire() + + async def wait_for(self, predicate: Callable[[], T]) -> T: + """ + Wait until a predicate becomes true. + + :param predicate: a callable that returns a truthy value when the condition is + met + :return: the result of the predicate + + .. versionadded:: 4.11.0 + + """ + while not (result := predicate()): + await self.wait() + + return result + + def statistics(self) -> ConditionStatistics: + """ + Return statistics about the current state of this condition. + + .. versionadded:: 3.0 + """ + return ConditionStatistics(len(self._waiters), self._lock.statistics()) + + +class Semaphore: + __slots__ = "__weakref__", "_fast_acquire" + + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Semaphore: + try: + return get_async_backend().create_semaphore( + initial_value, max_value=max_value, fast_acquire=fast_acquire + ) + except NoEventLoopError: + return SemaphoreAdapter(initial_value, max_value=max_value) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ): + if not isinstance(initial_value, int): + raise TypeError("initial_value must be an integer") + if initial_value < 0: + raise ValueError("initial_value must be >= 0") + if max_value is not None: + if not isinstance(max_value, int): + raise TypeError("max_value must be an integer or None") + if max_value < initial_value: + raise ValueError( + "max_value must be equal to or higher than initial_value" + ) + + self._fast_acquire = fast_acquire + + async def __aenter__(self) -> Self: + await self.acquire() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + async def acquire(self) -> None: + """Decrement the semaphore value, blocking if necessary.""" + raise NotImplementedError + + def acquire_nowait(self) -> None: + """ + Acquire the underlying lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + raise NotImplementedError + + def release(self) -> None: + """Increment the semaphore value.""" + raise NotImplementedError + + @property + def value(self) -> int: + """The current value of the semaphore.""" + raise NotImplementedError + + @property + def max_value(self) -> int | None: + """The maximum value of the semaphore.""" + raise NotImplementedError + + def statistics(self) -> SemaphoreStatistics: + """ + Return statistics about the current state of this semaphore. + + .. versionadded:: 3.0 + """ + raise NotImplementedError + + +class SemaphoreAdapter(Semaphore): + __slots__ = "_initial_value", "_internal_semaphore", "_max_value" + + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Self: + return object.__new__(cls) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> None: + super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire) + self._internal_semaphore: Semaphore | None = None + self._initial_value = initial_value + self._max_value = max_value + + @property + def _semaphore(self) -> Semaphore: + if self._internal_semaphore is None: + self._internal_semaphore = get_async_backend().create_semaphore( + self._initial_value, max_value=self._max_value + ) + + return self._internal_semaphore + + async def acquire(self) -> None: + await self._semaphore.acquire() + + def acquire_nowait(self) -> None: + self._semaphore.acquire_nowait() + + def release(self) -> None: + self._semaphore.release() + + @property + def value(self) -> int: + if self._internal_semaphore is None: + return self._initial_value + + return self._semaphore.value + + @property + def max_value(self) -> int | None: + return self._max_value + + def statistics(self) -> SemaphoreStatistics: + if self._internal_semaphore is None: + return SemaphoreStatistics(tasks_waiting=0) + + return self._semaphore.statistics() + + +class CapacityLimiter: + __slots__ = ("__weakref__",) + + def __new__(cls, total_tokens: float) -> CapacityLimiter: + try: + return get_async_backend().create_capacity_limiter(total_tokens) + except NoEventLoopError: + return CapacityLimiterAdapter(total_tokens) + + async def __aenter__(self) -> None: + raise NotImplementedError + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + raise NotImplementedError + + @property + def total_tokens(self) -> float: + """ + The total number of tokens available for borrowing. + + This is a read-write property. If the total number of tokens is increased, the + proportionate number of tasks waiting on this limiter will be granted their + tokens. + + .. versionchanged:: 3.0 + The property is now writable. + .. versionchanged:: 4.12 + The value can now be set to 0. + + """ + raise NotImplementedError + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + raise NotImplementedError + + @property + def borrowed_tokens(self) -> int: + """The number of tokens that have currently been borrowed.""" + raise NotImplementedError + + @property + def available_tokens(self) -> float: + """The number of tokens currently available to be borrowed""" + raise NotImplementedError + + def acquire_nowait(self) -> None: + """ + Acquire a token for the current task without waiting for one to become + available. + + :raises ~anyio.WouldBlock: if there are no tokens available for borrowing + + """ + raise NotImplementedError + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + """ + Acquire a token without waiting for one to become available. + + :param borrower: the entity borrowing a token + :raises ~anyio.WouldBlock: if there are no tokens available for borrowing + + """ + raise NotImplementedError + + async def acquire(self) -> None: + """ + Acquire a token for the current task, waiting if necessary for one to become + available. + + """ + raise NotImplementedError + + async def acquire_on_behalf_of(self, borrower: object) -> None: + """ + Acquire a token, waiting if necessary for one to become available. + + :param borrower: the entity borrowing a token + + """ + raise NotImplementedError + + def release(self) -> None: + """ + Release the token held by the current task. + + :raises RuntimeError: if the current task has not borrowed a token from this + limiter. + + """ + raise NotImplementedError + + def release_on_behalf_of(self, borrower: object) -> None: + """ + Release the token held by the given borrower. + + :raises RuntimeError: if the borrower has not borrowed a token from this + limiter. + + """ + raise NotImplementedError + + def statistics(self) -> CapacityLimiterStatistics: + """ + Return statistics about the current state of this limiter. + + .. versionadded:: 3.0 + + """ + raise NotImplementedError + + +class CapacityLimiterAdapter(CapacityLimiter): + __slots__ = "_internal_limiter", "_total_tokens" + + def __new__(cls, total_tokens: float) -> Self: + return object.__new__(cls) + + def __init__(self, total_tokens: float) -> None: + self._internal_limiter: CapacityLimiter | None = None + self.total_tokens = total_tokens + + @property + def _limiter(self) -> CapacityLimiter: + if self._internal_limiter is None: + self._internal_limiter = get_async_backend().create_capacity_limiter( + self._total_tokens + ) + + return self._internal_limiter + + async def __aenter__(self) -> None: + await self._limiter.__aenter__() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + return await self._limiter.__aexit__(exc_type, exc_val, exc_tb) + + @property + def total_tokens(self) -> float: + if self._internal_limiter is None: + return self._total_tokens + + return self._internal_limiter.total_tokens + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + if not isinstance(value, int) and not math.isinf(value): + raise TypeError("total_tokens must be an int or math.inf") + elif value < 0: + raise ValueError("total_tokens must be >= 0") + + if self._internal_limiter is None: + self._total_tokens = value + return + + self._limiter.total_tokens = value + + @property + def borrowed_tokens(self) -> int: + if self._internal_limiter is None: + return 0 + + return self._internal_limiter.borrowed_tokens + + @property + def available_tokens(self) -> float: + if self._internal_limiter is None: + return self._total_tokens + + return self._internal_limiter.available_tokens + + def acquire_nowait(self) -> None: + self._limiter.acquire_nowait() + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + self._limiter.acquire_on_behalf_of_nowait(borrower) + + async def acquire(self) -> None: + await self._limiter.acquire() + + async def acquire_on_behalf_of(self, borrower: object) -> None: + await self._limiter.acquire_on_behalf_of(borrower) + + def release(self) -> None: + self._limiter.release() + + def release_on_behalf_of(self, borrower: object) -> None: + self._limiter.release_on_behalf_of(borrower) + + def statistics(self) -> CapacityLimiterStatistics: + if self._internal_limiter is None: + return CapacityLimiterStatistics( + borrowed_tokens=0, + total_tokens=self.total_tokens, + borrowers=(), + tasks_waiting=0, + ) + + return self._internal_limiter.statistics() + + +class ResourceGuard: + """ + A context manager for ensuring that a resource is only used by a single task at a + time. + + Entering this context manager while the previous has not exited it yet will trigger + :exc:`BusyResourceError`. + + :param action: the action to guard against (visible in the :exc:`BusyResourceError` + when triggered, e.g. "Another task is already {action} this resource") + + .. versionadded:: 4.1 + """ + + __slots__ = "__weakref__", "_guarded", "action" + + def __init__(self, action: str = "using"): + self.action: str = action + self._guarded = False + + def __enter__(self) -> None: + if self._guarded: + raise BusyResourceError(self.action) + + self._guarded = True + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self._guarded = False diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_tasks.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_tasks.py new file mode 100644 index 0000000000000000000000000000000000000000..044eda6dc90ab446b5b6e1dc1626cb6dd510e7db --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_tasks.py @@ -0,0 +1,464 @@ +from __future__ import annotations + +import math +import sys +from collections.abc import ( + Coroutine, + Generator, +) +from contextlib import contextmanager +from enum import Enum, auto +from inspect import iscoroutine +from types import TracebackType +from typing import Any, Generic, final + +from ..abc import TaskGroup, TaskStatus +from ._eventloop import get_async_backend, get_cancelled_exc_class +from ._exceptions import TaskCancelled, TaskFailed, TaskNotFinished + +if sys.version_info >= (3, 13): + from typing import TypeVar +else: + from typing_extensions import TypeVar + +if sys.version_info >= (3, 11): + from typing import Never, Self, TypeVarTuple +else: + from typing_extensions import Never, Self, TypeVarTuple + +T = TypeVar("T") +T_co = TypeVar("T_co", covariant=True) +T_startval_co = TypeVar("T_startval_co", covariant=True, default=Never) +PosArgsT = TypeVarTuple("PosArgsT") + + +class _IgnoredTaskStatus(TaskStatus[object]): + def started(self, value: object = None) -> None: + pass + + +TASK_STATUS_IGNORED = _IgnoredTaskStatus() + + +class CancelScope: + """ + Wraps a unit of work that can be made separately cancellable. + + :param deadline: The time (clock value) when this scope is cancelled automatically + :param shield: ``True`` to shield the cancel scope from external cancellation + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + """ + + __slots__ = ("__weakref__",) + + def __new__( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + return get_async_backend().create_cancel_scope(shield=shield, deadline=deadline) + + def cancel(self, reason: str | None = None) -> None: + """ + Cancel this scope immediately. + + :param reason: a message describing the reason for the cancellation + + """ + raise NotImplementedError + + @property + def deadline(self) -> float: + """ + The time (clock value) when this scope is cancelled automatically. + + Will be ``float('inf')`` if no timeout has been set. + + """ + raise NotImplementedError + + @deadline.setter + def deadline(self, value: float) -> None: + raise NotImplementedError + + @property + def cancel_called(self) -> bool: + """``True`` if :meth:`cancel` has been called.""" + raise NotImplementedError + + @property + def cancelled_caught(self) -> bool: + """ + ``True`` if this scope suppressed a cancellation exception it itself raised. + + This is typically used to check if any work was interrupted, or to see if the + scope was cancelled due to its deadline being reached. The value will, however, + only be ``True`` if the cancellation was triggered by the scope itself (and not + an outer scope). + + """ + raise NotImplementedError + + @property + def shield(self) -> bool: + """ + ``True`` if this scope is shielded from external cancellation. + + While a scope is shielded, it will not receive cancellations from outside. + + """ + raise NotImplementedError + + @shield.setter + def shield(self, value: bool) -> None: + raise NotImplementedError + + def __enter__(self) -> Self: + raise NotImplementedError + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + raise NotImplementedError + + +@contextmanager +def fail_at( + deadline: float | None, shield: bool = False, reason: str | None = None +) -> Generator[CancelScope, None, None]: + """ + Create a context manager which raises a :class:`TimeoutError` if the code in the + enclosing context does not finish by the given deadline. + + :param deadline: the deadline before raising the exception, or + ``None`` to disable the timeout + :param shield: ``True`` to shield the cancel scope from external cancellation + :param reason: explanation for timeout to add to the message of a raised `TimeoutError` + :return: a context manager that yields a cancel scope + :rtype: :class:`~typing.ContextManager`\\[:class:`~anyio.CancelScope`\\] + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. versionadded:: 4.15.0 + + """ + current_time = get_async_backend().current_time + effective_deadline = math.inf if deadline is None else deadline + with get_async_backend().create_cancel_scope( + deadline=effective_deadline, shield=shield + ) as cancel_scope: + yield cancel_scope + + if cancel_scope.cancelled_caught and current_time() >= cancel_scope.deadline: + raise TimeoutError(reason) if reason else TimeoutError + + +@contextmanager +def fail_after( + delay: float | None, shield: bool = False, reason: str | None = None +) -> Generator[CancelScope, None, None]: + """ + Create a context manager which raises a :class:`TimeoutError` if the code in the + enclosing context block does not finish in time. + + :param delay: maximum allowed time (in seconds) before raising the exception, or + ``None`` to disable the timeout + :param shield: ``True`` to shield the cancel scope from external cancellation + :param reason: explanation for timeout to add to the message of a raised `TimeoutError` + :return: a context manager that yields a cancel scope + :rtype: :class:`~typing.ContextManager`\\[:class:`~anyio.CancelScope`\\] + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + current_time = get_async_backend().current_time + deadline = (current_time() + delay) if delay is not None else math.inf + with fail_at(deadline, shield=shield, reason=reason) as scope: + yield scope + + +def move_on_at(deadline: float | None, shield: bool = False) -> CancelScope: + """ + Create a cancel scope with a deadline that expires after the given delay. + + :param deadline: the deadline before exiting the context block, or + ``None`` to disable the timeout + :param shield: ``True`` to shield the cancel scope from external cancellation + :return: a cancel scope + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. versionadded:: 4.15.0 + + """ + return get_async_backend().create_cancel_scope( + deadline=deadline if deadline is not None else math.inf, shield=shield + ) + + +def move_on_after(delay: float | None, shield: bool = False) -> CancelScope: + """ + Create a cancel scope with a deadline that expires after the given delay. + + :param delay: maximum allowed time (in seconds) before exiting the context block, or + ``None`` to disable the timeout + :param shield: ``True`` to shield the cancel scope from external cancellation + :return: a cancel scope + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. note:: Unlike with :func:`fail_after`, the timer starts when this function is + called, not when the context manager is entered. This will be changed in v5.0. + + """ + deadline = ( + (get_async_backend().current_time() + delay) if delay is not None else math.inf + ) + return get_async_backend().create_cancel_scope(deadline=deadline, shield=shield) + + +def current_effective_deadline() -> float: + """ + Return the nearest deadline among all the cancel scopes effective for the current + task. + + :return: a clock value from the event loop's internal clock (or ``float('inf')`` if + there is no deadline in effect, or ``float('-inf')`` if the current scope has + been cancelled) + :rtype: float + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().current_effective_deadline() + + +def create_task_group() -> TaskGroup: + """ + Create a task group. + + :return: a task group + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().create_task_group() + + +@final +class TaskHandle(Generic[T_co, T_startval_co]): + """ + Returned from the task-spawning methods of :class:`TaskGroup`. Can be awaited on to + get the return value of the task (or the raised exception). If the task was + terminated by a :exc:`BaseException`, :exc:`TaskFailed` will be raised (or its + subclass :exc:`TaskCancelled` if the task was cancelled). + + .. versionadded:: 4.14.0 + """ + + class Status(Enum): + """ + The status of a task handle. + + .. attribute:: PENDING + + The task has not finished yet. + .. attribute:: FINISHED + + The task has finished with a return value. + .. attribute:: CANCELLING + + The task has been cancelled but has not finished yet. + .. attribute:: CANCELLED + + The task was cancelled and has finished since. + .. attribute:: FAILED + + The task raised an exception. + """ + + PENDING = auto() + FINISHED = auto() + CANCELLING = auto() + CANCELLED = auto() + FAILED = auto() + + __slots__ = ( + "__weakref__", + "_cancel_scope", + "_coro", + "_exception", + "_finished_event", + "_name", + "_return_value", + "_start_value", + ) + + _return_value: T_co + _start_value: T_startval_co + + def __init__( + self, + coro: Coroutine[Any, Any, T_co], + name: object, + cancel_scope: CancelScope | None = None, + ) -> None: + from ._synchronization import Event + + self._coro = coro + self._cancel_scope = cancel_scope if cancel_scope is not None else CancelScope() + self._finished_event = Event() + self._exception: BaseException | None = None + + if name is not None: + self._name = str(name) + elif iscoroutine(coro): + self._name = coro.__qualname__ + else: + self._name = str(coro) # coroutine-like object (e.g. asend() objects) + + async def _run_coro(self) -> None: + __tracebackhide__ = True + + with self._cancel_scope: + try: + retval = await self._coro + except BaseException as exc: + self._exception = exc + raise + else: + self._return_value = retval + finally: + self._finished_event.set() + del self # Break the reference cycle + + def cancel(self) -> None: + """ + Set the task to a cancelled state. + + This will interrupt any interruptible asynchronous operation, and will cause + any further awaits on this task to get immediately cancelled, unless done in + a shielded cancel scope. + + If the task has already finished, this method has no effect. + """ + if not self._finished_event.is_set(): + self._cancel_scope.cancel() + + @property + def coro(self) -> Coroutine[Any, Any, T_co]: + """ + The coroutine object that was passed to one of the task-spawning methods in + :class:`TaskGroup`. + """ + return self._coro + + @property + def status(self) -> TaskHandle.Status: + """ + The current status of the task. + + Every task starts in the :attr:`~TaskHandle.Status.PENDING` state. + If a task is cancelled while in this state, it will transition to the + :attr:`~TaskHandle.Status.CANCELLING` state. When the task finishes, it will + transition to one of the three final states ( + :attr:`~TaskHandle.Status.FINISHED`, :attr:`~TaskHandle.Status.FAILED`, or + :attr:`~TaskHandle.Status.CANCELLING`) depending on the exception the task + raised, if any. No other status transitions will happen. + """ + if not self._finished_event.is_set(): + if self._cancel_scope.cancel_called: + return TaskHandle.Status.CANCELLING + else: + return TaskHandle.Status.PENDING + elif self._exception is not None: + if isinstance(self._exception, get_cancelled_exc_class()): + return TaskHandle.Status.CANCELLED + else: + return TaskHandle.Status.FAILED + else: + return TaskHandle.Status.FINISHED + + @property + def name(self) -> str: + """The name of the task.""" + return self._name + + @property + def exception(self) -> BaseException | None: + """ + The exception raised by the task, or ``None`` if it finished without raising. + + :raises TaskNotFinished: if the task has not finished yet + :raises TaskCancelled: if the task was cancelled + + """ + match self.status: + case TaskHandle.Status.PENDING: + raise TaskNotFinished("the task has not finished yet") + case TaskHandle.Status.FINISHED: + return None + case TaskHandle.Status.CANCELLING: + raise TaskCancelled("the task was cancelled") + case TaskHandle.Status.CANCELLED: + raise TaskCancelled("the task was cancelled") from self._exception + case TaskHandle.Status.FAILED: + return self._exception + + @property + def return_value(self) -> T_co: + """ + The return value of the task. + + :raises TaskNotFinished: if the task has not finished yet + :raises TaskCancelled: if the task was cancelled + :raises TaskFailed: if the task raised an exception + + """ + match self.status: + case TaskHandle.Status.PENDING: + raise TaskNotFinished("the task has not finished yet") + case TaskHandle.Status.FINISHED: + return self._return_value + case TaskHandle.Status.CANCELLING: + raise TaskCancelled("the task was cancelled") + case TaskHandle.Status.CANCELLED: + raise TaskCancelled("the task was cancelled") from self._exception + case TaskHandle.Status.FAILED: + raise TaskFailed("the task raised an exception") from self._exception + + @property + def start_value(self) -> T_startval_co: + """ + The value passed to :meth:`task_status.started() <.abc.TaskStatus.started>`, + + :raises RuntimeError: if the task was not started with :meth:`TaskGroup.start() + <.abc.TaskGroup.start>` + """ + try: + return self._start_value + except AttributeError: + raise RuntimeError( + "the task was not started with TaskGroup.start()" + ) from None + + async def wait(self) -> None: + """ + Wait for the task to finish. + + This method will return as soon as the task has finished, no matter how it + happened. + """ + await self._finished_event.wait() + + def __await__(self) -> Generator[Any, Any, T_co]: + yield from self._finished_event.wait().__await__() + return self.return_value + + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__} {self.status.name.lower()} " + f"name={self._name!r} coro={self._coro!r}>" + ) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_tempfile.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_tempfile.py new file mode 100644 index 0000000000000000000000000000000000000000..96030867befb177e754f596efcc3e9ee1f234442 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_tempfile.py @@ -0,0 +1,615 @@ +from __future__ import annotations + +import os +import sys +import tempfile +from collections.abc import Iterable +from io import BytesIO, TextIOWrapper +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + AnyStr, + Generic, + overload, +) + +from .. import to_thread +from .._core._fileio import AsyncFile +from ..lowlevel import checkpoint_if_cancelled +from ._tasks import CancelScope + +if TYPE_CHECKING: + from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer + + +class TemporaryFile(Generic[AnyStr]): + """ + An asynchronous temporary file that is automatically created and cleaned up. + + This class provides an asynchronous context manager interface to a temporary file. + The file is created using Python's standard `tempfile.TemporaryFile` function in a + background thread, and is wrapped as an asynchronous file using `AsyncFile`. + + :param mode: The mode in which the file is opened. Defaults to "w+b". + :param buffering: The buffering policy (-1 means the default buffering). + :param encoding: The encoding used to decode or encode the file. Only applicable in + text mode. + :param newline: Controls how universal newlines mode works (only applicable in text + mode). + :param suffix: The suffix for the temporary file name. + :param prefix: The prefix for the temporary file name. + :param dir: The directory in which the temporary file is created. + :param errors: The error handling scheme used for encoding/decoding errors. + """ + + _async_file: AsyncFile[AnyStr] + + @overload + def __init__( + self: TemporaryFile[bytes], + mode: OpenBinaryMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + @overload + def __init__( + self: TemporaryFile[str], + mode: OpenTextMode, + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + + def __init__( + self, + mode: OpenTextMode | OpenBinaryMode = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + *, + errors: str | None = None, + ) -> None: + self.mode = mode + self.buffering = buffering + self.encoding = encoding + self.newline = newline + self.suffix: str | None = suffix + self.prefix: str | None = prefix + self.dir: str | None = dir + self.errors = errors + + async def __aenter__(self) -> AsyncFile[AnyStr]: + fp = await to_thread.run_sync( + lambda: tempfile.TemporaryFile( + self.mode, + self.buffering, + self.encoding, + self.newline, + self.suffix, + self.prefix, + self.dir, + errors=self.errors, + ) + ) + self._async_file = AsyncFile(fp) + return self._async_file + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self._async_file.aclose() + + +class NamedTemporaryFile(Generic[AnyStr]): + """ + An asynchronous named temporary file that is automatically created and cleaned up. + + This class provides an asynchronous context manager for a temporary file with a + visible name in the file system. It uses Python's standard + :func:`~tempfile.NamedTemporaryFile` function and wraps the file object with + :class:`AsyncFile` for asynchronous operations. + + :param mode: The mode in which the file is opened. Defaults to "w+b". + :param buffering: The buffering policy (-1 means the default buffering). + :param encoding: The encoding used to decode or encode the file. Only applicable in + text mode. + :param newline: Controls how universal newlines mode works (only applicable in text + mode). + :param suffix: The suffix for the temporary file name. + :param prefix: The prefix for the temporary file name. + :param dir: The directory in which the temporary file is created. + :param delete: Whether to delete the file when it is closed. + :param errors: The error handling scheme used for encoding/decoding errors. + :param delete_on_close: (Python 3.12+) Whether to delete the file on close. + """ + + _async_file: AsyncFile[AnyStr] + + @overload + def __init__( + self: NamedTemporaryFile[bytes], + mode: OpenBinaryMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + delete: bool = ..., + *, + errors: str | None = ..., + delete_on_close: bool = ..., + ): ... + @overload + def __init__( + self: NamedTemporaryFile[str], + mode: OpenTextMode, + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + delete: bool = ..., + *, + errors: str | None = ..., + delete_on_close: bool = ..., + ): ... + + def __init__( + self, + mode: OpenBinaryMode | OpenTextMode = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + delete: bool = True, + *, + errors: str | None = None, + delete_on_close: bool = True, + ) -> None: + self._params: dict[str, Any] = { + "mode": mode, + "buffering": buffering, + "encoding": encoding, + "newline": newline, + "suffix": suffix, + "prefix": prefix, + "dir": dir, + "delete": delete, + "errors": errors, + } + if sys.version_info >= (3, 12): + self._params["delete_on_close"] = delete_on_close + + async def __aenter__(self) -> AsyncFile[AnyStr]: + fp = await to_thread.run_sync( + lambda: tempfile.NamedTemporaryFile(**self._params) + ) + self._async_file = AsyncFile(fp) + return self._async_file + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self._async_file.aclose() + + +class SpooledTemporaryFile(AsyncFile[AnyStr]): + """ + An asynchronous spooled temporary file that starts in memory and is spooled to disk. + + This class provides an asynchronous interface to a spooled temporary file, much like + Python's standard :class:`~tempfile.SpooledTemporaryFile`. It supports asynchronous + write operations and provides a method to force a rollover to disk. + + :param max_size: Maximum size in bytes before the file is rolled over to disk. + :param mode: The mode in which the file is opened. Defaults to "w+b". + :param buffering: The buffering policy (-1 means the default buffering). + :param encoding: The encoding used to decode or encode the file (text mode only). + :param newline: Controls how universal newlines mode works (text mode only). + :param suffix: The suffix for the temporary file name. + :param prefix: The prefix for the temporary file name. + :param dir: The directory in which the temporary file is created. + :param errors: The error handling scheme used for encoding/decoding errors. + """ + + _rolled: bool = False + + @overload + def __init__( + self: SpooledTemporaryFile[bytes], + max_size: int = ..., + mode: OpenBinaryMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + @overload + def __init__( + self: SpooledTemporaryFile[str], + max_size: int = ..., + mode: OpenTextMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + + def __init__( + self, + max_size: int = 0, + mode: OpenBinaryMode | OpenTextMode = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + *, + errors: str | None = None, + ) -> None: + self._tempfile_params: dict[str, Any] = { + "mode": mode, + "buffering": buffering, + "encoding": encoding, + "newline": newline, + "suffix": suffix, + "prefix": prefix, + "dir": dir, + "errors": errors, + } + self._max_size = max_size + if "b" in mode: + super().__init__(BytesIO()) # type: ignore[arg-type] + else: + super().__init__( + TextIOWrapper( # type: ignore[arg-type] + BytesIO(), + encoding=encoding, + errors=errors, + newline=newline, + write_through=True, + ) + ) + + async def aclose(self) -> None: + if not self._rolled: + self._fp.close() + return + + await super().aclose() + + async def _check(self) -> None: + if self._rolled or self._fp.tell() <= self._max_size: + return + + await self.rollover() + + async def rollover(self) -> None: + if self._rolled: + return + + self._rolled = True + buffer = self._fp + buffer.seek(0) + self._fp = await to_thread.run_sync( + lambda: tempfile.TemporaryFile(**self._tempfile_params) + ) + await self.write(buffer.read()) + buffer.close() + + @property + def closed(self) -> bool: + return self._fp.closed + + async def read(self, size: int = -1) -> AnyStr: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.read(size) + + return await super().read(size) # type: ignore[return-value] + + async def read1(self: SpooledTemporaryFile[bytes], size: int = -1) -> bytes: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.read1(size) + + return await super().read1(size) + + async def readline(self) -> AnyStr: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.readline() + + return await super().readline() # type: ignore[return-value] + + async def readlines(self) -> list[AnyStr]: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.readlines() + + return await super().readlines() # type: ignore[return-value] + + async def readinto(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.readinto(b) + + return await super().readinto(b) + + async def readinto1(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.readinto1(b) + + return await super().readinto1(b) + + async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.seek(offset, whence) + + return await super().seek(offset, whence) + + async def tell(self) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.tell() + + return await super().tell() + + async def truncate(self, size: int | None = None) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.truncate(size) + + return await super().truncate(size) + + @overload + async def write(self: SpooledTemporaryFile[bytes], b: ReadableBuffer) -> int: ... + @overload + async def write(self: SpooledTemporaryFile[str], b: str) -> int: ... + + async def write(self, b: ReadableBuffer | str) -> int: + """ + Asynchronously write data to the spooled temporary file. + + If the file has not yet been rolled over, the data is written synchronously, + and a rollover is triggered if the size exceeds the maximum size. + + :param s: The data to write. + :return: The number of bytes written. + :raises RuntimeError: If the underlying file is not initialized. + + """ + if not self._rolled: + await checkpoint_if_cancelled() + result = self._fp.write(b) + await self._check() + return result + + return await super().write(b) # type: ignore[misc] + + @overload + async def writelines( + self: SpooledTemporaryFile[bytes], lines: Iterable[ReadableBuffer] + ) -> None: ... + @overload + async def writelines( + self: SpooledTemporaryFile[str], lines: Iterable[str] + ) -> None: ... + + async def writelines(self, lines: Iterable[str] | Iterable[ReadableBuffer]) -> None: + """ + Asynchronously write a list of lines to the spooled temporary file. + + If the file has not yet been rolled over, the lines are written synchronously, + and a rollover is triggered if the size exceeds the maximum size. + + :param lines: An iterable of lines to write. + :raises RuntimeError: If the underlying file is not initialized. + + """ + if not self._rolled: + await checkpoint_if_cancelled() + result = self._fp.writelines(lines) + await self._check() + return result + + return await super().writelines(lines) # type: ignore[misc] + + +class TemporaryDirectory(Generic[AnyStr]): + """ + An asynchronous temporary directory that is created and cleaned up automatically. + + This class provides an asynchronous context manager for creating a temporary + directory. It wraps Python's standard :class:`~tempfile.TemporaryDirectory` to + perform directory creation and cleanup operations in a background thread. + + :param suffix: Suffix to be added to the temporary directory name. + :param prefix: Prefix to be added to the temporary directory name. + :param dir: The parent directory where the temporary directory is created. + :param ignore_cleanup_errors: Whether to ignore errors during cleanup + :param delete: Whether to delete the directory upon closing (Python 3.12+). + """ + + def __init__( + self, + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: AnyStr | None = None, + *, + ignore_cleanup_errors: bool = False, + delete: bool = True, + ) -> None: + self.suffix: AnyStr | None = suffix + self.prefix: AnyStr | None = prefix + self.dir: AnyStr | None = dir + self.ignore_cleanup_errors = ignore_cleanup_errors + self.delete = delete + + self._tempdir: tempfile.TemporaryDirectory | None = None + + async def __aenter__(self) -> str: + params: dict[str, Any] = { + "suffix": self.suffix, + "prefix": self.prefix, + "dir": self.dir, + "ignore_cleanup_errors": self.ignore_cleanup_errors, + } + if sys.version_info >= (3, 12): + params["delete"] = self.delete + + self._tempdir = await to_thread.run_sync( + lambda: tempfile.TemporaryDirectory(**params) + ) + return await to_thread.run_sync(self._tempdir.__enter__) + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + if self._tempdir is not None: + with CancelScope(shield=True): + await to_thread.run_sync( + self._tempdir.__exit__, exc_type, exc_value, traceback + ) + + async def cleanup(self) -> None: + if self._tempdir is not None: + await to_thread.run_sync(self._tempdir.cleanup) + + +@overload +async def mkstemp( + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + text: bool = False, +) -> tuple[int, str]: ... + + +@overload +async def mkstemp( + suffix: bytes | None = None, + prefix: bytes | None = None, + dir: bytes | None = None, + text: bool = False, +) -> tuple[int, bytes]: ... + + +async def mkstemp( + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: AnyStr | None = None, + text: bool = False, +) -> tuple[int, str | bytes]: + """ + Asynchronously create a temporary file and return an OS-level handle and the file + name. + + This function wraps `tempfile.mkstemp` and executes it in a background thread. + + :param suffix: Suffix to be added to the file name. + :param prefix: Prefix to be added to the file name. + :param dir: Directory in which the temporary file is created. + :param text: Whether the file is opened in text mode. + :return: A tuple containing the file descriptor and the file name. + + """ + return await to_thread.run_sync(tempfile.mkstemp, suffix, prefix, dir, text) + + +@overload +async def mkdtemp( + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, +) -> str: ... + + +@overload +async def mkdtemp( + suffix: bytes | None = None, + prefix: bytes | None = None, + dir: bytes | None = None, +) -> bytes: ... + + +async def mkdtemp( + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: AnyStr | None = None, +) -> str | bytes: + """ + Asynchronously create a temporary directory and return its path. + + This function wraps `tempfile.mkdtemp` and executes it in a background thread. + + :param suffix: Suffix to be added to the directory name. + :param prefix: Prefix to be added to the directory name. + :param dir: Parent directory where the temporary directory is created. + :return: The path of the created temporary directory. + + """ + return await to_thread.run_sync(tempfile.mkdtemp, suffix, prefix, dir) + + +async def gettempdir() -> str: + """ + Asynchronously return the name of the directory used for temporary files. + + This function wraps `tempfile.gettempdir` and executes it in a background thread. + + :return: The path of the temporary directory as a string. + + """ + return await to_thread.run_sync(tempfile.gettempdir) + + +async def gettempdirb() -> bytes: + """ + Asynchronously return the name of the directory used for temporary files in bytes. + + This function wraps `tempfile.gettempdirb` and executes it in a background thread. + + :return: The path of the temporary directory as bytes. + + """ + return await to_thread.run_sync(tempfile.gettempdirb) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_testing.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_testing.py new file mode 100644 index 0000000000000000000000000000000000000000..fa2c3913709d4143d1967fc2857bb67be361f6db --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_testing.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from collections.abc import Awaitable, Generator +from typing import Any, cast + +from ._eventloop import get_async_backend + + +class TaskInfo: + """ + Represents an asynchronous task. + + :ivar int id: the unique identifier of the task + :ivar parent_id: the identifier of the parent task, if any + :vartype parent_id: Optional[int] + :ivar str name: the description of the task (if any) + :ivar ~collections.abc.Coroutine coro: the coroutine object of the task + """ + + __slots__ = "_name", "coro", "id", "name", "parent_id" + + def __init__( + self, + id: int, + parent_id: int | None, + name: str | None, + coro: Generator[Any, Any, Any] | Awaitable[Any], + ): + func = get_current_task + self._name = f"{func.__module__}.{func.__qualname__}" + self.id: int = id + self.parent_id: int | None = parent_id + self.name: str | None = name + self.coro: Generator[Any, Any, Any] | Awaitable[Any] = coro + + def __eq__(self, other: object) -> bool: + if isinstance(other, TaskInfo): + return self.id == other.id + + return NotImplemented + + def __hash__(self) -> int: + return hash(self.id) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(id={self.id!r}, name={self.name!r})" + + def has_pending_cancellation(self) -> bool: + """ + Return ``True`` if the task has a cancellation pending, ``False`` otherwise. + + """ + return False + + +def get_current_task() -> TaskInfo: + """ + Return the current task. + + :return: a representation of the current task + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().get_current_task() + + +def get_running_tasks() -> list[TaskInfo]: + """ + Return a list of running tasks in the current event loop. + + :return: a list of task info objects + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return cast("list[TaskInfo]", get_async_backend().get_running_tasks()) + + +async def wait_all_tasks_blocked() -> None: + """Wait until all other tasks are waiting for something.""" + await get_async_backend().wait_all_tasks_blocked() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_typedattr.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_typedattr.py new file mode 100644 index 0000000000000000000000000000000000000000..59d3da9d8787d65295cffc3a1f43275f27d71772 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_core/_typedattr.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import sys +from collections.abc import Callable, Mapping +from typing import Any, TypeVar, final, overload + +from ._exceptions import TypedAttributeLookupError + +if sys.version_info < (3, 15): + from typing_extensions import sentinel + +T_Attr = TypeVar("T_Attr") +T_Default = TypeVar("T_Default") +undefined = sentinel("undefined") + + +def typed_attribute() -> Any: + """Return a unique object, used to mark typed attributes.""" + return object() + + +class TypedAttributeSet: + """ + Superclass for typed attribute collections. + + Checks that every public attribute of every subclass has a type annotation. + """ + + def __init_subclass__(cls) -> None: + annotations: dict[str, Any] = getattr(cls, "__annotations__", {}) + for attrname in dir(cls): + if not attrname.startswith("_") and attrname not in annotations: + raise TypeError( + f"Attribute {attrname!r} is missing its type annotation" + ) + + super().__init_subclass__() + + +class TypedAttributeProvider: + """Base class for classes that wish to provide typed extra attributes.""" + + @property + def extra_attributes(self) -> Mapping[T_Attr, Callable[[], T_Attr]]: + """ + A mapping of the extra attributes to callables that return the corresponding + values. + + If the provider wraps another provider, the attributes from that wrapper should + also be included in the returned mapping (but the wrapper may override the + callables from the wrapped instance). + + """ + return {} + + @overload + def extra(self, attribute: T_Attr) -> T_Attr: ... + + @overload + def extra(self, attribute: T_Attr, default: T_Default) -> T_Attr | T_Default: ... + + @final + def extra(self, attribute: Any, default: object = undefined) -> object: + """ + extra(attribute, default=undefined) + + Return the value of the given typed extra attribute. + + :param attribute: the attribute (member of a :class:`~TypedAttributeSet`) to + look for + :param default: the value that should be returned if no value is found for the + attribute + :raises ~anyio.TypedAttributeLookupError: if the search failed and no default + value was given + + """ + try: + getter = self.extra_attributes[attribute] + except KeyError: + if default is undefined: + raise TypedAttributeLookupError("Attribute not found") from None + else: + return default + + return getter() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/_lazyimport.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_lazyimport.py new file mode 100644 index 0000000000000000000000000000000000000000..dc0a4faba7009d3fb542842cce1c0eec1c568440 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/_lazyimport.py @@ -0,0 +1,176 @@ +from __future__ import annotations + +__all__ = ( + "fix_package_names", + "install_lazy_importer", + "set_deprecated_aliases", +) + +import ast +import inspect +import sys +import warnings +from importlib import import_module +from types import ModuleType +from typing import Any + + +def install_lazy_importer() -> bool: + module_globals = sys._getframe(1).f_globals + module_name = module_globals["__name__"] + module_prefix = module_name + "." + module = sys.modules[module_name] + lazy_map, deprecated_aliases, submodule_names = _build_lazy_map(module) + names = sorted(lazy_map) + + # Delete symbols that are not part of the API + del module_globals["TYPE_CHECKING"] + del module_globals["install_lazy_importer"] + + if not lazy_map and not deprecated_aliases: + return False + + def __getattr__(name: str) -> Any: + if new_name := deprecated_aliases.get(name): + emit_deprecation_warning(module_name, name, new_name) + target_mod, target_attr = new_name.rsplit(".", 1) + elif name in submodule_names: + target_mod, target_attr = "." + name, "" + else: + try: + target_mod, target_attr = lazy_map[name] + except KeyError: + raise AttributeError( + f"module {module_name!r} has no attribute {name!r}" + ) from None + + imported = import_module(target_mod, module_name) + value = getattr(imported, target_attr) if target_attr else imported + + # patch the module name to match + if ( + getattr(value, "__module__", "").startswith(module_prefix) + and name not in deprecated_aliases + ): + value.__module__ = module_name + + module_globals[name] = value + return value + + def __dir__() -> list[str]: + return names + + module_globals["__dir__"] = __dir__ + module_globals["__getattr__"] = __getattr__ + module_globals.pop("fix_package_names", None) + module_globals.pop("set_deprecated_aliases", None) + return True + + +def fix_package_names() -> None: + module_globals = sys._getframe(1).f_globals + module_prefix = module_globals["__name__"] + "." + del module_globals[fix_package_names.__name__] + for value in module_globals.values(): + if modname := getattr(value, "__module__", ""): + if modname.startswith(module_prefix): + parts = modname.split(".") + value.__module__ = ".".join( + part for part in parts if not part.startswith("_") + ) + + +def emit_deprecation_warning(module_name: str, name: str, target: str) -> None: + warnings.warn( + f"The {module_name}.{name} alias is deprecated, use {target} instead.", + DeprecationWarning, + stacklevel=3, + ) + + +def set_deprecated_aliases(aliases: dict[str, str]) -> None: + module_globals = sys._getframe(1).f_globals + module_name = module_globals["__name__"] + del module_globals[set_deprecated_aliases.__name__] + + def __getattr__(name: str) -> Any: + try: + target = aliases[name] + except KeyError: + raise AttributeError( + f"module {module_name!r} has no attribute {name!r}" + ) from None + + emit_deprecation_warning(module_name, name, target) + target_modname, attrname = target.rsplit(".", 1) + module = import_module(target_modname) + return getattr(module, attrname) + + sys.modules[module_name].__dict__["__getattr__"] = __getattr__ + + +def _build_lazy_map( + module: ModuleType, +) -> tuple[dict[str, tuple[str, str]], dict[str, str], list[str]]: + try: + source = inspect.getsource(module) + except OSError: + return {}, {}, [] + + tree = compile(source, module.__file__ or "", "exec", ast.PyCF_ONLY_AST) + assert isinstance(tree, ast.Module) + out: dict[str, tuple[str, str]] = {} + deprecated_aliases: dict[str, str] = {} + submodule_names: list[str] = [] + + for node in tree.body: + if not isinstance(node, ast.If) or not _is_type_checking_block(node.test): + continue + + for stmt in node.body: + match stmt: + case ast.ImportFrom(): + if stmt.module is None: + submodule_names.extend(alias.name for alias in stmt.names) + else: + base = "." * stmt.level + (stmt.module or "") + for alias in stmt.names: + if alias.name == "*": + raise RuntimeError("star imports not supported") + + exported = alias.asname or alias.name + out[exported] = (base, alias.name) + case ast.Expr() if isinstance(stmt.value, ast.Call): + call = stmt.value + if ( + isinstance(call.func, ast.Name) + and call.func.id == "set_deprecated_aliases" + ): + arg0 = call.args[0] + assert isinstance(arg0, ast.Dict) + for key, value in zip(arg0.keys, arg0.values, strict=True): + assert isinstance(key, ast.Constant) + assert isinstance(key.value, str) + assert isinstance(value, ast.Constant) + assert isinstance(value.value, str) + deprecated_aliases[key.value] = value.value + + return out, deprecated_aliases, submodule_names + + +def _is_type_checking_block(test: ast.AST) -> bool: + if not isinstance(test, ast.BoolOp): + return False + + subtest = test.values[0] + match subtest: + case ast.Name(): + return subtest.id == "TYPE_CHECKING" + case ast.Attribute(): + return ( + isinstance(subtest.value, ast.Name) + and subtest.value.id == "typing" + and subtest.attr == "TYPE_CHECKING" + ) + case _: + return False diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/__init__.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ba738f87f2fc778f5bb4c87fa1251a42afd22529 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/__init__.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .._lazyimport import ( + fix_package_names, + install_lazy_importer, + set_deprecated_aliases, +) + +if TYPE_CHECKING or not install_lazy_importer(): + from ._eventloop import AsyncBackend as AsyncBackend + from ._resources import AsyncResource as AsyncResource + from ._sockets import ConnectedUDPSocket as ConnectedUDPSocket + from ._sockets import ConnectedUNIXDatagramSocket as ConnectedUNIXDatagramSocket + from ._sockets import IPAddressType as IPAddressType + from ._sockets import IPSockAddrType as IPSockAddrType + from ._sockets import SocketAttribute as SocketAttribute + from ._sockets import SocketListener as SocketListener + from ._sockets import SocketStream as SocketStream + from ._sockets import UDPPacketType as UDPPacketType + from ._sockets import UDPSocket as UDPSocket + from ._sockets import UNIXDatagramPacketType as UNIXDatagramPacketType + from ._sockets import UNIXDatagramSocket as UNIXDatagramSocket + from ._sockets import UNIXSocketStream as UNIXSocketStream + from ._streams import AnyByteReceiveStream as AnyByteReceiveStream + from ._streams import AnyByteSendStream as AnyByteSendStream + from ._streams import AnyByteStream as AnyByteStream + from ._streams import AnyByteStreamConnectable as AnyByteStreamConnectable + from ._streams import ( + AnyUnreliableByteReceiveStream as AnyUnreliableByteReceiveStream, + ) + from ._streams import AnyUnreliableByteSendStream as AnyUnreliableByteSendStream + from ._streams import AnyUnreliableByteStream as AnyUnreliableByteStream + from ._streams import ByteReceiveStream as ByteReceiveStream + from ._streams import ByteSendStream as ByteSendStream + from ._streams import ByteStream as ByteStream + from ._streams import ByteStreamConnectable as ByteStreamConnectable + from ._streams import Listener as Listener + from ._streams import ObjectReceiveStream as ObjectReceiveStream + from ._streams import ObjectSendStream as ObjectSendStream + from ._streams import ObjectStream as ObjectStream + from ._streams import ObjectStreamConnectable as ObjectStreamConnectable + from ._streams import UnreliableObjectReceiveStream as UnreliableObjectReceiveStream + from ._streams import UnreliableObjectSendStream as UnreliableObjectSendStream + from ._streams import UnreliableObjectStream as UnreliableObjectStream + from ._subprocesses import Process as Process + from ._tasks import TaskGroup as TaskGroup + from ._tasks import TaskStatus as TaskStatus + from ._testing import TestRunner as TestRunner + + fix_package_names() + set_deprecated_aliases( + { + "CapacityLimiter": "anyio.CapacityLimiter", + "Condition": "anyio.Condition", + "Event": "anyio.Event", + "Lock": "anyio.Lock", + "Semaphore": "anyio.Semaphore", + "CancelScope": "anyio.CancelScope", + "BlockingPortal": "anyio.from_thread.BlockingPortal", + } + ) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_eventloop.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_eventloop.py new file mode 100644 index 0000000000000000000000000000000000000000..a3cbed2febe41bbff583c80bc1442daee3862e8b --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_eventloop.py @@ -0,0 +1,428 @@ +from __future__ import annotations + +import math +import sys +from abc import ABCMeta, abstractmethod +from collections.abc import ( + AsyncIterator, + Awaitable, + Callable, + Coroutine, + Iterable, + Mapping, + Sequence, +) +from contextlib import AbstractContextManager +from os import PathLike +from signal import Signals +from socket import AddressFamily, SocketKind, socket +from typing import ( + IO, + TYPE_CHECKING, + Any, + TypeAlias, + TypeVar, + overload, +) + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike + + from .._core._synchronization import CapacityLimiter, Event, Lock, Semaphore + from .._core._tasks import CancelScope + from .._core._testing import TaskInfo + from ._sockets import ( + ConnectedUDPSocket, + ConnectedUNIXDatagramSocket, + IPSockAddrType, + SocketListener, + SocketStream, + UDPSocket, + UNIXDatagramSocket, + UNIXSocketStream, + ) + from ._subprocesses import Process + from ._tasks import TaskGroup + from ._testing import TestRunner + +T_Retval = TypeVar("T_Retval") +T_co = TypeVar("T_co", covariant=True) +PosArgsT = TypeVarTuple("PosArgsT") +StrOrBytesPath: TypeAlias = str | bytes | PathLike[str] | PathLike[bytes] + + +class AsyncBackend(metaclass=ABCMeta): + @classmethod + @abstractmethod + def run( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + options: dict[str, Any], + ) -> T_Retval: + """ + Run the given coroutine function in an asynchronous event loop. + + The current thread must not be already running an event loop. + + :param func: a coroutine function + :param args: positional arguments to ``func`` + :param kwargs: positional arguments to ``func`` + :param options: keyword arguments to call the backend ``run()`` implementation + with + :return: the return value of the coroutine function + """ + + @classmethod + @abstractmethod + def current_token(cls) -> object: + """ + Return an object that allows other threads to run code inside the event loop. + + :return: a token object, specific to the event loop running in the current + thread + """ + + @classmethod + @abstractmethod + def current_time(cls) -> float: + """ + Return the current value of the event loop's internal clock. + + :return: the clock value (seconds) + """ + + @classmethod + @abstractmethod + def cancelled_exception_class(cls) -> type[BaseException]: + """Return the exception class that is raised in a task if it's cancelled.""" + + @classmethod + @abstractmethod + async def checkpoint(cls) -> None: + """ + Check if the task has been cancelled, and allow rescheduling of other tasks. + + This is effectively the same as running :meth:`checkpoint_if_cancelled` and then + :meth:`cancel_shielded_checkpoint`. + """ + + @classmethod + async def checkpoint_if_cancelled(cls) -> None: + """ + Check if the current task group has been cancelled. + + This will check if the task has been cancelled, but will not allow other tasks + to be scheduled if not. + + """ + if cls.current_effective_deadline() == -math.inf: + await cls.checkpoint() + + @classmethod + async def cancel_shielded_checkpoint(cls) -> None: + """ + Allow the rescheduling of other tasks. + + This will give other tasks the opportunity to run, but without checking if the + current task group has been cancelled, unlike with :meth:`checkpoint`. + + """ + with cls.create_cancel_scope(shield=True): + await cls.sleep(0) + + @classmethod + @abstractmethod + async def sleep(cls, delay: float) -> None: + """ + Pause the current task for the specified duration. + + :param delay: the duration, in seconds + """ + + @classmethod + @abstractmethod + def create_cancel_scope( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + pass + + @classmethod + @abstractmethod + def current_effective_deadline(cls) -> float: + """ + Return the nearest deadline among all the cancel scopes effective for the + current task. + + :return: + - a clock value from the event loop's internal clock + - ``inf`` if there is no deadline in effect + - ``-inf`` if the current scope has been cancelled + :rtype: float + """ + + @classmethod + @abstractmethod + def create_task_group(cls) -> TaskGroup: + pass + + @classmethod + @abstractmethod + def create_event(cls) -> Event: + pass + + @classmethod + @abstractmethod + def create_lock(cls, *, fast_acquire: bool) -> Lock: + pass + + @classmethod + @abstractmethod + def create_semaphore( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Semaphore: + pass + + @classmethod + @abstractmethod + def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter: + pass + + @classmethod + @abstractmethod + async def run_sync_in_worker_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + abandon_on_cancel: bool = False, + limiter: CapacityLimiter | None = None, + ) -> T_Retval: + pass + + @classmethod + @abstractmethod + def check_cancelled(cls) -> None: + pass + + @classmethod + @abstractmethod + def run_async_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_co: + pass + + @classmethod + @abstractmethod + def run_sync_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + pass + + @classmethod + @abstractmethod + async def open_process( + cls, + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None, + stdout: int | IO[Any] | None, + stderr: int | IO[Any] | None, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, + **kwargs: Any, + ) -> Process: + pass + + @classmethod + @abstractmethod + def setup_process_pool_exit_at_shutdown(cls, workers: set[Process]) -> None: + pass + + @classmethod + @abstractmethod + async def connect_tcp( + cls, host: str, port: int, local_address: IPSockAddrType | None = None + ) -> SocketStream: + pass + + @classmethod + @abstractmethod + async def connect_unix(cls, path: str | bytes) -> UNIXSocketStream: + pass + + @classmethod + @abstractmethod + def create_tcp_listener(cls, sock: socket) -> SocketListener: + pass + + @classmethod + @abstractmethod + def create_unix_listener(cls, sock: socket) -> SocketListener: + pass + + @classmethod + @abstractmethod + async def create_udp_socket( + cls, + family: AddressFamily, + local_address: IPSockAddrType | None, + remote_address: IPSockAddrType | None, + reuse_port: bool, + ) -> UDPSocket | ConnectedUDPSocket: + pass + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket, remote_path: None + ) -> UNIXDatagramSocket: ... + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket, remote_path: str | bytes + ) -> ConnectedUNIXDatagramSocket: ... + + @classmethod + @abstractmethod + async def create_unix_datagram_socket( + cls, raw_socket: socket, remote_path: str | bytes | None + ) -> UNIXDatagramSocket | ConnectedUNIXDatagramSocket: + pass + + @classmethod + @abstractmethod + async def getaddrinfo( + cls, + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, + ) -> Sequence[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], + ] + ]: + pass + + @classmethod + @abstractmethod + async def getnameinfo( + cls, sockaddr: IPSockAddrType, flags: int = 0 + ) -> tuple[str, str]: + pass + + @classmethod + @abstractmethod + async def wait_readable(cls, obj: FileDescriptorLike) -> None: + pass + + @classmethod + @abstractmethod + async def wait_writable(cls, obj: FileDescriptorLike) -> None: + pass + + @classmethod + @abstractmethod + def notify_closing(cls, obj: FileDescriptorLike) -> None: + pass + + @classmethod + @abstractmethod + async def wrap_listener_socket(cls, sock: socket) -> SocketListener: + pass + + @classmethod + @abstractmethod + async def wrap_stream_socket(cls, sock: socket) -> SocketStream: + pass + + @classmethod + @abstractmethod + async def wrap_unix_stream_socket(cls, sock: socket) -> UNIXSocketStream: + pass + + @classmethod + @abstractmethod + async def wrap_udp_socket(cls, sock: socket) -> UDPSocket: + pass + + @classmethod + @abstractmethod + async def wrap_connected_udp_socket(cls, sock: socket) -> ConnectedUDPSocket: + pass + + @classmethod + @abstractmethod + async def wrap_unix_datagram_socket(cls, sock: socket) -> UNIXDatagramSocket: + pass + + @classmethod + @abstractmethod + async def wrap_connected_unix_datagram_socket( + cls, sock: socket + ) -> ConnectedUNIXDatagramSocket: + pass + + @classmethod + @abstractmethod + def current_default_thread_limiter(cls) -> CapacityLimiter: + pass + + @classmethod + @abstractmethod + def open_signal_receiver( + cls, *signals: Signals + ) -> AbstractContextManager[AsyncIterator[Signals]]: + pass + + @classmethod + @abstractmethod + def get_current_task(cls) -> TaskInfo: + pass + + @classmethod + @abstractmethod + def get_running_tasks(cls) -> Sequence[TaskInfo]: + pass + + @classmethod + @abstractmethod + async def wait_all_tasks_blocked(cls) -> None: + pass + + @classmethod + @abstractmethod + def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: + pass diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_resources.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_resources.py new file mode 100644 index 0000000000000000000000000000000000000000..9373f71a3fc4ccfb2002d03328cf62f78802375a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_resources.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +import sys +from abc import ABCMeta, abstractmethod +from types import TracebackType +from typing import TypeVar + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + +T = TypeVar("T") + + +class AsyncResource(metaclass=ABCMeta): + """ + Abstract base class for all closeable asynchronous resources. + + Works as an asynchronous context manager which returns the instance itself on enter, + and calls :meth:`aclose` on exit. + """ + + __slots__ = () + + async def __aenter__(self) -> Self: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.aclose() + + @abstractmethod + async def aclose(self) -> None: + """Close the resource.""" diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_sockets.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_sockets.py new file mode 100644 index 0000000000000000000000000000000000000000..d6a1139dfd9d31776c581b25db1309d9bfbb080e --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_sockets.py @@ -0,0 +1,401 @@ +from __future__ import annotations + +import errno +import socket +from abc import abstractmethod +from collections.abc import Callable, Collection, Mapping +from contextlib import AsyncExitStack +from io import IOBase +from ipaddress import IPv4Address, IPv6Address +from socket import AddressFamily +from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar + +from .._core._eventloop import get_async_backend +from .._core._typedattr import ( + TypedAttributeProvider, + TypedAttributeSet, + typed_attribute, +) +from ._streams import ByteStream, Listener, UnreliableObjectStream + +if TYPE_CHECKING: + from ._tasks import TaskGroup + +IPAddressType: TypeAlias = str | IPv4Address | IPv6Address +IPSockAddrType: TypeAlias = tuple[str, int] +SockAddrType: TypeAlias = IPSockAddrType | str +UDPPacketType: TypeAlias = tuple[bytes, IPSockAddrType] +UNIXDatagramPacketType: TypeAlias = tuple[bytes, str] +T_Retval = TypeVar("T_Retval") + + +def _validate_socket( + sock_or_fd: socket.socket | int, + sock_type: socket.SocketKind, + addr_family: socket.AddressFamily = socket.AF_UNSPEC, + *, + require_connected: bool = False, + require_bound: bool = False, +) -> socket.socket: + if isinstance(sock_or_fd, int): + try: + sock = socket.socket(fileno=sock_or_fd) + except OSError as exc: + if exc.errno == errno.ENOTSOCK: + raise ValueError( + "the file descriptor does not refer to a socket" + ) from exc + elif require_connected: + raise ValueError("the socket must be connected") from exc + elif require_bound: + raise ValueError("the socket must be bound to a local address") from exc + else: + raise + elif isinstance(sock_or_fd, socket.socket): + sock = sock_or_fd + else: + raise TypeError( + f"expected an int or socket, got {type(sock_or_fd).__qualname__} instead" + ) + + try: + if require_connected: + try: + sock.getpeername() + except OSError as exc: + raise ValueError("the socket must be connected") from exc + + if require_bound: + try: + if sock.family in (socket.AF_INET, socket.AF_INET6): + bound_addr = sock.getsockname()[1] + else: + bound_addr = sock.getsockname() + except OSError: + bound_addr = None + + if not bound_addr: + raise ValueError("the socket must be bound to a local address") + + if addr_family != socket.AF_UNSPEC and sock.family != addr_family: + raise ValueError( + f"address family mismatch: expected {addr_family.name}, got " + f"{sock.family.name}" + ) + + if sock.type != sock_type: + raise ValueError( + f"socket type mismatch: expected {sock_type.name}, got {sock.type.name}" + ) + except BaseException: + # Avoid ResourceWarning from the locally constructed socket object + if isinstance(sock_or_fd, int): + sock.detach() + + raise + + sock.setblocking(False) + return sock + + +class SocketAttribute(TypedAttributeSet): + """ + .. attribute:: family + :type: socket.AddressFamily + + the address family of the underlying socket + + .. attribute:: local_address + :type: tuple[str, int] | str + + the local address the underlying socket is connected to + + .. attribute:: local_port + :type: int + + for IP based sockets, the local port the underlying socket is bound to + + .. attribute:: raw_socket + :type: socket.socket + + the underlying stdlib socket object + + .. attribute:: remote_address + :type: tuple[str, int] | str + + the remote address the underlying socket is connected to + + .. attribute:: remote_port + :type: int + + for IP based sockets, the remote port the underlying socket is connected to + """ + + family: AddressFamily = typed_attribute() + local_address: SockAddrType = typed_attribute() + local_port: int = typed_attribute() + raw_socket: socket.socket = typed_attribute() + remote_address: SockAddrType = typed_attribute() + remote_port: int = typed_attribute() + + +class _SocketProvider(TypedAttributeProvider): + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + from .._core._sockets import convert_ipv6_sockaddr as convert + + attributes: dict[Any, Callable[[], Any]] = { + SocketAttribute.family: lambda: self._raw_socket.family, + SocketAttribute.local_address: lambda: convert( + self._raw_socket.getsockname() + ), + SocketAttribute.raw_socket: lambda: self._raw_socket, + } + try: + peername: tuple[str, int] | None = convert(self._raw_socket.getpeername()) + except OSError: + peername = None + + # Provide the remote address for connected sockets + if peername is not None: + attributes[SocketAttribute.remote_address] = lambda: peername + + # Provide local and remote ports for IP based sockets + if self._raw_socket.family in (AddressFamily.AF_INET, AddressFamily.AF_INET6): + attributes[SocketAttribute.local_port] = lambda: ( + self._raw_socket.getsockname()[1] + ) + if peername is not None: + remote_port = peername[1] + attributes[SocketAttribute.remote_port] = lambda: remote_port + + return attributes + + @property + @abstractmethod + def _raw_socket(self) -> socket.socket: + pass + + +class SocketStream(ByteStream, _SocketProvider): + """ + Transports bytes over a socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket(cls, sock_or_fd: socket.socket | int) -> SocketStream: + """ + Wrap an existing socket object or file descriptor as a socket stream. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must already be connected. + + :param sock_or_fd: a socket object or file descriptor + :return: a socket stream + + """ + sock = _validate_socket(sock_or_fd, socket.SOCK_STREAM, require_connected=True) + return await get_async_backend().wrap_stream_socket(sock) + + +class UNIXSocketStream(SocketStream): + @classmethod + async def from_socket(cls, sock_or_fd: socket.socket | int) -> UNIXSocketStream: + """ + Wrap an existing socket object or file descriptor as a UNIX socket stream. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must already be connected. + + :param sock_or_fd: a socket object or file descriptor + :return: a UNIX socket stream + + """ + sock = _validate_socket( + sock_or_fd, socket.SOCK_STREAM, socket.AF_UNIX, require_connected=True + ) + return await get_async_backend().wrap_unix_stream_socket(sock) + + @abstractmethod + async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: + """ + Send file descriptors along with a message to the peer. + + :param message: a non-empty bytestring + :param fds: a collection of files (either numeric file descriptors or open file + or socket objects) + """ + + @abstractmethod + async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: + """ + Receive file descriptors along with a message from the peer. + + :param msglen: length of the message to expect from the peer + :param maxfds: maximum number of file descriptors to expect from the peer + :return: a tuple of (message, file descriptors) + """ + + +class SocketListener(Listener[SocketStream], _SocketProvider): + """ + Listens to incoming socket connections. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket( + cls, + sock_or_fd: socket.socket | int, + ) -> SocketListener: + """ + Wrap an existing socket object or file descriptor as a socket listener. + + The newly created listener takes ownership of the socket being passed in. + + :param sock_or_fd: a socket object or file descriptor + :return: a socket listener + + """ + sock = _validate_socket(sock_or_fd, socket.SOCK_STREAM, require_bound=True) + return await get_async_backend().wrap_listener_socket(sock) + + @abstractmethod + async def accept(self) -> SocketStream: + """Accept an incoming connection.""" + + async def serve( + self, + handler: Callable[[SocketStream], Any], + task_group: TaskGroup | None = None, + ) -> None: + from .. import create_task_group + + async with AsyncExitStack() as stack: + if task_group is None: + task_group = await stack.enter_async_context(create_task_group()) + + while True: + stream = await self.accept() + task_group.start_soon(handler, stream) + + +class UDPSocket(UnreliableObjectStream[UDPPacketType], _SocketProvider): + """ + Represents an unconnected UDP socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket(cls, sock_or_fd: socket.socket | int) -> UDPSocket: + """ + Wrap an existing socket object or file descriptor as a UDP socket. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must be bound to a local address. + + :param sock_or_fd: a socket object or file descriptor + :return: a UDP socket + + """ + sock = _validate_socket(sock_or_fd, socket.SOCK_DGRAM, require_bound=True) + return await get_async_backend().wrap_udp_socket(sock) + + async def sendto(self, data: bytes, host: str, port: int) -> None: + """ + Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, (host, port))). + + """ + return await self.send((data, (host, port))) + + +class ConnectedUDPSocket(UnreliableObjectStream[bytes], _SocketProvider): + """ + Represents an connected UDP socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket(cls, sock_or_fd: socket.socket | int) -> ConnectedUDPSocket: + """ + Wrap an existing socket object or file descriptor as a connected UDP socket. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must already be connected. + + :param sock_or_fd: a socket object or file descriptor + :return: a connected UDP socket + + """ + sock = _validate_socket( + sock_or_fd, + socket.SOCK_DGRAM, + require_connected=True, + ) + return await get_async_backend().wrap_connected_udp_socket(sock) + + +class UNIXDatagramSocket( + UnreliableObjectStream[UNIXDatagramPacketType], _SocketProvider +): + """ + Represents an unconnected Unix datagram socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket( + cls, + sock_or_fd: socket.socket | int, + ) -> UNIXDatagramSocket: + """ + Wrap an existing socket object or file descriptor as a UNIX datagram + socket. + + The newly created socket wrapper takes ownership of the socket being passed in. + + :param sock_or_fd: a socket object or file descriptor + :return: a UNIX datagram socket + + """ + sock = _validate_socket(sock_or_fd, socket.SOCK_DGRAM, socket.AF_UNIX) + return await get_async_backend().wrap_unix_datagram_socket(sock) + + async def sendto(self, data: bytes, path: str) -> None: + """Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, path)).""" + return await self.send((data, path)) + + +class ConnectedUNIXDatagramSocket(UnreliableObjectStream[bytes], _SocketProvider): + """ + Represents a connected Unix datagram socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket( + cls, + sock_or_fd: socket.socket | int, + ) -> ConnectedUNIXDatagramSocket: + """ + Wrap an existing socket object or file descriptor as a connected UNIX datagram + socket. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must already be connected. + + :param sock_or_fd: a socket object or file descriptor + :return: a connected UNIX datagram socket + + """ + sock = _validate_socket( + sock_or_fd, socket.SOCK_DGRAM, socket.AF_UNIX, require_connected=True + ) + return await get_async_backend().wrap_connected_unix_datagram_socket(sock) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_streams.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_streams.py new file mode 100644 index 0000000000000000000000000000000000000000..daf335dc3235ff8e4d5253610cd5e68de17c3f6d --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_streams.py @@ -0,0 +1,237 @@ +from __future__ import annotations + +from abc import ABCMeta, abstractmethod +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Generic, TypeAlias, TypeVar + +from .._core._exceptions import EndOfStream +from .._core._typedattr import TypedAttributeProvider +from ._resources import AsyncResource + +if TYPE_CHECKING: + from ._tasks import TaskGroup + +T_Item = TypeVar("T_Item") +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + + +class UnreliableObjectReceiveStream( + AsyncResource, TypedAttributeProvider, Generic[T_co] +): + """ + An interface for receiving objects. + + This interface makes no guarantees that the received messages arrive in the order in + which they were sent, or that no messages are missed. + + Asynchronously iterating over objects of this type will yield objects matching the + given type parameter. + """ + + def __aiter__(self) -> UnreliableObjectReceiveStream[T_co]: + return self + + async def __anext__(self) -> T_co: + try: + return await self.receive() + except EndOfStream: + raise StopAsyncIteration from None + + @abstractmethod + async def receive(self) -> T_co: + """ + Receive the next item. + + :raises ~anyio.ClosedResourceError: if the receive stream has been explicitly + closed + :raises ~anyio.EndOfStream: if this stream has been closed from the other end + :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable + due to external causes + """ + + +class UnreliableObjectSendStream( + AsyncResource, TypedAttributeProvider, Generic[T_contra] +): + """ + An interface for sending objects. + + This interface makes no guarantees that the messages sent will reach the + recipient(s) in the same order in which they were sent, or at all. + """ + + @abstractmethod + async def send(self, item: T_contra) -> None: + """ + Send an item to the peer(s). + + :param item: the item to send + :raises ~anyio.ClosedResourceError: if the send stream has been explicitly + closed + :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable + due to external causes + """ + + +class UnreliableObjectStream( + UnreliableObjectReceiveStream[T_Item], UnreliableObjectSendStream[T_Item] +): + """ + A bidirectional message stream which does not guarantee the order or reliability of + message delivery. + """ + + +class ObjectReceiveStream(UnreliableObjectReceiveStream[T_co]): + """ + A receive message stream which guarantees that messages are received in the same + order in which they were sent, and that no messages are missed. + """ + + +class ObjectSendStream(UnreliableObjectSendStream[T_contra]): + """ + A send message stream which guarantees that messages are delivered in the same order + in which they were sent, without missing any messages in the middle. + """ + + +class ObjectStream( + ObjectReceiveStream[T_Item], + ObjectSendStream[T_Item], + UnreliableObjectStream[T_Item], +): + """ + A bidirectional message stream which guarantees the order and reliability of message + delivery. + """ + + @abstractmethod + async def send_eof(self) -> None: + """ + Send an end-of-file indication to the peer. + + You should not try to send any further data to this stream after calling this + method. This method is idempotent (does nothing on successive calls). + """ + + +class ByteReceiveStream(AsyncResource, TypedAttributeProvider): + """ + An interface for receiving bytes from a single peer. + + Iterating this byte stream will yield a byte string of arbitrary length, but no more + than 65536 bytes. + """ + + def __aiter__(self) -> ByteReceiveStream: + return self + + async def __anext__(self) -> bytes: + try: + return await self.receive() + except EndOfStream: + raise StopAsyncIteration from None + + @abstractmethod + async def receive(self, max_bytes: int = 65536) -> bytes: + """ + Receive at most ``max_bytes`` bytes from the peer. + + .. note:: Implementers of this interface should not return an empty + :class:`bytes` object, and users should ignore them. + + :param max_bytes: maximum number of bytes to receive (must be a positive + integer) + :return: the received bytes + :raises ValueError: if ``max_bytes`` is less than 1 + :raises ~anyio.EndOfStream: if this stream has been closed from the other end + """ + + +class ByteSendStream(AsyncResource, TypedAttributeProvider): + """An interface for sending bytes to a single peer.""" + + @abstractmethod + async def send(self, item: bytes) -> None: + """ + Send the given bytes to the peer. + + :param item: the bytes to send + """ + + +class ByteStream(ByteReceiveStream, ByteSendStream): + """A bidirectional byte stream.""" + + @abstractmethod + async def send_eof(self) -> None: + """ + Send an end-of-file indication to the peer. + + You should not try to send any further data to this stream after calling this + method. This method is idempotent (does nothing on successive calls). + """ + + +#: Type alias for all unreliable bytes-oriented receive streams. +AnyUnreliableByteReceiveStream: TypeAlias = ( + UnreliableObjectReceiveStream[bytes] | ByteReceiveStream +) +#: Type alias for all unreliable bytes-oriented send streams. +AnyUnreliableByteSendStream: TypeAlias = ( + UnreliableObjectSendStream[bytes] | ByteSendStream +) +#: Type alias for all unreliable bytes-oriented streams. +AnyUnreliableByteStream: TypeAlias = UnreliableObjectStream[bytes] | ByteStream +#: Type alias for all bytes-oriented receive streams. +AnyByteReceiveStream: TypeAlias = ObjectReceiveStream[bytes] | ByteReceiveStream +#: Type alias for all bytes-oriented send streams. +AnyByteSendStream: TypeAlias = ObjectSendStream[bytes] | ByteSendStream +#: Type alias for all bytes-oriented streams. +AnyByteStream: TypeAlias = ObjectStream[bytes] | ByteStream + + +class Listener(AsyncResource, TypedAttributeProvider, Generic[T_co]): + """An interface for objects that let you accept incoming connections.""" + + @abstractmethod + async def serve( + self, handler: Callable[[T_co], Any], task_group: TaskGroup | None = None + ) -> None: + """ + Accept incoming connections as they come in and start tasks to handle them. + + :param handler: a callable that will be used to handle each accepted connection + :param task_group: the task group that will be used to start tasks for handling + each accepted connection (if omitted, an ad-hoc task group will be created) + """ + + +class ObjectStreamConnectable(Generic[T_co], metaclass=ABCMeta): + @abstractmethod + async def connect(self) -> ObjectStream[T_co]: + """ + Connect to the remote endpoint. + + :return: an object stream connected to the remote end + :raises ConnectionFailed: if the connection fails + """ + + +class ByteStreamConnectable(metaclass=ABCMeta): + @abstractmethod + async def connect(self) -> ByteStream: + """ + Connect to the remote endpoint. + + :return: a bytestream connected to the remote end + :raises ConnectionFailed: if the connection fails + """ + + +#: Type alias for all connectables returning bytestreams or bytes-oriented object streams +AnyByteStreamConnectable: TypeAlias = ( + ObjectStreamConnectable[bytes] | ByteStreamConnectable +) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_subprocesses.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_subprocesses.py new file mode 100644 index 0000000000000000000000000000000000000000..555f15f10d2b8375a791a620eafd87795d8e9107 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_subprocesses.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from abc import abstractmethod +from signal import Signals +from typing import TYPE_CHECKING + +from ._resources import AsyncResource + +if TYPE_CHECKING: + from ._streams import ByteReceiveStream, ByteSendStream + + +class Process(AsyncResource): + """An asynchronous version of :class:`subprocess.Popen`.""" + + @abstractmethod + async def wait(self) -> int: + """ + Wait until the process exits. + + :return: the exit code of the process + """ + + @abstractmethod + def terminate(self) -> None: + """ + Terminates the process, gracefully if possible. + + On Windows, this calls ``TerminateProcess()``. + On POSIX systems, this sends ``SIGTERM`` to the process. + + .. seealso:: :meth:`subprocess.Popen.terminate` + """ + + @abstractmethod + def kill(self) -> None: + """ + Kills the process. + + On Windows, this calls ``TerminateProcess()``. + On POSIX systems, this sends ``SIGKILL`` to the process. + + .. seealso:: :meth:`subprocess.Popen.kill` + """ + + @abstractmethod + def send_signal(self, signal: Signals) -> None: + """ + Send a signal to the subprocess. + + .. seealso:: :meth:`subprocess.Popen.send_signal` + + :param signal: the signal number (e.g. :data:`signal.SIGHUP`) + """ + + @property + @abstractmethod + def pid(self) -> int: + """The process ID of the process.""" + + @property + @abstractmethod + def returncode(self) -> int | None: + """ + The return code of the process. If the process has not yet terminated, this will + be ``None``. + """ + + @property + @abstractmethod + def stdin(self) -> ByteSendStream | None: + """The stream for the standard input of the process.""" + + @property + @abstractmethod + def stdout(self) -> ByteReceiveStream | None: + """The stream for the standard output of the process.""" + + @property + @abstractmethod + def stderr(self) -> ByteReceiveStream | None: + """The stream for the standard error output of the process.""" diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_tasks.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_tasks.py new file mode 100644 index 0000000000000000000000000000000000000000..0a019e2ed3bd6bc36c97f87f3a8b9eee2ba0f657 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_tasks.py @@ -0,0 +1,223 @@ +from __future__ import annotations + +import sys +from abc import ABCMeta, abstractmethod +from collections.abc import Callable, Coroutine +from contextvars import Context +from types import TracebackType +from typing import TYPE_CHECKING, Any, Literal, Protocol, final, overload + +if sys.version_info >= (3, 13): + from typing import TypeVar +else: + from typing_extensions import TypeVar + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if TYPE_CHECKING: + from .._core._tasks import CancelScope, TaskHandle + +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True, default=None) +PosArgsT = TypeVarTuple("PosArgsT") + + +def get_coro_name(coro: Coroutine[Any, Any, object], override: object = None) -> str: + if override is not None: + return str(override) + + try: + cr_frame = coro.cr_frame # type: ignore[attr-defined] + module = cr_frame.f_globals["__name__"] + except (AttributeError, KeyError): + module = None + + qualname = getattr(coro, "__qualname__", None) + return ".".join([x for x in (module, qualname) if x]) + + +def get_callable_name(func: Callable, override: object = None) -> str: + if override is not None: + return str(override) + + module = getattr(func, "__module__", None) + qualname = getattr(func, "__qualname__", None) + return ".".join([x for x in (module, qualname) if x]) + + +def call_for_coroutine( + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + args: tuple[Unpack[PosArgsT]], + **kwargs: Any, +) -> Coroutine[Any, Any, T_co]: + """ + Call the given function with the given positional and keyword arguments. + + :return: the resulting coroutine + :raises TypeError: if the return value was not a coroutine object + + """ + coro = func(*args, **kwargs) + if not isinstance(coro, Coroutine): + prefix = f"{func.__module__}." if hasattr(func, "__module__") else "" + raise TypeError( + f"Expected {prefix}{func.__qualname__}() to return a coroutine, but " + f"the return value ({coro!r}) is not a coroutine object" + ) + + return coro + + +class TaskStatus(Protocol[T_contra]): + @overload + def started(self: TaskStatus[None]) -> None: ... + + @overload + def started(self, value: T_contra) -> None: ... + + def started(self, value: T_contra | None = None) -> None: + """ + Signal that the task has started. + + :param value: object passed back to the starter of the task + """ + + +class TaskGroup(metaclass=ABCMeta): + """ + Groups several asynchronous tasks together. + + :ivar cancel_scope: the cancel scope inherited by all child tasks + :vartype cancel_scope: CancelScope + + .. note:: On asyncio, support for eager task factories is considered to be + **experimental**. In particular, they don't follow the usual semantics of new + tasks being scheduled on the next iteration of the event loop, and may thus + cause unexpected behavior in code that wasn't written with such semantics in + mind. + """ + + cancel_scope: CancelScope + + def cancel(self, reason: str | None = None) -> None: + """ + Cancel this task group's cancel scope immediately. + + This is a shortcut for calling ``.cancel_scope.cancel()`` on the task group. + + :param reason: a message describing the reason for the cancellation + + .. versionadded:: 4.14.0 + + """ + self.cancel_scope.cancel(reason) + + @abstractmethod + def create_task( + self, + coro: Coroutine[Any, Any, T_co], + *, + name: object = None, + context: Context | None = None, + ) -> TaskHandle[T_co]: + """ + Create a new task from a coroutine object and schedule it to run. + + :param coro: a coroutine object + :param name: optional name to give the task + :param context: optional context to run the task in + :return: a task handle + + .. versionadded:: 4.14.0 + """ + + @final + def start_soon( + self, + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + *args: Unpack[PosArgsT], + name: object = None, + ) -> TaskHandle[T_co]: + """ + Start a new task in this task group. + + :param func: a coroutine function + :param args: positional arguments to call the function with + :param name: name of the task, for the purposes of introspection and debugging + :return: a task handle + + .. versionadded:: 3.0 + .. versionchanged:: 4.14.0 + This method now returns a task handle. + + """ + final_name = get_callable_name(func, name) + return self.create_task(call_for_coroutine(func, args), name=final_name) + + @overload + async def start( + self, + func: Callable[..., Coroutine[Any, Any, T_co]], + *args: object, + name: object = None, + return_handle: Literal[False] = ..., + ) -> Any: ... + + @overload + async def start( + self, + func: Callable[..., Coroutine[Any, Any, T_co]], + *args: object, + name: object = None, + return_handle: Literal[True], + ) -> TaskHandle[T_co, Any]: ... + + @abstractmethod + async def start( + self, + func: Callable[..., Coroutine[Any, Any, T_co]], + *args: object, + name: object = None, + return_handle: Literal[False, True] = False, + ) -> Any: + """ + Start a new task and wait until it signals for readiness. + + The target callable must accept a keyword argument ``task_status`` (of type + :class:`TaskStatus`). Awaiting on this method will return whatever was passed to + ``task_status.started()`` (``None`` by default). + + .. note:: The :class:`TaskStatus` class is generic, and the type argument should + indicate the type of the value that will be passed to + ``task_status.started()``. + + :param func: a coroutine function that accepts the ``task_status`` keyword + argument + :param args: positional arguments to call the function with + :param name: an optional name for the task, for introspection and debugging + :param return_handle: if ``True``, return a :class:`TaskHandle` which also + contains the start value in ``start_value`` + :return: the value passed to ``task_status.started()`` + :raises RuntimeError: if the task finishes without calling + ``task_status.started()`` + + .. seealso:: :ref:`start_initialize` + + .. versionadded:: 3.0 + """ + + @abstractmethod + async def __aenter__(self) -> TaskGroup: + """Enter the task group context and allow starting new tasks.""" + + @abstractmethod + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + """Exit the task group context waiting for all tasks to finish.""" diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_testing.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_testing.py new file mode 100644 index 0000000000000000000000000000000000000000..b18d8357e7a71d228877584e98b1286d273928f4 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/abc/_testing.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import sys +import types +from abc import ABCMeta, abstractmethod +from collections.abc import AsyncGenerator, Callable, Coroutine, Iterable +from typing import Any, TypeVar + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + +_T = TypeVar("_T") + + +class TestRunner(metaclass=ABCMeta): + """ + Encapsulates a running event loop. Every call made through this object will use the + same event loop. + """ + + def __enter__(self) -> Self: + return self + + @abstractmethod + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> bool | None: ... + + @abstractmethod + def run_asyncgen_fixture( + self, + fixture_func: Callable[..., AsyncGenerator[_T, Any]], + kwargs: dict[str, Any], + ) -> Iterable[_T]: + """ + Run an async generator fixture. + + :param fixture_func: the fixture function + :param kwargs: keyword arguments to call the fixture function with + :return: an iterator yielding the value yielded from the async generator + """ + + @abstractmethod + def run_fixture( + self, + fixture_func: Callable[..., Coroutine[Any, Any, _T]], + kwargs: dict[str, Any], + ) -> _T: + """ + Run an async fixture. + + :param fixture_func: the fixture function + :param kwargs: keyword arguments to call the fixture function with + :return: the return value of the fixture function + """ + + @abstractmethod + def run_test( + self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] + ) -> None: + """ + Run an async test function. + + :param test_func: the test function + :param kwargs: keyword arguments to call the test function with + """ + + @abstractmethod + def is_running(self) -> bool: + """ + Check if the test runner is running. + + :return: ``True`` if the coroutine is currently being run, ``False`` otherwise. + """ diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/from_thread.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/from_thread.py new file mode 100644 index 0000000000000000000000000000000000000000..f163d818b030f752350558e805988bf8c558b262 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/from_thread.py @@ -0,0 +1,588 @@ +from __future__ import annotations + +__all__ = ( + "BlockingPortal", + "BlockingPortalProvider", + "check_cancelled", + "run", + "run_sync", + "start_blocking_portal", +) + +import sys +from collections.abc import Awaitable, Callable, Coroutine, Generator +from concurrent.futures import Future +from contextlib import ( + AbstractAsyncContextManager, + AbstractContextManager, + contextmanager, +) +from contextvars import Context +from dataclasses import dataclass, field +from functools import partial +from inspect import isawaitable +from threading import Lock, Thread, current_thread, get_ident +from types import TracebackType +from typing import ( + Any, + Generic, + TypeVar, + cast, + overload, +) + +from ._core._eventloop import ( + get_cancelled_exc_class, + threadlocals, +) +from ._core._eventloop import run as run_eventloop +from ._core._exceptions import NoEventLoopError +from ._core._synchronization import Event +from ._core._tasks import CancelScope, create_task_group +from .abc._tasks import TaskStatus +from .lowlevel import EventLoopToken, current_token + +if sys.version_info >= (3, 11): + from typing import Self, TypeVarTuple, Unpack +else: + from typing_extensions import Self, TypeVarTuple, Unpack + +T_Retval = TypeVar("T_Retval") +T_co = TypeVar("T_co", covariant=True) +PosArgsT = TypeVarTuple("PosArgsT") + + +def _token_or_error(token: EventLoopToken | None) -> EventLoopToken: + if token is not None: + return token + + try: + return threadlocals.current_token + except AttributeError: + raise NoEventLoopError( + "Not running inside an AnyIO worker thread, and no event loop token was " + "provided" + ) from None + + +def run( + func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], + *args: Unpack[PosArgsT], + token: EventLoopToken | None = None, +) -> T_co: + """ + Call a coroutine function from a worker thread. + + :param func: a coroutine function + :param args: positional arguments for the callable + :param token: an event loop token to use to get back to the event loop thread + (required if calling this function from outside an AnyIO worker thread) + :return: the return value of the coroutine function + :raises MissingTokenError: if no token was provided and called from outside an + AnyIO worker thread + :raises RunFinishedError: if the event loop tied to ``token`` is no longer running + + .. versionchanged:: 4.11.0 + Added the ``token`` parameter. + + """ + explicit_token = token is not None + token = _token_or_error(token) + return token.backend_class.run_async_from_thread( + func, args, token=token.native_token if explicit_token else None + ) + + +def run_sync( + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + token: EventLoopToken | None = None, +) -> T_Retval: + """ + Call a function in the event loop thread from a worker thread. + + :param func: a callable + :param args: positional arguments for the callable + :param token: an event loop token to use to get back to the event loop thread + (required if calling this function from outside an AnyIO worker thread) + :return: the return value of the callable + :raises MissingTokenError: if no token was provided and called from outside an + AnyIO worker thread + :raises RunFinishedError: if the event loop tied to ``token`` is no longer running + + .. versionchanged:: 4.11.0 + Added the ``token`` parameter. + + """ + explicit_token = token is not None + token = _token_or_error(token) + return token.backend_class.run_sync_from_thread( + func, args, token=token.native_token if explicit_token else None + ) + + +class _BlockingAsyncContextManager(AbstractContextManager, Generic[T_co]): + _enter_future: Future[T_co] + _exit_future: Future[bool | None] + _exit_event: Event + _exit_exc_info: tuple[ + type[BaseException] | None, BaseException | None, TracebackType | None + ] = (None, None, None) + + def __init__( + self, async_cm: AbstractAsyncContextManager[T_co], portal: BlockingPortal + ): + self._async_cm = async_cm + self._portal = portal + + async def run_async_cm(self) -> bool | None: + try: + self._exit_event = Event() + value = await self._async_cm.__aenter__() + except BaseException as exc: + self._enter_future.set_exception(exc) + raise + else: + self._enter_future.set_result(value) + + try: + # Wait for the sync context manager to exit. + # This next statement can raise `get_cancelled_exc_class()` if + # something went wrong in a task group in this async context + # manager. + await self._exit_event.wait() + finally: + # In case of cancellation, it could be that we end up here before + # `_BlockingAsyncContextManager.__exit__` is called, and an + # `_exit_exc_info` has been set. + result = await self._async_cm.__aexit__(*self._exit_exc_info) + + return result + + def __enter__(self) -> T_co: + self._enter_future = Future() + self._exit_future = self._portal.start_task_soon(self.run_async_cm) + return self._enter_future.result() + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + /, + ) -> bool | None: + self._exit_exc_info = exc_type, exc_value, traceback + self._portal.call(self._exit_event.set) + return self._exit_future.result() + + +class _BlockingPortalTaskStatus(TaskStatus): + def __init__(self, future: Future): + self._future = future + + def started(self, value: object = None) -> None: + self._future.set_result(value) + + +class BlockingPortal: + """ + An object that lets external threads run code in an asynchronous event loop. + + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + """ + + def __init__(self) -> None: + self._token = current_token() + self._event_loop_thread_id: int | None = get_ident() + self._stop_event = Event() + self._task_group = create_task_group() + + async def __aenter__(self) -> Self: + await self._task_group.__aenter__() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + await self.stop() + return await self._task_group.__aexit__(exc_type, exc_val, exc_tb) + + def _check_running(self) -> None: + if self._event_loop_thread_id is None: + raise RuntimeError("This portal is not running") + if self._event_loop_thread_id == get_ident(): + raise RuntimeError( + "This method cannot be called from the event loop thread" + ) + + async def sleep_until_stopped(self) -> None: + """Sleep until :meth:`stop` is called.""" + await self._stop_event.wait() + + async def stop(self, cancel_remaining: bool = False) -> None: + """ + Signal the portal to shut down. + + This marks the portal as no longer accepting new calls and exits from + :meth:`sleep_until_stopped`. + + :param cancel_remaining: ``True`` to cancel all the remaining tasks, ``False`` + to let them finish before returning + + """ + self._event_loop_thread_id = None + self._stop_event.set() + if cancel_remaining: + self._task_group.cancel_scope.cancel("the blocking portal is shutting down") + + async def _call_func( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + future: Future[T_Retval], + ) -> None: + event_loop_thread_id = self._event_loop_thread_id + + def callback(f: Future[T_Retval]) -> None: + if f.cancelled(): + if event_loop_thread_id == get_ident(): + scope.cancel("the future was cancelled") + elif event_loop_thread_id is not None: + run_sync( + scope.cancel, "the future was cancelled", token=self._token + ) + + try: + retval_or_awaitable = func(*args, **kwargs) + if isawaitable(retval_or_awaitable): + with CancelScope() as scope: + future.add_done_callback(callback) + retval = await retval_or_awaitable + else: + retval = retval_or_awaitable + except get_cancelled_exc_class(): + future.cancel() + future.set_running_or_notify_cancel() + except BaseException as exc: + if not future.cancelled(): + future.set_exception(exc) + + # Let base exceptions fall through + if not isinstance(exc, Exception): + raise + else: + if not future.cancelled(): + future.set_result(retval) + finally: + scope = None # type: ignore[assignment] + + def _spawn_task_from_thread( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + name: object, + future: Future[T_Retval], + ) -> None: + """ + Spawn a new task using the given callable. + + :param func: a callable + :param args: positional arguments to be passed to the callable + :param kwargs: keyword arguments to be passed to the callable + :param name: name of the task (will be coerced to a string if not ``None``) + :param future: a future that will resolve to the return value of the callable, + or the exception raised during its execution + + """ + run_sync( + partial(self._task_group.start_soon, name=name), + self._call_func, + func, + args, + kwargs, + future, + token=self._token, + ) + + @overload + def call( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + *args: Unpack[PosArgsT], + ) -> T_Retval: ... + + @overload + def call( + self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT] + ) -> T_Retval: ... + + def call( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + *args: Unpack[PosArgsT], + ) -> T_Retval: + """ + Call the given function in the event loop thread. + + If the callable returns a coroutine object, it is awaited on. + + :param func: any callable + :raises RuntimeError: if the portal is not running or if this method is called + from within the event loop thread + + """ + return cast(T_Retval, self.start_task_soon(func, *args).result()) + + @overload + def start_task_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + *args: Unpack[PosArgsT], + name: object = None, + ) -> Future[T_Retval]: ... + + @overload + def start_task_soon( + self, + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + name: object = None, + ) -> Future[T_Retval]: ... + + def start_task_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + *args: Unpack[PosArgsT], + name: object = None, + ) -> Future[T_Retval]: + """ + Start a task in the portal's task group. + + The task will be run inside a cancel scope which can be cancelled by cancelling + the returned future. + + :param func: the target function + :param args: positional arguments passed to ``func`` + :param name: name of the task (will be coerced to a string if not ``None``) + :return: a future that resolves with the return value of the callable if the + task completes successfully, or with the exception raised in the task + :raises RuntimeError: if the portal is not running or if this method is called + from within the event loop thread + :rtype: concurrent.futures.Future[T_Retval] + + .. versionadded:: 3.0 + + """ + self._check_running() + f: Future[T_Retval] = Future() + self._spawn_task_from_thread(func, args, {}, name, f) + return f + + def start_task( + self, + func: Callable[..., Awaitable[T_Retval]], + *args: object, + name: object = None, + ) -> tuple[Future[T_Retval], Any]: + """ + Start a task in the portal's task group and wait until it signals for readiness. + + This method works the same way as :meth:`.abc.TaskGroup.start`. + + :param func: the target function + :param args: positional arguments passed to ``func`` + :param name: name of the task (will be coerced to a string if not ``None``) + :return: a tuple of (future, task_status_value) where the ``task_status_value`` + is the value passed to ``task_status.started()`` from within the target + function + :rtype: tuple[concurrent.futures.Future[T_Retval], Any] + + .. versionadded:: 3.0 + + """ + + def task_done(future: Future[T_Retval]) -> None: + if not task_status_future.done(): + if future.cancelled(): + task_status_future.cancel() + elif future.exception(): + task_status_future.set_exception(future.exception()) + else: + exc = RuntimeError( + "Task exited without calling task_status.started()" + ) + task_status_future.set_exception(exc) + + self._check_running() + task_status_future: Future = Future() + task_status = _BlockingPortalTaskStatus(task_status_future) + f: Future = Future() + f.add_done_callback(task_done) + self._spawn_task_from_thread(func, args, {"task_status": task_status}, name, f) + return f, task_status_future.result() + + def wrap_async_context_manager( + self, cm: AbstractAsyncContextManager[T_co] + ) -> AbstractContextManager[T_co]: + """ + Wrap an async context manager as a synchronous context manager via this portal. + + Spawns a task that will call both ``__aenter__()`` and ``__aexit__()``, stopping + in the middle until the synchronous context manager exits. + + :param cm: an asynchronous context manager + :return: a synchronous context manager + + .. versionadded:: 2.1 + + """ + return _BlockingAsyncContextManager(cm, self) + + +@dataclass +class BlockingPortalProvider: + """ + A manager for a blocking portal. Used as a context manager. The first thread to + enter this context manager causes a blocking portal to be started with the specific + parameters, and the last thread to exit causes the portal to be shut down. Thus, + there will be exactly one blocking portal running in this context as long as at + least one thread has entered this context manager. + + The parameters are the same as for :func:`~anyio.run`. + + :param backend: name of the backend + :param backend_options: backend options + + .. versionadded:: 4.4 + """ + + backend: str = "asyncio" + backend_options: dict[str, Any] | None = None + _lock: Lock = field(init=False, default_factory=Lock) + _leases: int = field(init=False, default=0) + _portal: BlockingPortal = field(init=False) + _portal_cm: AbstractContextManager[BlockingPortal] | None = field( + init=False, default=None + ) + + def __enter__(self) -> BlockingPortal: + with self._lock: + if self._portal_cm is None: + self._portal_cm = start_blocking_portal( + self.backend, self.backend_options + ) + self._portal = self._portal_cm.__enter__() + + self._leases += 1 + return self._portal + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + portal_cm: AbstractContextManager[BlockingPortal] | None = None + with self._lock: + assert self._portal_cm + assert self._leases > 0 + self._leases -= 1 + if not self._leases: + portal_cm = self._portal_cm + self._portal_cm = None + del self._portal + + if portal_cm: + portal_cm.__exit__(None, None, None) + + +@contextmanager +def start_blocking_portal( + backend: str = "asyncio", + backend_options: dict[str, Any] | None = None, + *, + name: str | None = None, +) -> Generator[BlockingPortal, Any, None]: + """ + Start a new event loop in a new thread and run a blocking portal in its main task. + + The parameters are the same as for :func:`~anyio.run`. + + :param backend: name of the backend + :param backend_options: backend options + :param name: name of the thread + :return: a context manager that yields a blocking portal + + .. versionchanged:: 3.0 + Usage as a context manager is now required. + + """ + + async def run_portal() -> None: + async with BlockingPortal() as portal_: + if name is None: + current_thread().name = f"{backend}-portal-{id(portal_):x}" + + future.set_result(portal_) + await portal_.sleep_until_stopped() + + def run_blocking_portal() -> None: + if future.set_running_or_notify_cancel(): + try: + run_eventloop( + run_portal, backend=backend, backend_options=backend_options + ) + except BaseException as exc: + if not future.done(): + future.set_exception(exc) + + future: Future[BlockingPortal] = Future() + kwargs: dict[str, Any] = {} + if sys.version_info >= (3, 14): + kwargs["context"] = Context() + + thread = Thread(target=run_blocking_portal, daemon=True, name=name, **kwargs) + thread.start() + try: + cancel_remaining_tasks = False + portal = future.result() + try: + yield portal + except BaseException: + cancel_remaining_tasks = True + raise + finally: + try: + portal.call(portal.stop, cancel_remaining_tasks) + except RuntimeError: + pass + finally: + thread.join() + + +def check_cancelled() -> None: + """ + Check if the cancel scope of the host task's running the current worker thread has + been cancelled. + + If the host task's current cancel scope has indeed been cancelled, the + backend-specific cancellation exception will be raised. + + :raises RuntimeError: if the current thread was not spawned by + :func:`.to_thread.run_sync` + + """ + try: + token: EventLoopToken = threadlocals.current_token + except AttributeError: + raise NoEventLoopError( + "This function can only be called inside an AnyIO worker thread" + ) from None + + token.backend_class.check_cancelled() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/functools.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/functools.py new file mode 100644 index 0000000000000000000000000000000000000000..b0bdfb4585efc4e4799388547668fba86fb5c687 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/functools.py @@ -0,0 +1,400 @@ +from __future__ import annotations + +__all__ = ( + "AsyncCacheInfo", + "AsyncCacheParameters", + "AsyncLRUCacheWrapper", + "cache", + "lru_cache", + "reduce", +) + +import functools +from collections import OrderedDict +from collections.abc import ( + AsyncIterable, + Awaitable, + Callable, + Coroutine, + Hashable, + Iterable, +) +from functools import update_wrapper +from inspect import iscoroutinefunction +from typing import ( + Any, + Generic, + NamedTuple, + ParamSpec, + TypedDict, + TypeVar, + cast, + final, + overload, +) +from weakref import WeakKeyDictionary + +from ._core._eventloop import current_time +from ._core._synchronization import Lock +from .lowlevel import RunVar, checkpoint + +T = TypeVar("T") +S = TypeVar("S") +P = ParamSpec("P") +lru_cache_items: RunVar[ + WeakKeyDictionary[ + AsyncLRUCacheWrapper[Any, Any], + OrderedDict[ + Hashable, + tuple[_InitialMissingType, Lock, float | None] + | tuple[Any, None, float | None], + ], + ] +] = RunVar("lru_cache_items") + + +class _InitialMissingType: + pass + + +initial_missing: _InitialMissingType = _InitialMissingType() + + +class AsyncCacheInfo(NamedTuple): + hits: int + misses: int + maxsize: int | None + currsize: int + ttl: int | None + + +class AsyncCacheParameters(TypedDict): + maxsize: int | None + typed: bool + always_checkpoint: bool + ttl: int | None + + +class _LRUMethodWrapper(Generic[T]): + def __init__(self, wrapper: AsyncLRUCacheWrapper[..., T], instance: object): + self.__wrapper = wrapper + self.__instance = instance + + def cache_info(self) -> AsyncCacheInfo: + return self.__wrapper.cache_info() + + def cache_parameters(self) -> AsyncCacheParameters: + return self.__wrapper.cache_parameters() + + def cache_clear(self) -> None: + self.__wrapper.cache_clear() + + async def __call__(self, *args: Any, **kwargs: Any) -> T: + if self.__instance is None: + return await self.__wrapper(*args, **kwargs) + + return await self.__wrapper(self.__instance, *args, **kwargs) + + +@final +class AsyncLRUCacheWrapper(Generic[P, T]): + def __init__( + self, + func: Callable[P, Awaitable[T]], + maxsize: int | None, + typed: bool, + always_checkpoint: bool, + ttl: int | None, + ): + self.__wrapped__ = func + self._hits: int = 0 + self._misses: int = 0 + self._maxsize = max(maxsize, 0) if maxsize is not None else None + self._currsize: int = 0 + self._typed = typed + self._always_checkpoint = always_checkpoint + self._ttl = ttl + update_wrapper(self, func) + + def cache_info(self) -> AsyncCacheInfo: + return AsyncCacheInfo( + self._hits, self._misses, self._maxsize, self._currsize, self._ttl + ) + + def cache_parameters(self) -> AsyncCacheParameters: + return { + "maxsize": self._maxsize, + "typed": self._typed, + "always_checkpoint": self._always_checkpoint, + "ttl": self._ttl, + } + + def cache_clear(self) -> None: + if cache := lru_cache_items.get(None): + cache.pop(self, None) + self._hits = self._misses = self._currsize = 0 + + async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: + # Easy case first: if maxsize == 0, no caching is done + if self._maxsize == 0: + value = await self.__wrapped__(*args, **kwargs) + self._misses += 1 + return value + + # The key is constructed as a flat tuple to avoid memory overhead + key: tuple[Any, ...] = args + if kwargs: + # initial_missing is used as a separator + key += (initial_missing,) + sum(kwargs.items(), ()) + + if self._typed: + key += tuple(type(arg) for arg in args) + if kwargs: + key += (initial_missing,) + tuple(type(val) for val in kwargs.values()) + + try: + cache = lru_cache_items.get() + except LookupError: + cache = WeakKeyDictionary() + lru_cache_items.set(cache) + + try: + cache_entry = cache[self] + except KeyError: + cache_entry = cache[self] = OrderedDict() + + cached_value: T | _InitialMissingType + try: + cached_value, lock, expires_at = cache_entry[key] + except KeyError: + # We're the first task to call this function + cached_value, lock, expires_at = ( + initial_missing, + Lock(fast_acquire=not self._always_checkpoint), + None, + ) + cache_entry[key] = cached_value, lock, expires_at + + if lock is None: + if expires_at is not None and current_time() >= expires_at: + self._currsize -= 1 + cached_value, lock, expires_at = ( + initial_missing, + Lock(fast_acquire=not self._always_checkpoint), + None, + ) + cache_entry[key] = cached_value, lock, expires_at + else: + # The value was already cached + self._hits += 1 + cache_entry.move_to_end(key) + if self._always_checkpoint: + await checkpoint() + + return cast(T, cached_value) + + async with lock: + # Check if another task filled the cache while we acquired the lock + if (cached_value := cache_entry[key][0]) is initial_missing: + self._misses += 1 + if self._maxsize is not None and self._currsize >= self._maxsize: + cache_entry.popitem(last=False) + else: + self._currsize += 1 + + value = await self.__wrapped__(*args, **kwargs) + expires_at = ( + current_time() + self._ttl if self._ttl is not None else None + ) + cache_entry[key] = value, None, expires_at + else: + # Another task filled the cache while we were waiting for the lock + self._hits += 1 + cache_entry.move_to_end(key) + value = cast(T, cached_value) + + return value + + def __get__( + self, instance: object, owner: type | None = None + ) -> _LRUMethodWrapper[T]: + wrapper = _LRUMethodWrapper(self, instance) + update_wrapper(wrapper, self.__wrapped__) + return wrapper + + +class _LRUCacheWrapper: + def __init__( + self, maxsize: int | None, typed: bool, always_checkpoint: bool, ttl: int | None + ): + self._maxsize = maxsize + self._typed = typed + self._always_checkpoint = always_checkpoint + self._ttl = ttl + + @overload + def __call__( # type: ignore[overload-overlap] + self, func: Callable[P, Coroutine[Any, Any, T]], / + ) -> AsyncLRUCacheWrapper[P, T]: ... + + @overload + def __call__( + self, func: Callable[..., T], / + ) -> functools._lru_cache_wrapper[T]: ... + + def __call__( + self, f: Callable[P, Coroutine[Any, Any, T]] | Callable[..., T], / + ) -> AsyncLRUCacheWrapper[P, T] | functools._lru_cache_wrapper[T]: + if iscoroutinefunction(f): + return AsyncLRUCacheWrapper( + f, self._maxsize, self._typed, self._always_checkpoint, self._ttl + ) + + return functools.lru_cache(maxsize=self._maxsize, typed=self._typed)(f) # type: ignore[arg-type] + + +@overload +def cache( # type: ignore[overload-overlap] + func: Callable[P, Coroutine[Any, Any, T]], / +) -> AsyncLRUCacheWrapper[P, T]: ... + + +@overload +def cache(func: Callable[..., T], /) -> functools._lru_cache_wrapper[T]: ... + + +def cache(func: Callable[..., Any] | Callable[P, Coroutine[Any, Any, Any]], /) -> Any: + """ + A convenient shortcut for :func:`lru_cache` with ``maxsize=None``. + + This is the asynchronous equivalent to :func:`functools.cache`. + + """ + return lru_cache(maxsize=None)(func) + + +@overload +def lru_cache( + *, + maxsize: int | None = ..., + typed: bool = ..., + always_checkpoint: bool = ..., + ttl: int | None = ..., +) -> _LRUCacheWrapper: ... + + +@overload +def lru_cache( # type: ignore[overload-overlap] + func: Callable[P, Coroutine[Any, Any, T]], / +) -> AsyncLRUCacheWrapper[P, T]: ... + + +@overload +def lru_cache(func: Callable[..., T], /) -> functools._lru_cache_wrapper[T]: ... + + +def lru_cache( + func: Callable[..., Coroutine[Any, Any, Any]] | Callable[..., Any] | None = None, + /, + *, + maxsize: int | None = 128, + typed: bool = False, + always_checkpoint: bool = False, + ttl: int | None = None, +) -> Any: + """ + An asynchronous version of :func:`functools.lru_cache`. + + If a synchronous function is passed, the standard library + :func:`functools.lru_cache` is applied instead. + + :param always_checkpoint: if ``True``, every call to the cached function will be + guaranteed to yield control to the event loop at least once + :param ttl: time in seconds after which to invalidate cache entries + + .. note:: Caches and locks are managed on a per-event loop basis. + + """ + if func is None: + return _LRUCacheWrapper(maxsize, typed, always_checkpoint, ttl) + + if not callable(func): + raise TypeError("the first argument must be callable") + + return _LRUCacheWrapper(maxsize, typed, always_checkpoint, ttl)(func) + + +@overload +async def reduce( + function: Callable[[T, S], Awaitable[T]], + iterable: Iterable[S] | AsyncIterable[S], + /, + initial: T, +) -> T: ... + + +@overload +async def reduce( + function: Callable[[T, T], Awaitable[T]], + iterable: Iterable[T] | AsyncIterable[T], + /, +) -> T: ... + + +async def reduce( # type: ignore[misc] + function: Callable[[T, T], Awaitable[T]] | Callable[[T, S], Awaitable[T]], + iterable: Iterable[T] | Iterable[S] | AsyncIterable[T] | AsyncIterable[S], + /, + initial: T | _InitialMissingType = initial_missing, +) -> T: + """ + Asynchronous version of :func:`functools.reduce`. + + :param function: a coroutine function that takes two arguments: the accumulated + value and the next element from the iterable + :param iterable: an iterable or async iterable + :param initial: the initial value (if missing, the first element of the iterable is + used as the initial value) + + """ + element: Any + function_called = False + if isinstance(iterable, AsyncIterable): + async_it = iterable.__aiter__() + if initial is initial_missing: + try: + value = cast(T, await async_it.__anext__()) + except StopAsyncIteration: + raise TypeError( + "reduce() of empty sequence with no initial value" + ) from None + else: + value = cast(T, initial) + + async for element in async_it: + value = await function(value, element) + function_called = True + elif isinstance(iterable, Iterable): + it = iter(iterable) + if initial is initial_missing: + try: + value = cast(T, next(it)) + except StopIteration: + raise TypeError( + "reduce() of empty sequence with no initial value" + ) from None + else: + value = cast(T, initial) + + for element in it: + value = await function(value, element) + function_called = True + else: + raise TypeError("reduce() argument 2 must be an iterable or async iterable") + + # Make sure there is at least one checkpoint, even if an empty iterable and an + # initial value were given + if not function_called: + await checkpoint() + + return value diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/itertools.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/itertools.py new file mode 100644 index 0000000000000000000000000000000000000000..2a10cfd3ba01b22f424d18accaf9758233a67cca --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/itertools.py @@ -0,0 +1,629 @@ +from __future__ import annotations + +__all__ = ( + "Chain", + "accumulate", + "batched", + "combinations", + "combinations_with_replacement", + "compress", + "count", + "cycle", + "dropwhile", + "filterfalse", + "groupby", + "islice", + "pairwise", + "permutations", + "product", + "repeat", + "starmap", + "takewhile", + "tee", + "zip_longest", +) + +import itertools +import operator +import sys +from collections.abc import ( + AsyncGenerator, + AsyncIterable, + AsyncIterator, + Awaitable, + Callable, + Iterable, + Iterator, +) +from dataclasses import dataclass, field +from typing import Any, Generic, TypeVar, cast, overload + +from ._core._synchronization import Lock +from ._core._tasks import CancelScope +from .lowlevel import cancel_shielded_checkpoint, checkpoint, checkpoint_if_cancelled + +if sys.version_info < (3, 15): + from typing_extensions import sentinel + +T = TypeVar("T") +R = TypeVar("R") +_tee_end = sentinel("_tee_end") + + +@dataclass(eq=False) +class _IterableAsyncIterator(AsyncIterator[T]): + iterator: Iterator[T] + + async def __anext__(self) -> T: + await checkpoint_if_cancelled() + try: + result = next(self.iterator) + except StopIteration: + await cancel_shielded_checkpoint() + raise StopAsyncIteration from None + + await cancel_shielded_checkpoint() + return result + + +def _iterate(iterable: Iterable[T] | AsyncIterable[T]) -> AsyncIterator[T]: + if isinstance(iterable, AsyncIterator): + return iterable + + if isinstance(iterable, AsyncIterable): + return iterable.__aiter__() + + return _IterableAsyncIterator(iter(iterable)) + + +@dataclass(eq=False) +class _TeeLink(Generic[T]): + value: object | None = None + next: _TeeLink[T] | None = None + filled: bool = False + + +@dataclass(eq=False) +class _TeeState(Generic[T]): + iterator: AsyncIterator[T] + lock: Lock = field(default_factory=Lock) + + async def fill(self, link: _TeeLink[T]) -> bool: + if link.filled: + return False + + async with self.lock: + if link.filled: + return True + + link.value = await anext(self.iterator, _tee_end) + if link.value is not _tee_end: + link.next = _TeeLink() + + link.filled = True + return True + + +class _TeeAsyncIterator(AsyncIterator[T]): + _state: _TeeState[T] + _link: _TeeLink[T] + _element_yielded: bool + + def __init__( + self, iterable: Iterable[T] | AsyncIterable[T] | _TeeAsyncIterator[T] + ) -> None: + if isinstance(iterable, _TeeAsyncIterator): + self._state = iterable._state + self._link = iterable._link + else: + self._state = _TeeState(_iterate(iterable)) + self._link = _TeeLink() + + self._element_yielded = False + + async def __anext__(self) -> T: + had_yieldpoint = await self._state.fill(self._link) + if self._link.value is _tee_end: + if not self._element_yielded: + await checkpoint() + + raise StopAsyncIteration + + if not had_yieldpoint: + await checkpoint_if_cancelled() + + self._element_yielded = True + value = cast(T, self._link.value) + next_link = self._link.next + assert next_link is not None + self._link = next_link + if not had_yieldpoint: + await cancel_shielded_checkpoint() + + return value + + +async def _operator_add(x: T, y: T) -> T: + return operator.add(x, y) + + +async def accumulate( + iterable: Iterable[T] | AsyncIterable[T], + function: Callable[[T, T], Awaitable[T]] = _operator_add, + *, + initial: T | None = None, +) -> AsyncGenerator[T, None]: + iterator = _iterate(iterable) + if initial is None: + try: + total = await anext(iterator) + except StopAsyncIteration: + await checkpoint() + return + else: + await checkpoint_if_cancelled() + total = initial + await cancel_shielded_checkpoint() + + yield total + + async for element in iterator: + total = await function(total, element) + yield total + + +async def batched( + iterable: Iterable[T] | AsyncIterable[T], n: int, *, strict: bool = False +) -> AsyncGenerator[tuple[T, ...], None]: + if n < 1: + raise ValueError("n must be at least one") + + iterator = _iterate(iterable) + + while True: + batch: list[T] = [] + for _ in range(n): + try: + batch.append(await anext(iterator)) + except StopAsyncIteration: + if not batch: + await checkpoint() + return + if strict: + raise ValueError("batched(): incomplete batch") from None + + yield tuple(batch) + return + + yield tuple(batch) + + +class Chain: + def __call__( + self, *iterables: Iterable[T] | AsyncIterable[T] + ) -> AsyncGenerator[T, None]: + return self.from_iterable(iterables) + + async def from_iterable( + self, + iterables: ( + Iterable[Iterable[T] | AsyncIterable[T]] + | AsyncIterable[Iterable[T] | AsyncIterable[T]] + ), + ) -> AsyncGenerator[T, None]: + element_yielded = False + outer_iter = _iterate(iterables) + + try: + async for iterable in outer_iter: + async for element in _iterate(iterable): + element_yielded = True + yield element + finally: + aclose = getattr(outer_iter, "aclose", None) + if aclose is not None: + with CancelScope(shield=True): + await aclose() + + if not element_yielded: + await checkpoint() + + +chain: Chain = Chain() + + +async def combinations( + iterable: Iterable[T] | AsyncIterable[T], r: int +) -> AsyncGenerator[tuple[T, ...], None]: + pool: list[T] = [element async for element in _iterate(iterable)] + async for combination in _iterate(itertools.combinations(pool, r)): + yield combination + + +async def combinations_with_replacement( + iterable: Iterable[T] | AsyncIterable[T], r: int +) -> AsyncGenerator[tuple[T, ...], None]: + pool: list[T] = [element async for element in _iterate(iterable)] + async for combination in _iterate(itertools.combinations_with_replacement(pool, r)): + yield combination + + +async def compress( + data: Iterable[T] | AsyncIterable[T], + selectors: Iterable[object] | AsyncIterable[object], +) -> AsyncGenerator[T, None]: + data_iterator = _iterate(data) + selector_iterator = _iterate(selectors) + element_yielded = False + + while True: + try: + datum = await anext(data_iterator) + selector = await anext(selector_iterator) + except StopAsyncIteration: + if not element_yielded: + await checkpoint() + + return + + if selector: + element_yielded = True + yield datum + + +async def count(start: int = 0, step: int = 1) -> AsyncGenerator[int, None]: + n = start + while True: + await checkpoint_if_cancelled() + value = n + n += step + await cancel_shielded_checkpoint() + yield value + + +async def cycle( + iterable: Iterable[T] | AsyncIterable[T], +) -> AsyncGenerator[T, None]: + saved: list[T] = [] + async for element in _iterate(iterable): + saved.append(element) + yield element + + if not saved: + await checkpoint() + return + + while True: + for element in saved: + await checkpoint() + yield element + + +async def dropwhile( + predicate: Callable[[T], Awaitable[object]], + iterable: Iterable[T] | AsyncIterable[T], +) -> AsyncGenerator[T, None]: + element_yielded = False + dropping = True + + async for element in _iterate(iterable): + if dropping and await predicate(element): + continue + + dropping = False + element_yielded = True + yield element + + if not element_yielded: + await checkpoint() + + +async def filterfalse( + predicate: Callable[[T], Awaitable[object]], + iterable: Iterable[T] | AsyncIterable[T], +) -> AsyncGenerator[T, None]: + element_yielded = False + + async for element in _iterate(iterable): + if not await predicate(element): + element_yielded = True + yield element + + if not element_yielded: + await checkpoint() + + +@overload +def groupby( + iterable: Iterable[T] | AsyncIterable[T], +) -> AsyncGenerator[tuple[T, list[T]], None]: ... + + +@overload +def groupby( + iterable: Iterable[T] | AsyncIterable[T], + key: Callable[[T], Awaitable[R]], +) -> AsyncGenerator[tuple[R, list[T]], None]: ... + + +async def groupby( + iterable: Iterable[T] | AsyncIterable[T], + key: Callable[[T], Awaitable[object]] | None = None, +) -> AsyncGenerator[tuple[object, list[T]], None]: + iterator = _iterate(iterable) + try: + element = await anext(iterator) + except StopAsyncIteration: + await checkpoint() + return + + group_key = element if key is None else await key(element) + values = [element] + + async for element in iterator: + next_key = element if key is None else await key(element) + if next_key != group_key: + completed_group = group_key, values + group_key = next_key + values = [element] + yield completed_group + else: + values.append(element) + + yield group_key, values + + +@overload +def islice( + iterable: Iterable[T] | AsyncIterable[T], + stop: int | None, + /, +) -> AsyncGenerator[T, None]: ... + + +@overload +def islice( + iterable: Iterable[T] | AsyncIterable[T], + start: int | None, + stop: int | None, + step: int | None = 1, + /, +) -> AsyncGenerator[T, None]: ... + + +async def islice( + iterable: Iterable[T] | AsyncIterable[T], + *args: int | None, +) -> AsyncGenerator[T, None]: + if not args: + raise TypeError("islice expected at least 2 arguments, got 1") + if len(args) > 3: + raise TypeError(f"islice expected at most 4 arguments, got {len(args) + 1}") + + slice_args = slice(*args) + + start_message = ( + "Indices for islice() must be None or an integer: 0 <= x <= sys.maxsize." + ) + stop_message = ( + "Stop argument for islice() must be None or an integer: 0 <= x <= sys.maxsize." + ) + step_message = "Step for islice() must be a positive integer or None." + + def normalize_index(value: object, message: str) -> int: + try: + index = operator.index(cast(Any, value)) + except TypeError: + raise ValueError(message) from None + + if index < 0 or index > sys.maxsize: + raise ValueError(message) + + return index + + start = ( + 0 + if slice_args.start is None + else normalize_index(slice_args.start, start_message) + ) + stop = ( + None + if slice_args.stop is None + else normalize_index(slice_args.stop, stop_message) + ) + step = ( + 1 if slice_args.step is None else normalize_index(slice_args.step, step_message) + ) + + if step <= 0: + raise ValueError(step_message) + + if stop == 0 or start == stop: + await checkpoint() + return + + iterator = _iterate(iterable) + index = 0 + element_yielded = False + + while stop is None or index < stop: + try: + element = await anext(iterator) + except StopAsyncIteration: + if not element_yielded: + await checkpoint() + + return + + if index >= start and (index - start) % step == 0: + index += 1 + element_yielded = True + yield element + else: + index += 1 + + if not element_yielded: + await checkpoint() + + +async def pairwise( + iterable: Iterable[T] | AsyncIterable[T], +) -> AsyncGenerator[tuple[T, T], None]: + iterator = _iterate(iterable) + try: + previous = await anext(iterator) + except StopAsyncIteration: + await checkpoint() + return + + element_yielded = False + async for element in iterator: + element_yielded = True + pair = (previous, element) + previous = element + yield pair + + if not element_yielded: + await checkpoint() + + +async def permutations( + iterable: Iterable[T] | AsyncIterable[T], r: int | None = None +) -> AsyncGenerator[tuple[T, ...], None]: + pool: list[T] = [element async for element in _iterate(iterable)] + n = len(pool) + if r is None: + r = n + elif not isinstance(r, int): + raise TypeError("Expected int as r") + elif r < 0: + raise ValueError("r must be non-negative") + + async for permutation in _iterate(itertools.permutations(pool, r)): + yield permutation + + +async def product( + *iterables: Iterable[T] | AsyncIterable[T], repeat: int = 1 +) -> AsyncGenerator[tuple[T, ...], None]: + repeat = operator.index(repeat) + if repeat < 0: + raise ValueError("repeat argument cannot be negative") + + pools: list[tuple[T, ...]] = [] + for iterable in iterables: + pool: list[T] = [element async for element in _iterate(iterable)] + pools.append(tuple(pool)) + + async for value in _iterate(itertools.product(*pools, repeat=repeat)): + yield value + + +async def repeat(element: T, times: int | None = None) -> AsyncGenerator[T, None]: + if times is None: + while True: + await checkpoint() + yield element + + remaining = operator.index(cast(Any, times)) + if remaining <= 0: + await checkpoint() + return + + while remaining > 0: + await checkpoint_if_cancelled() + remaining -= 1 + await cancel_shielded_checkpoint() + yield element + + +async def starmap( + function: Callable[..., Awaitable[R]], + iterable: ( + Iterable[Iterable[object] | AsyncIterable[object]] + | AsyncIterable[Iterable[object] | AsyncIterable[object]] + ), +) -> AsyncGenerator[R, None]: + result_yielded = False + + async for args_iterable in _iterate(iterable): + args = [element async for element in _iterate(args_iterable)] + result_yielded = True + yield await function(*args) + + if not result_yielded: + await checkpoint() + + +def tee( + iterable: Iterable[T] | AsyncIterable[T], n: int = 2 +) -> tuple[AsyncIterator[T], ...]: + n = operator.index(cast(Any, n)) + if n < 0: + raise ValueError("n must be >= 0") + if n == 0: + return () + + iterator = _TeeAsyncIterator(iterable) + iterators: list[AsyncIterator[T]] = [iterator] + iterators.extend(_TeeAsyncIterator(iterator) for _ in range(n - 1)) + return tuple(iterators) + + +async def takewhile( + predicate: Callable[[T], Awaitable[object]], + iterable: Iterable[T] | AsyncIterable[T], +) -> AsyncGenerator[T, None]: + element_yielded = False + + async for element in _iterate(iterable): + if not await predicate(element): + if not element_yielded: + await checkpoint() + + return + + element_yielded = True + yield element + + if not element_yielded: + await checkpoint() + + +async def zip_longest( + *iterables: Iterable[object] | AsyncIterable[object], + fillvalue: object = None, +) -> AsyncGenerator[tuple[object, ...], None]: + iterators = [_iterate(iterable) for iterable in iterables] + num_active = len(iterators) + if not num_active: + await checkpoint() + return + + active = [True] * num_active + tuple_yielded = False + + while True: + values: list[object] = [] + for index, iterator in enumerate(iterators): + if not active[index]: + values.append(fillvalue) + continue + + try: + value = await anext(iterator) + except StopAsyncIteration: + active[index] = False + num_active -= 1 + if not num_active: + if not tuple_yielded: + await checkpoint() + + return + + value = fillvalue + + values.append(value) + + tuple_yielded = True + yield tuple(values) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/lowlevel.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/lowlevel.py new file mode 100644 index 0000000000000000000000000000000000000000..361b2eb84d3600e8c64afe9b010825fd2b8c5288 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/lowlevel.py @@ -0,0 +1,228 @@ +from __future__ import annotations + +__all__ = ( + "EventLoopToken", + "RunVar", + "RunvarToken", + "cancel_shielded_checkpoint", + "checkpoint", + "checkpoint_if_cancelled", + "current_token", +) + +import enum +from dataclasses import dataclass +from types import TracebackType +from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, final, overload +from weakref import WeakKeyDictionary + +from ._core._eventloop import get_async_backend + +if TYPE_CHECKING: + from .abc import AsyncBackend + +T = TypeVar("T") +D = TypeVar("D") + + +async def checkpoint() -> None: + """ + Check for cancellation and allow the scheduler to switch to another task. + + Equivalent to (but more efficient than):: + + await checkpoint_if_cancelled() + await cancel_shielded_checkpoint() + + .. versionadded:: 3.0 + + """ + await get_async_backend().checkpoint() + + +async def checkpoint_if_cancelled() -> None: + """ + Enter a checkpoint if the enclosing cancel scope has been cancelled. + + This does not allow the scheduler to switch to a different task. + + .. versionadded:: 3.0 + + """ + await get_async_backend().checkpoint_if_cancelled() + + +async def cancel_shielded_checkpoint() -> None: + """ + Allow the scheduler to switch to another task but without checking for cancellation. + + Equivalent to (but potentially more efficient than):: + + with CancelScope(shield=True): + await checkpoint() + + .. versionadded:: 3.0 + + """ + await get_async_backend().cancel_shielded_checkpoint() + + +@final +@dataclass(frozen=True, repr=False) +class EventLoopToken: + """ + An opaque object that holds a reference to an event loop. + + .. versionadded:: 4.11.0 + """ + + backend_class: type[AsyncBackend] + native_token: object + + +def current_token() -> EventLoopToken: + """ + Return a token object that can be used to call code in the current event loop from + another thread. + + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. versionadded:: 4.11.0 + + """ + backend_class = get_async_backend() + raw_token = backend_class.current_token() + return EventLoopToken(backend_class, raw_token) + + +_run_vars: WeakKeyDictionary[object, dict[RunVar[Any], Any]] = WeakKeyDictionary() + + +class _NoValueSet(enum.Enum): + NO_VALUE_SET = enum.auto() + + +class RunvarToken(Generic[T]): + """ + A token that can be used to restore a :class:`RunVar` to its previous value. + + Returned by :meth:`RunVar.set`. Can be used as a context manager to automatically + reset the variable on exit, or passed directly to :meth:`RunVar.reset`. + """ + + __slots__ = "_redeemed", "_value", "_var" + + def __init__(self, var: RunVar[T], value: T | Literal[_NoValueSet.NO_VALUE_SET]): + self._var = var + self._value: T | Literal[_NoValueSet.NO_VALUE_SET] = value + self._redeemed = False + + def __enter__(self) -> RunvarToken[T]: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self._var.reset(self) + + +class RunVar(Generic[T]): + """ + Like a :class:`~contextvars.ContextVar`, except scoped to the running event loop. + + Can be used as a context manager, Just like :class:`~contextvars.ContextVar`, that + will reset the variable to its previous value when the context block is exited. + """ + + __slots__ = "_default", "_name" + + NO_VALUE_SET: Literal[_NoValueSet.NO_VALUE_SET] = _NoValueSet.NO_VALUE_SET + + def __init__( + self, name: str, default: T | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET + ): + self._name = name + self._default = default + + @property + def _current_vars(self) -> dict[RunVar[T], T]: + native_token = current_token().native_token + try: + return _run_vars[native_token] + except KeyError: + run_vars = _run_vars[native_token] = {} + return run_vars + + @overload + def get(self, default: D) -> T | D: ... + + @overload + def get(self) -> T: ... + + def get( + self, default: D | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET + ) -> T | D: + """ + Return the current value of this run variable. + + :param default: a fallback value to return if no value has been set + :return: the current value, the provided default, or the variable's own default + :raises LookupError: if no value is set and no default is available + + """ + try: + return self._current_vars[self] + except KeyError: + if default is not RunVar.NO_VALUE_SET: + return default + elif self._default is not RunVar.NO_VALUE_SET: + return self._default + + raise LookupError( + f'Run variable "{self._name}" has no value and no default set' + ) + + def set(self, value: T) -> RunvarToken[T]: + """ + Set the value of this run variable for the current event loop. + + :param value: the new value + :return: a token that can be used to restore the previous value + + """ + current_vars = self._current_vars + token = RunvarToken(self, current_vars.get(self, RunVar.NO_VALUE_SET)) + current_vars[self] = value + return token + + def reset(self, token: RunvarToken[T]) -> None: + """ + Restore this run variable to the value it held before the matching :meth:`set`. + + :param token: the token returned by :meth:`set` + :raises ValueError: if the token belongs to a different :class:`RunVar` or the token + has already been used + + """ + if token._var is not self: + raise ValueError("This token does not belong to this RunVar") + + if token._redeemed: + raise ValueError("This token has already been used") + + if token._value is _NoValueSet.NO_VALUE_SET: + try: + del self._current_vars[self] + except KeyError: + pass + else: + self._current_vars[self] = token._value + + token._redeemed = True + + def __repr__(self) -> str: + return f"" diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/py.typed b/.venvs/kimodo/lib/python3.10/site-packages/anyio/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/pytest_plugin.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/pytest_plugin.py new file mode 100644 index 0000000000000000000000000000000000000000..d6238cd110ab793ccccb9671fc7eccc33c5415e5 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/pytest_plugin.py @@ -0,0 +1,404 @@ +from __future__ import annotations + +import dataclasses +import socket +import sys +from collections.abc import Callable, Generator, Iterator +from contextlib import ExitStack, contextmanager +from inspect import isasyncgenfunction, iscoroutinefunction, ismethod +from typing import TYPE_CHECKING, Any, cast + +import pytest +from _pytest.fixtures import FuncFixtureInfo, SubRequest +from _pytest.outcomes import Exit +from _pytest.scope import Scope + +from . import get_available_backends +from ._core._eventloop import ( + current_async_library, + get_async_backend, + reset_current_async_library, + set_current_async_library, +) +from ._core._exceptions import iterate_exceptions + +if TYPE_CHECKING: + from .abc import TestRunner + +if sys.version_info < (3, 11): + from exceptiongroup import ExceptionGroup + +if TYPE_CHECKING: + # pytest >= 9.2 keeps CallSpec2 as a TYPE_CHECKING-only alias of CallSpec + from _pytest.python import CallSpec2 as CallSpec +else: + try: + # c.f. https://github.com/pytest-dev/pytest/pull/14742 + # pytest >= 9.2 + from _pytest.python import CallSpec + except ImportError: + from _pytest.python import CallSpec2 as CallSpec + +_current_runner: TestRunner | None = None +_runner_stack: ExitStack | None = None +_runner_leases = 0 + + +def extract_backend_and_options(backend: object) -> tuple[str, dict[str, Any]]: + if isinstance(backend, str): + return backend, {} + elif isinstance(backend, tuple) and len(backend) == 2: + if isinstance(backend[0], str) and isinstance(backend[1], dict): + return cast(tuple[str, dict[str, Any]], backend) + + raise TypeError("anyio_backend must be either a string or tuple of (string, dict)") + + +@contextmanager +def get_runner( + backend_name: str, backend_options: dict[str, Any] +) -> Iterator[TestRunner]: + global _current_runner, _runner_leases, _runner_stack + if _current_runner is None: + asynclib = get_async_backend(backend_name) + _runner_stack = ExitStack() + if current_async_library() is None: + # Since we're in control of the event loop, we can cache the name of the + # async library + token = set_current_async_library(backend_name) + _runner_stack.callback(reset_current_async_library, token) + + backend_options = backend_options or {} + _current_runner = _runner_stack.enter_context( + asynclib.create_test_runner(backend_options) + ) + + _runner_leases += 1 + try: + yield _current_runner + finally: + _runner_leases -= 1 + if not _runner_leases: + assert _runner_stack is not None + _runner_stack.close() + _runner_stack = _current_runner = None + + +def pytest_addoption(parser: pytest.Parser) -> None: + group = parser.getgroup("anyio") + parser.addini( + "anyio_mode", + default="strict", + help='AnyIO plugin mode (either "strict" or "auto")', + ) + group.addoption( + "--anyio-mode", + default=None, + dest="anyio_mode", + help=""" + 'auto' - All async test functions will be handled by AnyIO pytest plugin + 'strict' - Disabling autoprocessing(useful when anyio tests + need to coexist with other async test plugins) + """, + ) + + +def _is_auto_mode(config: pytest.Config, mode: str) -> bool: + return ( + config.getoption(mode, default=None) == "auto" or config.getini(mode) == "auto" + ) + + +def pytest_configure(config: pytest.Config) -> None: + config.addinivalue_line( + "markers", + "anyio: mark the (coroutine function) test to be run asynchronously via anyio.", + ) + if ( + _is_auto_mode(config, "anyio_mode") + and config.pluginmanager.has_plugin("asyncio") + and _is_auto_mode(config, "asyncio_mode") + ): + config.issue_config_time_warning( + pytest.PytestConfigWarning( + "AnyIO auto mode has been enabled together with pytest-asyncio auto " + "mode. This may cause unexpected behavior." + ), + 1, + ) + + +@pytest.hookimpl(hookwrapper=True) +def pytest_fixture_setup(fixturedef: Any, request: Any) -> Generator[Any]: + def wrapper(anyio_backend: Any, request: SubRequest, **kwargs: Any) -> Any: + # Rebind any fixture methods to the request instance + if ( + request.instance + and ismethod(func) + and type(func.__self__) is type(request.instance) + ): + local_func = func.__func__.__get__(request.instance) + else: + local_func = func + + backend_name, backend_options = extract_backend_and_options(anyio_backend) + if has_backend_arg: + kwargs["anyio_backend"] = anyio_backend + + if has_request_arg: + kwargs["request"] = request + + with get_runner(backend_name, backend_options) as runner: + # re-entrant call into the test runner detected. this happens when an async fixture + # is dynamically requested via request.getfixturevalue() from inside a running async + # test or fixture. on asyncio this raises RuntimeError: This event loop is already + # running, on trio the runner deadlocks - the host loop blocks waiting for the + # coroutine to return, but the coroutine is waiting for the host loop. raising here + # prevents the hang and gives a consistent error across backends. + if runner.is_running(): + raise RuntimeError( + "Cannot schedule a coroutine in the test runner while another is already running; " + "likely caused by request.getfixturevalue() on an async fixture." + ) + + if isasyncgenfunction(local_func): + yield from runner.run_asyncgen_fixture(local_func, kwargs) + else: + yield runner.run_fixture(local_func, kwargs) + + # Only apply this to coroutine functions and async generator functions in requests + # that involve the anyio_backend fixture + func = fixturedef.func + if isasyncgenfunction(func) or iscoroutinefunction(func): + if "anyio_backend" in request.fixturenames: + fixturedef.func = wrapper + original_argname = fixturedef.argnames + + if not (has_backend_arg := "anyio_backend" in fixturedef.argnames): + fixturedef.argnames += ("anyio_backend",) + + if not (has_request_arg := "request" in fixturedef.argnames): + fixturedef.argnames += ("request",) + + try: + return (yield) + finally: + fixturedef.func = func + fixturedef.argnames = original_argname + + return (yield) + + +@pytest.hookimpl(tryfirst=True) +def pytest_pycollect_makeitem( + collector: pytest.Module | pytest.Class, name: str, obj: object +) -> None: + if collector.istestfunction(obj, name): + inner_func = obj.hypothesis.inner_test if hasattr(obj, "hypothesis") else obj + if iscoroutinefunction(inner_func): + anyio_auto_mode = _is_auto_mode(collector.config, "anyio_mode") + marker = collector.get_closest_marker("anyio") + own_markers = getattr(obj, "pytestmark", ()) + if ( + anyio_auto_mode + or marker + or any(marker.name == "anyio" for marker in own_markers) + ): + pytest.mark.usefixtures("anyio_backend")(obj) + + +def pytest_collection_finish(session: pytest.Session) -> None: + for i, item in reversed(list(enumerate(session.items))): + if ( + isinstance(item, pytest.Function) + and iscoroutinefunction(item.function) + and item.get_closest_marker("anyio") is not None + and "anyio_backend" not in item.fixturenames + ): + new_items = [] + try: + cs_fields = {f.name for f in dataclasses.fields(CallSpec)} + except TypeError: + cs_fields = set() + + for param_index, backend in enumerate(get_available_backends()): + if "_arg2scope" in cs_fields: # pytest >= 8 + callspec = CallSpec( + params={"anyio_backend": backend}, + indices={"anyio_backend": param_index}, + _arg2scope={"anyio_backend": Scope.Module}, + _idlist=[backend], + marks=[], + ) + else: # pytest 7.x + callspec = CallSpec( # type: ignore[call-arg] + funcargs={}, + params={"anyio_backend": backend}, + indices={"anyio_backend": param_index}, + arg2scope={"anyio_backend": Scope.Module}, + idlist=[backend], + marks=[], + ) + + fi = item._fixtureinfo + new_names_closure = list(fi.names_closure) + if "anyio_backend" not in new_names_closure: + new_names_closure.append("anyio_backend") + + new_fixtureinfo = FuncFixtureInfo( + argnames=fi.argnames, + initialnames=fi.initialnames, + names_closure=new_names_closure, + name2fixturedefs=fi.name2fixturedefs, + ) + new_item = pytest.Function.from_parent( + item.parent, + name=f"{item.originalname}[{backend}]", + callspec=callspec, + callobj=item.obj, + fixtureinfo=new_fixtureinfo, + keywords=item.keywords, + originalname=item.originalname, + ) + new_items.append(new_item) + + session.items[i : i + 1] = new_items + + +@pytest.hookimpl(tryfirst=True) +def pytest_pyfunc_call(pyfuncitem: Any) -> bool | None: + def run_with_hypothesis(**kwargs: Any) -> None: + with get_runner(backend_name, backend_options) as runner: + runner.run_test(original_func, kwargs) + + backend = pyfuncitem.funcargs.get("anyio_backend") + if backend: + backend_name, backend_options = extract_backend_and_options(backend) + + if hasattr(pyfuncitem.obj, "hypothesis"): + # Wrap the inner test function unless it's already wrapped + original_func = pyfuncitem.obj.hypothesis.inner_test + if original_func.__qualname__ != run_with_hypothesis.__qualname__: + if iscoroutinefunction(original_func): + pyfuncitem.obj.hypothesis.inner_test = run_with_hypothesis + + return None + + if iscoroutinefunction(pyfuncitem.obj): + funcargs = pyfuncitem.funcargs + testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} + with get_runner(backend_name, backend_options) as runner: + try: + runner.run_test(pyfuncitem.obj, testargs) + except ExceptionGroup as excgrp: + for exc in iterate_exceptions(excgrp): + if isinstance(exc, (Exit, KeyboardInterrupt, SystemExit)): + raise exc from excgrp + + raise + + return True + + return None + + +@pytest.fixture(scope="module", params=get_available_backends()) +def anyio_backend(request: Any) -> Any: + return request.param + + +@pytest.fixture +def anyio_backend_name(anyio_backend: Any) -> str: + if isinstance(anyio_backend, str): + return anyio_backend + else: + return anyio_backend[0] + + +@pytest.fixture +def anyio_backend_options(anyio_backend: Any) -> dict[str, Any]: + if isinstance(anyio_backend, str): + return {} + else: + return anyio_backend[1] + + +class FreePortFactory: + """ + Manages port generation based on specified socket kind, ensuring no duplicate + ports are generated. + + This class provides functionality for generating available free ports on the + system. It is initialized with a specific socket kind and can generate ports + for given address families while avoiding reuse of previously generated ports. + + Users should not instantiate this class directly, but use the + ``free_tcp_port_factory`` and ``free_udp_port_factory`` fixtures instead. For simple + uses cases, ``free_tcp_port`` and ``free_udp_port`` can be used instead. + """ + + def __init__(self, kind: socket.SocketKind) -> None: + self._kind = kind + self._generated = set[int]() + + @property + def kind(self) -> socket.SocketKind: + """ + The type of socket connection (e.g., :data:`~socket.SOCK_STREAM` or + :data:`~socket.SOCK_DGRAM`) used to bind for checking port availability + + """ + return self._kind + + def __call__(self, family: socket.AddressFamily | None = None) -> int: + """ + Return an unbound port for the given address family. + + :param family: if omitted, both IPv4 and IPv6 addresses will be tried + :return: a port number + + """ + if family is not None: + families = [family] + else: + families = [socket.AF_INET] + if socket.has_ipv6: + families.append(socket.AF_INET6) + + while True: + port = 0 + with ExitStack() as stack: + for fam in families: + sock = stack.enter_context(socket.socket(fam, self._kind)) + addr = "::1" if fam == socket.AF_INET6 else "127.0.0.1" + try: + sock.bind((addr, port)) + except OSError: + break + + if not port: + port = sock.getsockname()[1] + else: + if port not in self._generated: + self._generated.add(port) + return port + + +@pytest.fixture(scope="session") +def free_tcp_port_factory() -> FreePortFactory: + return FreePortFactory(socket.SOCK_STREAM) + + +@pytest.fixture(scope="session") +def free_udp_port_factory() -> FreePortFactory: + return FreePortFactory(socket.SOCK_DGRAM) + + +@pytest.fixture +def free_tcp_port(free_tcp_port_factory: Callable[[], int]) -> int: + return free_tcp_port_factory() + + +@pytest.fixture +def free_udp_port(free_udp_port_factory: Callable[[], int]) -> int: + return free_udp_port_factory() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/__init__.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/buffered.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/buffered.py new file mode 100644 index 0000000000000000000000000000000000000000..a3b07f73ac44d5876c239fd0317846564ef06f91 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/buffered.py @@ -0,0 +1,201 @@ +from __future__ import annotations + +__all__ = ( + "BufferedByteReceiveStream", + "BufferedByteStream", + "BufferedConnectable", +) + +import sys +from collections.abc import Callable, Iterable, Mapping +from dataclasses import dataclass, field +from typing import Any, SupportsIndex + +from .. import ClosedResourceError, DelimiterNotFound, EndOfStream, IncompleteRead +from ..abc import ( + AnyByteReceiveStream, + AnyByteStream, + AnyByteStreamConnectable, + ByteReceiveStream, + ByteStream, + ByteStreamConnectable, +) + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + + +@dataclass(eq=False) +class BufferedByteReceiveStream(ByteReceiveStream): + """ + Wraps any bytes-based receive stream and uses a buffer to provide sophisticated + receiving capabilities in the form of a byte stream. + """ + + receive_stream: AnyByteReceiveStream + _buffer: bytearray = field(init=False, default_factory=bytearray) + _closed: bool = field(init=False, default=False) + + async def aclose(self) -> None: + await self.receive_stream.aclose() + self._closed = True + + @property + def buffer(self) -> bytes: + """The bytes currently in the buffer.""" + return bytes(self._buffer) + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return self.receive_stream.extra_attributes + + def feed_data(self, data: Iterable[SupportsIndex], /) -> None: + """ + Append data directly into the buffer. + + Any data in the buffer will be consumed by receive operations before receiving + anything from the wrapped stream. + + :param data: the data to append to the buffer (can be bytes or anything else + that supports ``__index__()``) + + """ + self._buffer.extend(data) + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + if self._closed: + raise ClosedResourceError + + if self._buffer: + chunk = bytes(self._buffer[:max_bytes]) + del self._buffer[:max_bytes] + return chunk + elif isinstance(self.receive_stream, ByteReceiveStream): + return await self.receive_stream.receive(max_bytes) + else: + # With a bytes-oriented object stream, we need to handle any surplus bytes + # we get from the receive() call + chunk = await self.receive_stream.receive() + if len(chunk) > max_bytes: + # Save the surplus bytes in the buffer + self._buffer.extend(chunk[max_bytes:]) + return chunk[:max_bytes] + else: + return chunk + + async def receive_exactly(self, nbytes: int) -> bytes: + """ + Read exactly the given amount of bytes from the stream. + + :param nbytes: the number of bytes to read + :return: the bytes read + :raises ~anyio.IncompleteRead: if the stream was closed before the requested + amount of bytes could be read from the stream + + """ + while True: + remaining = nbytes - len(self._buffer) + if remaining <= 0: + retval = self._buffer[:nbytes] + del self._buffer[:nbytes] + return bytes(retval) + + try: + if isinstance(self.receive_stream, ByteReceiveStream): + chunk = await self.receive_stream.receive(remaining) + else: + chunk = await self.receive_stream.receive() + except EndOfStream as exc: + raise IncompleteRead from exc + + self._buffer.extend(chunk) + + async def receive_until(self, delimiter: bytes, max_bytes: int) -> bytes: + """ + Read from the stream until the delimiter is found or max_bytes have been read. + + :param delimiter: the marker to look for in the stream + :param max_bytes: maximum number of bytes that will be read before raising + :exc:`~anyio.DelimiterNotFound` + :return: the bytes read (not including the delimiter) + :raises ~anyio.IncompleteRead: if the stream was closed before the delimiter + was found + :raises ~anyio.DelimiterNotFound: if the delimiter is not found within the + bytes read up to the maximum allowed + + """ + delimiter_size = len(delimiter) + offset = 0 + while True: + # Check if the delimiter can be found in the current buffer + index = self._buffer.find(delimiter, offset) + if index >= 0: + found = self._buffer[:index] + del self._buffer[: index + len(delimiter) :] + return bytes(found) + + # Check if the buffer is already at or over the limit + if len(self._buffer) >= max_bytes: + raise DelimiterNotFound(max_bytes) + + # Read more data into the buffer from the socket + try: + data = await self.receive_stream.receive() + except EndOfStream as exc: + raise IncompleteRead from exc + + # Move the offset forward and add the new data to the buffer + offset = max(len(self._buffer) - delimiter_size + 1, 0) + self._buffer.extend(data) + + +class BufferedByteStream(BufferedByteReceiveStream, ByteStream): + """ + A full-duplex variant of :class:`BufferedByteReceiveStream`. All writes are passed + through to the wrapped stream as-is. + """ + + def __init__(self, stream: AnyByteStream): + """ + :param stream: the stream to be wrapped + + """ + super().__init__(stream) + self._stream = stream + + @override + async def send_eof(self) -> None: + await self._stream.send_eof() + + @override + async def send(self, item: bytes) -> None: + await self._stream.send(item) + + +class BufferedConnectable(ByteStreamConnectable): + """ + Wraps a byte stream connectable to produce :class:`BufferedByteStream` connections. + + Use this when you want the streams returned by :meth:`connect` to have the buffered + receive API (e.g. :meth:`~BufferedByteReceiveStream.receive_exactly` and + :meth:`~BufferedByteReceiveStream.receive_until`). + + :param connectable: the byte stream connectable to wrap + """ + + def __init__(self, connectable: AnyByteStreamConnectable): + """ + :param connectable: the connectable to wrap + + """ + self.connectable = connectable + + @override + async def connect(self) -> BufferedByteStream: + stream = await self.connectable.connect() + return BufferedByteStream(stream) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/file.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/file.py new file mode 100644 index 0000000000000000000000000000000000000000..b9d7ca4f3341a7ad7e44553e4e15134f223cb750 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/file.py @@ -0,0 +1,157 @@ +from __future__ import annotations + +__all__ = ( + "FileReadStream", + "FileStreamAttribute", + "FileWriteStream", +) + +from collections.abc import Callable, Mapping +from io import SEEK_SET, UnsupportedOperation +from os import PathLike +from pathlib import Path +from typing import IO, Any + +from .. import ( + BrokenResourceError, + ClosedResourceError, + EndOfStream, + TypedAttributeSet, + to_thread, + typed_attribute, +) +from ..abc import ByteReceiveStream, ByteSendStream + + +class FileStreamAttribute(TypedAttributeSet): + #: the open file descriptor + file: IO[bytes] = typed_attribute() + #: the path of the file on the file system, if available (file must be a real file) + path: Path = typed_attribute() + #: the file number, if available (file must be a real file or a TTY) + fileno: int = typed_attribute() + + +class _BaseFileStream: + def __init__(self, file: IO[bytes]): + self._file = file + + async def aclose(self) -> None: + await to_thread.run_sync(self._file.close) + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + attributes: dict[Any, Callable[[], Any]] = { + FileStreamAttribute.file: lambda: self._file, + } + + if hasattr(self._file, "name"): + attributes[FileStreamAttribute.path] = lambda: Path(self._file.name) + + try: + self._file.fileno() + except UnsupportedOperation: + pass + else: + attributes[FileStreamAttribute.fileno] = lambda: self._file.fileno() + + return attributes + + +class FileReadStream(_BaseFileStream, ByteReceiveStream): + """ + A byte stream that reads from a file in the file system. + + :param file: a file that has been opened for reading in binary mode + + .. versionadded:: 3.0 + """ + + @classmethod + async def from_path(cls, path: str | PathLike[str]) -> FileReadStream: + """ + Create a file read stream by opening the given file. + + :param path: path of the file to read from + + """ + file = await to_thread.run_sync(Path(path).open, "rb") + return cls(file) + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + try: + data = await to_thread.run_sync(self._file.read, max_bytes) + except ValueError: + raise ClosedResourceError from None + except OSError as exc: + raise BrokenResourceError from exc + + if data: + return data + else: + raise EndOfStream + + async def seek(self, position: int, whence: int = SEEK_SET) -> int: + """ + Seek the file to the given position. + + .. seealso:: :meth:`io.IOBase.seek` + + .. note:: Not all file descriptors are seekable. + + :param position: position to seek the file to + :param whence: controls how ``position`` is interpreted + :return: the new absolute position + :raises OSError: if the file is not seekable + + """ + return await to_thread.run_sync(self._file.seek, position, whence) + + async def tell(self) -> int: + """ + Return the current stream position. + + .. note:: Not all file descriptors are seekable. + + :return: the current absolute position + :raises OSError: if the file is not seekable + + """ + return await to_thread.run_sync(self._file.tell) + + +class FileWriteStream(_BaseFileStream, ByteSendStream): + """ + A byte stream that writes to a file in the file system. + + :param file: a file that has been opened for writing in binary mode + + .. versionadded:: 3.0 + """ + + @classmethod + async def from_path( + cls, path: str | PathLike[str], append: bool = False + ) -> FileWriteStream: + """ + Create a file write stream by opening the given file for writing. + + :param path: path of the file to write to + :param append: if ``True``, open the file for appending; if ``False``, any + existing file at the given path will be truncated + + """ + mode = "ab" if append else "wb" + file = await to_thread.run_sync(Path(path).open, mode) + return cls(file) + + async def send(self, item: bytes) -> None: + try: + await to_thread.run_sync(self._file.write, item) + except ValueError: + raise ClosedResourceError from None + except OSError as exc: + raise BrokenResourceError from exc diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/memory.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/memory.py new file mode 100644 index 0000000000000000000000000000000000000000..329a75e44b7af979042c54e65529c7148e67cd8d --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/memory.py @@ -0,0 +1,326 @@ +from __future__ import annotations + +__all__ = ( + "MemoryObjectReceiveStream", + "MemoryObjectSendStream", + "MemoryObjectStreamStatistics", +) + +import warnings +from collections import OrderedDict, deque +from dataclasses import dataclass, field +from types import TracebackType +from typing import Generic, NamedTuple, TypeVar + +from .._core._exceptions import ( + BrokenResourceError, + ClosedResourceError, + EndOfStream, + WouldBlock, +) +from .._core._synchronization import Event +from .._core._testing import TaskInfo, get_current_task +from ..abc import ObjectReceiveStream, ObjectSendStream +from ..lowlevel import checkpoint + +T_Item = TypeVar("T_Item") +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + + +class MemoryObjectStreamStatistics(NamedTuple): + current_buffer_used: int #: number of items stored in the buffer + #: maximum number of items that can be stored on this stream (or :data:`math.inf`) + max_buffer_size: float + open_send_streams: int #: number of unclosed clones of the send stream + open_receive_streams: int #: number of unclosed clones of the receive stream + #: number of tasks blocked on :meth:`MemoryObjectSendStream.send` + tasks_waiting_send: int + #: number of tasks blocked on :meth:`MemoryObjectReceiveStream.receive` + tasks_waiting_receive: int + + +@dataclass(eq=False) +class _MemoryObjectItemReceiver(Generic[T_Item]): + task_info: TaskInfo = field(init=False, default_factory=get_current_task) + item: T_Item = field(init=False) + + def __repr__(self) -> str: + # When item is not defined, we get following error with default __repr__: + # AttributeError: 'MemoryObjectItemReceiver' object has no attribute 'item' + item = getattr(self, "item", None) + return f"{self.__class__.__name__}(task_info={self.task_info}, item={item!r})" + + +@dataclass(eq=False) +class _MemoryObjectStreamState(Generic[T_Item]): + max_buffer_size: float = field() + buffer: deque[T_Item] = field(init=False, default_factory=deque) + open_send_channels: int = field(init=False, default=0) + open_receive_channels: int = field(init=False, default=0) + waiting_receivers: OrderedDict[Event, _MemoryObjectItemReceiver[T_Item]] = field( + init=False, default_factory=OrderedDict + ) + waiting_senders: OrderedDict[Event, T_Item] = field( + init=False, default_factory=OrderedDict + ) + + def statistics(self) -> MemoryObjectStreamStatistics: + return MemoryObjectStreamStatistics( + len(self.buffer), + self.max_buffer_size, + self.open_send_channels, + self.open_receive_channels, + len(self.waiting_senders), + len(self.waiting_receivers), + ) + + +@dataclass(eq=False) +class MemoryObjectReceiveStream(ObjectReceiveStream[T_co], Generic[T_co]): + _state: _MemoryObjectStreamState[T_co] + _closed: bool = field(init=False, default=False) + + def __post_init__(self) -> None: + self._state.open_receive_channels += 1 + + def receive_nowait(self) -> T_co: + """ + Receive the next item if it can be done without waiting. + + :return: the received item + :raises ~anyio.ClosedResourceError: if this send stream has been closed + :raises ~anyio.EndOfStream: if the buffer is empty and this stream has been + closed from the sending end + :raises ~anyio.WouldBlock: if there are no items in the buffer and no tasks + waiting to send + + """ + if self._closed: + raise ClosedResourceError + + if self._state.waiting_senders: + # Get the item from the next sender + send_event, item = self._state.waiting_senders.popitem(last=False) + self._state.buffer.append(item) + send_event.set() + + if self._state.buffer: + return self._state.buffer.popleft() + elif not self._state.open_send_channels: + raise EndOfStream + + raise WouldBlock + + async def receive(self) -> T_co: + await checkpoint() + try: + return self.receive_nowait() + except WouldBlock: + # Add ourselves in the queue + receive_event = Event() + receiver = _MemoryObjectItemReceiver[T_co]() + self._state.waiting_receivers[receive_event] = receiver + + try: + await receive_event.wait() + finally: + self._state.waiting_receivers.pop(receive_event, None) + + try: + return receiver.item + except AttributeError: + raise EndOfStream from None + + def clone(self) -> MemoryObjectReceiveStream[T_co]: + """ + Create a clone of this receive stream. + + Each clone can be closed separately. Only when all clones have been closed will + the receiving end of the memory stream be considered closed by the sending ends. + + :return: the cloned stream + + """ + if self._closed: + raise ClosedResourceError + + return MemoryObjectReceiveStream(_state=self._state) + + def close(self) -> None: + """ + Close the stream. + + This works the exact same way as :meth:`aclose`, but is provided as a special + case for the benefit of synchronous callbacks. + + """ + if not self._closed: + self._closed = True + self._state.open_receive_channels -= 1 + if self._state.open_receive_channels == 0: + send_events = list(self._state.waiting_senders.keys()) + for event in send_events: + event.set() + + async def aclose(self) -> None: + self.close() + + def statistics(self) -> MemoryObjectStreamStatistics: + """ + Return statistics about the current state of this stream. + + .. versionadded:: 3.0 + """ + return self._state.statistics() + + def __enter__(self) -> MemoryObjectReceiveStream[T_co]: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def __del__(self) -> None: + if not self._closed: + warnings.warn( + f"Unclosed <{self.__class__.__name__} at {id(self):x}>", + ResourceWarning, + stacklevel=1, + source=self, + ) + + +@dataclass(eq=False) +class MemoryObjectSendStream(ObjectSendStream[T_contra], Generic[T_contra]): + _state: _MemoryObjectStreamState[T_contra] + _closed: bool = field(init=False, default=False) + + def __post_init__(self) -> None: + self._state.open_send_channels += 1 + + def send_nowait(self, item: T_contra) -> None: + """ + Send an item immediately if it can be done without waiting. + + :param item: the item to send + :raises ~anyio.ClosedResourceError: if this send stream has been closed + :raises ~anyio.BrokenResourceError: if the stream has been closed from the + receiving end + :raises ~anyio.WouldBlock: if the buffer is full and there are no tasks waiting + to receive + + """ + if self._closed: + raise ClosedResourceError + if not self._state.open_receive_channels: + raise BrokenResourceError + + while self._state.waiting_receivers: + receive_event, receiver = self._state.waiting_receivers.popitem(last=False) + if not receiver.task_info.has_pending_cancellation(): + receiver.item = item + receive_event.set() + return + + if len(self._state.buffer) < self._state.max_buffer_size: + self._state.buffer.append(item) + else: + raise WouldBlock + + async def send(self, item: T_contra) -> None: + """ + Send an item to the stream. + + If the buffer is full, this method blocks until there is again room in the + buffer or the item can be sent directly to a receiver. + + :param item: the item to send + :raises ~anyio.ClosedResourceError: if this send stream has been closed + :raises ~anyio.BrokenResourceError: if the stream has been closed from the + receiving end + + """ + await checkpoint() + try: + self.send_nowait(item) + except WouldBlock: + # Wait until there's someone on the receiving end + send_event = Event() + self._state.waiting_senders[send_event] = item + try: + await send_event.wait() + except BaseException: + self._state.waiting_senders.pop(send_event, None) + raise + + if send_event in self._state.waiting_senders: + del self._state.waiting_senders[send_event] + raise BrokenResourceError from None + + def clone(self) -> MemoryObjectSendStream[T_contra]: + """ + Create a clone of this send stream. + + Each clone can be closed separately. Only when all clones have been closed will + the sending end of the memory stream be considered closed by the receiving ends. + + :return: the cloned stream + + """ + if self._closed: + raise ClosedResourceError + + return MemoryObjectSendStream(_state=self._state) + + def close(self) -> None: + """ + Close the stream. + + This works the exact same way as :meth:`aclose`, but is provided as a special + case for the benefit of synchronous callbacks. + + """ + if not self._closed: + self._closed = True + self._state.open_send_channels -= 1 + if self._state.open_send_channels == 0: + receive_events = list(self._state.waiting_receivers.keys()) + self._state.waiting_receivers.clear() + for event in receive_events: + event.set() + + async def aclose(self) -> None: + self.close() + + def statistics(self) -> MemoryObjectStreamStatistics: + """ + Return statistics about the current state of this stream. + + .. versionadded:: 3.0 + """ + return self._state.statistics() + + def __enter__(self) -> MemoryObjectSendStream[T_contra]: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def __del__(self) -> None: + if not self._closed: + warnings.warn( + f"Unclosed <{self.__class__.__name__} at {id(self):x}>", + ResourceWarning, + stacklevel=1, + source=self, + ) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/stapled.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/stapled.py new file mode 100644 index 0000000000000000000000000000000000000000..9a7534563b1aa9613516cd468daf6e68b4fa8a71 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/stapled.py @@ -0,0 +1,160 @@ +from __future__ import annotations + +__all__ = ( + "MultiListener", + "StapledByteStream", + "StapledObjectStream", +) + +from collections.abc import Callable, Mapping, Sequence +from dataclasses import dataclass +from typing import Any, Generic, TypeVar + +from ..abc import ( + ByteReceiveStream, + ByteSendStream, + ByteStream, + Listener, + ObjectReceiveStream, + ObjectSendStream, + ObjectStream, + TaskGroup, +) + +T_Item = TypeVar("T_Item") +T_Stream = TypeVar("T_Stream") + + +@dataclass(eq=False) +class StapledByteStream(ByteStream): + """ + Combines two byte streams into a single, bidirectional byte stream. + + Extra attributes will be provided from both streams, with the receive stream + providing the values in case of a conflict. + + :param ByteSendStream send_stream: the sending byte stream + :param ByteReceiveStream receive_stream: the receiving byte stream + """ + + send_stream: ByteSendStream + receive_stream: ByteReceiveStream + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + return await self.receive_stream.receive(max_bytes) + + async def send(self, item: bytes) -> None: + await self.send_stream.send(item) + + async def send_eof(self) -> None: + await self.send_stream.aclose() + + async def aclose(self) -> None: + await self.send_stream.aclose() + await self.receive_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self.send_stream.extra_attributes, + **self.receive_stream.extra_attributes, + } + + +@dataclass(eq=False) +class StapledObjectStream(ObjectStream[T_Item], Generic[T_Item]): + """ + Combines two object streams into a single, bidirectional object stream. + + Extra attributes will be provided from both streams, with the receive stream + providing the values in case of a conflict. + + :param ObjectSendStream send_stream: the sending object stream + :param ObjectReceiveStream receive_stream: the receiving object stream + """ + + send_stream: ObjectSendStream[T_Item] + receive_stream: ObjectReceiveStream[T_Item] + + async def receive(self) -> T_Item: + return await self.receive_stream.receive() + + async def send(self, item: T_Item) -> None: + await self.send_stream.send(item) + + def send_nowait(self, item: T_Item) -> None: + try: + send_nowait = self.send_stream.send_nowait # type: ignore[attr-defined] + except AttributeError as exc: + raise NotImplementedError( + f"'send_nowait' method not implemented in {type(self.send_stream)}" + ) from exc + + send_nowait(item) + + async def send_eof(self) -> None: + await self.send_stream.aclose() + + async def aclose(self) -> None: + await self.send_stream.aclose() + await self.receive_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self.send_stream.extra_attributes, + **self.receive_stream.extra_attributes, + } + + +@dataclass(eq=False) +class MultiListener(Listener[T_Stream], Generic[T_Stream]): + """ + Combines multiple listeners into one, serving connections from all of them at once. + + Any MultiListeners in the given collection of listeners will have their listeners + moved into this one. + + Extra attributes are provided from each listener, with each successive listener + overriding any conflicting attributes from the previous one. + + :param listeners: listeners to serve + :type listeners: Sequence[Listener[T_Stream]] + """ + + listeners: Sequence[Listener[T_Stream]] + + def __post_init__(self) -> None: + listeners: list[Listener[T_Stream]] = [] + for listener in self.listeners: + if isinstance(listener, MultiListener): + listeners.extend(listener.listeners) + del listener.listeners[:] # type: ignore[attr-defined] + else: + listeners.append(listener) + + self.listeners = listeners + + async def serve( + self, handler: Callable[[T_Stream], Any], task_group: TaskGroup | None = None + ) -> None: + from .. import create_task_group + + async with create_task_group() as tg: + for listener in self.listeners: + tg.start_soon(listener.serve, handler, task_group) + + async def aclose(self) -> None: + for listener in self.listeners: + await listener.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + attributes: dict = {} + for listener in self.listeners: + attributes.update(listener.extra_attributes) + + return attributes diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/text.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/text.py new file mode 100644 index 0000000000000000000000000000000000000000..296cd250459f3848bb333301fff1ac32973f219a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/text.py @@ -0,0 +1,176 @@ +from __future__ import annotations + +__all__ = ( + "TextConnectable", + "TextReceiveStream", + "TextSendStream", + "TextStream", +) + +import codecs +import sys +from collections.abc import Callable, Mapping +from dataclasses import InitVar, dataclass, field +from typing import Any + +from ..abc import ( + AnyByteReceiveStream, + AnyByteSendStream, + AnyByteStream, + AnyByteStreamConnectable, + ObjectReceiveStream, + ObjectSendStream, + ObjectStream, + ObjectStreamConnectable, +) + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + + +@dataclass(eq=False) +class TextReceiveStream(ObjectReceiveStream[str]): + """ + Stream wrapper that decodes bytes to strings using the given encoding. + + Decoding is done using :class:`~codecs.IncrementalDecoder` which returns any + completely received unicode characters as soon as they come in. + + :param transport_stream: any bytes-based receive stream + :param encoding: character encoding to use for decoding bytes to strings (defaults + to ``utf-8``) + :param errors: handling scheme for decoding errors (defaults to ``strict``; see the + `codecs module documentation`_ for a comprehensive list of options) + + .. _codecs module documentation: + https://docs.python.org/3/library/codecs.html#codec-objects + """ + + transport_stream: AnyByteReceiveStream + encoding: InitVar[str] = "utf-8" + errors: InitVar[str] = "strict" + _decoder: codecs.IncrementalDecoder = field(init=False) + + def __post_init__(self, encoding: str, errors: str) -> None: + decoder_class = codecs.getincrementaldecoder(encoding) + self._decoder = decoder_class(errors=errors) + + async def receive(self) -> str: + while True: + chunk = await self.transport_stream.receive() + decoded = self._decoder.decode(chunk) + if decoded: + return decoded + + async def aclose(self) -> None: + await self.transport_stream.aclose() + self._decoder.reset() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return self.transport_stream.extra_attributes + + +@dataclass(eq=False) +class TextSendStream(ObjectSendStream[str]): + """ + Sends strings to the wrapped stream as bytes using the given encoding. + + :param AnyByteSendStream transport_stream: any bytes-based send stream + :param str encoding: character encoding to use for encoding strings to bytes + (defaults to ``utf-8``) + :param str errors: handling scheme for encoding errors (defaults to ``strict``; see + the `codecs module documentation`_ for a comprehensive list of options) + + .. _codecs module documentation: + https://docs.python.org/3/library/codecs.html#codec-objects + """ + + transport_stream: AnyByteSendStream + encoding: InitVar[str] = "utf-8" + errors: str = "strict" + _encoder: Callable[..., tuple[bytes, int]] = field(init=False) + + def __post_init__(self, encoding: str) -> None: + self._encoder = codecs.getencoder(encoding) + + async def send(self, item: str) -> None: + encoded = self._encoder(item, self.errors)[0] + await self.transport_stream.send(encoded) + + async def aclose(self) -> None: + await self.transport_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return self.transport_stream.extra_attributes + + +@dataclass(eq=False) +class TextStream(ObjectStream[str]): + """ + A bidirectional stream that decodes bytes to strings on receive and encodes strings + to bytes on send. + + Extra attributes will be provided from both streams, with the receive stream + providing the values in case of a conflict. + + :param AnyByteStream transport_stream: any bytes-based stream + :param str encoding: character encoding to use for encoding/decoding strings to/from + bytes (defaults to ``utf-8``) + :param str errors: handling scheme for encoding errors (defaults to ``strict``; see + the `codecs module documentation`_ for a comprehensive list of options) + + .. _codecs module documentation: + https://docs.python.org/3/library/codecs.html#codec-objects + """ + + transport_stream: AnyByteStream + encoding: InitVar[str] = "utf-8" + errors: InitVar[str] = "strict" + _receive_stream: TextReceiveStream = field(init=False) + _send_stream: TextSendStream = field(init=False) + + def __post_init__(self, encoding: str, errors: str) -> None: + self._receive_stream = TextReceiveStream( + self.transport_stream, encoding=encoding, errors=errors + ) + self._send_stream = TextSendStream( + self.transport_stream, encoding=encoding, errors=errors + ) + + async def receive(self) -> str: + return await self._receive_stream.receive() + + async def send(self, item: str) -> None: + await self._send_stream.send(item) + + async def send_eof(self) -> None: + await self.transport_stream.send_eof() + + async def aclose(self) -> None: + await self._send_stream.aclose() + await self._receive_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self._send_stream.extra_attributes, + **self._receive_stream.extra_attributes, + } + + +class TextConnectable(ObjectStreamConnectable[str]): + def __init__(self, connectable: AnyByteStreamConnectable): + """ + :param connectable: the bytestream endpoint to wrap + + """ + self.connectable = connectable + + @override + async def connect(self) -> TextStream: + stream = await self.connectable.connect() + return TextStream(stream) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/tls.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/tls.py new file mode 100644 index 0000000000000000000000000000000000000000..b9ddf1d333143767b737bbd5915bebe1309481b2 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/streams/tls.py @@ -0,0 +1,436 @@ +from __future__ import annotations + +__all__ = ( + "TLSAttribute", + "TLSConnectable", + "TLSListener", + "TLSStream", +) + +import logging +import re +import ssl +import sys +from collections.abc import Callable, Mapping +from dataclasses import dataclass +from functools import wraps +from ssl import SSLContext +from typing import Any, TypeAlias, TypeVar + +from .. import ( + BrokenResourceError, + EndOfStream, + aclose_forcefully, + get_cancelled_exc_class, + to_thread, +) +from .._core._typedattr import TypedAttributeSet, typed_attribute +from ..abc import ( + AnyByteStream, + AnyByteStreamConnectable, + ByteStream, + ByteStreamConnectable, + Listener, + TaskGroup, +) + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") +_PCTRTT: TypeAlias = tuple[tuple[str, str], ...] +_PCTRTTT: TypeAlias = tuple[_PCTRTT, ...] + + +class TLSAttribute(TypedAttributeSet): + """Contains Transport Layer Security related attributes.""" + + #: the selected ALPN protocol + alpn_protocol: str | None = typed_attribute() + #: the channel binding for type ``tls-unique`` + channel_binding_tls_unique: bytes = typed_attribute() + #: the selected cipher + cipher: tuple[str, str, int] = typed_attribute() + #: the peer certificate in dictionary form (see :meth:`ssl.SSLSocket.getpeercert` + # for more information) + peer_certificate: None | (dict[str, str | _PCTRTTT | _PCTRTT]) = typed_attribute() + #: the peer certificate in binary form + peer_certificate_binary: bytes | None = typed_attribute() + #: ``True`` if this is the server side of the connection + server_side: bool = typed_attribute() + #: ciphers shared by the client during the TLS handshake (``None`` if this is the + #: client side) + shared_ciphers: list[tuple[str, str, int]] | None = typed_attribute() + #: the :class:`~ssl.SSLObject` used for encryption + ssl_object: ssl.SSLObject = typed_attribute() + #: ``True`` if this stream does (and expects) a closing TLS handshake when the + #: stream is being closed + standard_compatible: bool = typed_attribute() + #: the TLS protocol version (e.g. ``TLSv1.2``) + tls_version: str = typed_attribute() + + +@dataclass(eq=False) +class TLSStream(ByteStream): + """ + A stream wrapper that encrypts all sent data and decrypts received data. + + This class has no public initializer; use :meth:`wrap` instead. + All extra attributes from :class:`~TLSAttribute` are supported. + + :var AnyByteStream transport_stream: the wrapped stream + + """ + + transport_stream: AnyByteStream + standard_compatible: bool + _ssl_object: ssl.SSLObject + _read_bio: ssl.MemoryBIO + _write_bio: ssl.MemoryBIO + + @classmethod + async def wrap( + cls, + transport_stream: AnyByteStream, + *, + server_side: bool | None = None, + hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + standard_compatible: bool = True, + ) -> TLSStream: + """ + Wrap an existing stream with Transport Layer Security. + + This performs a TLS handshake with the peer. + + :param transport_stream: a bytes-transporting stream to wrap + :param server_side: ``True`` if this is the server side of the connection, + ``False`` if this is the client side (if omitted, will be set to ``False`` + if ``hostname`` has been provided, ``False`` otherwise). Used only to create + a default context when an explicit context has not been provided. + :param hostname: host name of the peer (if host name checking is desired) + :param ssl_context: the SSLContext object to use (if not provided, a secure + default will be created) + :param standard_compatible: if ``False``, skip the closing handshake when + closing the connection, and don't raise an exception if the peer does the + same + :raises ~ssl.SSLError: if the TLS handshake fails + + """ + if server_side is None: + server_side = not hostname + + if not ssl_context: + purpose = ( + ssl.Purpose.CLIENT_AUTH if server_side else ssl.Purpose.SERVER_AUTH + ) + ssl_context = ssl.create_default_context(purpose) + + # Re-enable detection of unexpected EOFs if it was disabled by Python + if hasattr(ssl, "OP_IGNORE_UNEXPECTED_EOF"): + ssl_context.options &= ~ssl.OP_IGNORE_UNEXPECTED_EOF + + bio_in = ssl.MemoryBIO() + bio_out = ssl.MemoryBIO() + + # Resolve international host names using IDNA 2008. + # Otherwise wrap_bio() would resolve them with IDNA 2003. + if hostname is not None: + from .._core._sockets import idna2008_resolve + + server_hostname: bytes | None = idna2008_resolve(hostname) + else: + server_hostname = None + + # External SSLContext implementations may do blocking I/O in wrap_bio(), + # but the standard library implementation won't + if type(ssl_context) is ssl.SSLContext: + ssl_object = ssl_context.wrap_bio( + bio_in, + bio_out, + server_side=server_side, + server_hostname=server_hostname, + ) + else: + ssl_object = await to_thread.run_sync( + ssl_context.wrap_bio, + bio_in, + bio_out, + server_side, + server_hostname, + None, + ) + + wrapper = cls( + transport_stream=transport_stream, + standard_compatible=standard_compatible, + _ssl_object=ssl_object, + _read_bio=bio_in, + _write_bio=bio_out, + ) + await wrapper._call_sslobject_method(ssl_object.do_handshake) + return wrapper + + async def _call_sslobject_method( + self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT] + ) -> T_Retval: + while True: + try: + result = func(*args) + except ssl.SSLWantReadError: + try: + # Flush any pending writes first + if self._write_bio.pending: + await self.transport_stream.send(self._write_bio.read()) + + data = await self.transport_stream.receive() + except EndOfStream: + self._read_bio.write_eof() + except OSError as exc: + self._read_bio.write_eof() + self._write_bio.write_eof() + raise BrokenResourceError from exc + else: + self._read_bio.write(data) + except ssl.SSLWantWriteError: + await self.transport_stream.send(self._write_bio.read()) + except ssl.SSLSyscallError as exc: + self._read_bio.write_eof() + self._write_bio.write_eof() + raise BrokenResourceError from exc + except ssl.SSLError as exc: + self._read_bio.write_eof() + self._write_bio.write_eof() + if isinstance(exc, ssl.SSLEOFError) or ( + exc.strerror and "UNEXPECTED_EOF_WHILE_READING" in exc.strerror + ): + if self.standard_compatible: + raise BrokenResourceError from exc + else: + raise EndOfStream from None + + raise + else: + # Flush any pending writes first + if self._write_bio.pending: + await self.transport_stream.send(self._write_bio.read()) + + return result + + async def unwrap(self) -> tuple[AnyByteStream, bytes]: + """ + Does the TLS closing handshake. + + :return: a tuple of (wrapped byte stream, bytes left in the read buffer) + + """ + await self._call_sslobject_method(self._ssl_object.unwrap) + self._read_bio.write_eof() + self._write_bio.write_eof() + return self.transport_stream, self._read_bio.read() + + async def aclose(self) -> None: + if self.standard_compatible: + try: + await self.unwrap() + except BaseException: + await aclose_forcefully(self.transport_stream) + raise + + await self.transport_stream.aclose() + + async def receive(self, max_bytes: int = 65536) -> bytes: + if max_bytes < 1: + raise ValueError("max_bytes must be a positive integer") + + data = await self._call_sslobject_method(self._ssl_object.read, max_bytes) + if not data: + raise EndOfStream + + return data + + async def send(self, item: bytes) -> None: + await self._call_sslobject_method(self._ssl_object.write, item) + + async def send_eof(self) -> None: + tls_version = self.extra(TLSAttribute.tls_version) + match = re.match(r"TLSv(\d+)(?:\.(\d+))?", tls_version) + if match: + major, minor = int(match.group(1)), int(match.group(2) or 0) + if (major, minor) < (1, 3): + raise NotImplementedError( + f"send_eof() requires at least TLSv1.3; current " + f"session uses {tls_version}" + ) + + raise NotImplementedError( + "send_eof() has not yet been implemented for TLS streams" + ) + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self.transport_stream.extra_attributes, + TLSAttribute.alpn_protocol: self._ssl_object.selected_alpn_protocol, + TLSAttribute.channel_binding_tls_unique: ( + self._ssl_object.get_channel_binding + ), + TLSAttribute.cipher: self._ssl_object.cipher, + TLSAttribute.peer_certificate: lambda: self._ssl_object.getpeercert(False), + TLSAttribute.peer_certificate_binary: lambda: self._ssl_object.getpeercert( + True + ), + TLSAttribute.server_side: lambda: self._ssl_object.server_side, + TLSAttribute.shared_ciphers: lambda: ( + self._ssl_object.shared_ciphers() + if self._ssl_object.server_side + else None + ), + TLSAttribute.standard_compatible: lambda: self.standard_compatible, + TLSAttribute.ssl_object: lambda: self._ssl_object, + TLSAttribute.tls_version: self._ssl_object.version, + } + + +@dataclass(eq=False) +class TLSListener(Listener[TLSStream]): + """ + A convenience listener that wraps another listener and auto-negotiates a TLS session + on every accepted connection. + + If the TLS handshake times out or raises an exception, + :meth:`handle_handshake_error` is called to do whatever post-mortem processing is + deemed necessary. + + Supports only the :attr:`~TLSAttribute.standard_compatible` extra attribute. + + :param Listener listener: the listener to wrap + :param ssl_context: the SSL context object + :param standard_compatible: a flag passed through to :meth:`TLSStream.wrap` + :param handshake_timeout: time limit for the TLS handshake + (passed to :func:`~anyio.fail_after`) + """ + + listener: Listener[Any] + ssl_context: ssl.SSLContext + standard_compatible: bool = True + handshake_timeout: float = 30 + + @staticmethod + async def handle_handshake_error(exc: BaseException, stream: AnyByteStream) -> None: + """ + Handle an exception raised during the TLS handshake. + + This method does 3 things: + + #. Forcefully closes the original stream + #. Logs the exception (unless it was a cancellation exception) using the + ``anyio.streams.tls`` logger + #. Reraises the exception if it was a base exception or a cancellation exception + + :param exc: the exception + :param stream: the original stream + + """ + await aclose_forcefully(stream) + + # Log all except cancellation exceptions + if not isinstance(exc, get_cancelled_exc_class()): + # CPython (as of 3.11.5) returns incorrect `sys.exc_info()` here when using + # any asyncio implementation, so we explicitly pass the exception to log + # (https://github.com/python/cpython/issues/108668). Trio does not have this + # issue because it works around the CPython bug. + logging.getLogger(__name__).exception( + "Error during TLS handshake", exc_info=exc + ) + + # Only reraise base exceptions and cancellation exceptions + if not isinstance(exc, Exception) or isinstance(exc, get_cancelled_exc_class()): + raise # noqa: PLE0704 + + async def serve( + self, + handler: Callable[[TLSStream], Any], + task_group: TaskGroup | None = None, + ) -> None: + @wraps(handler) + async def handler_wrapper(stream: AnyByteStream) -> None: + from .. import fail_after + + try: + with fail_after(self.handshake_timeout): + wrapped_stream = await TLSStream.wrap( + stream, + ssl_context=self.ssl_context, + standard_compatible=self.standard_compatible, + ) + except BaseException as exc: + await self.handle_handshake_error(exc, stream) + else: + await handler(wrapped_stream) + + await self.listener.serve(handler_wrapper, task_group) + + async def aclose(self) -> None: + await self.listener.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + TLSAttribute.standard_compatible: lambda: self.standard_compatible, + } + + +class TLSConnectable(ByteStreamConnectable): + """ + Wraps another connectable and does TLS negotiation after a successful connection. + + :param connectable: the connectable to wrap + :param hostname: host name of the server (if host name checking is desired) + :param ssl_context: the SSLContext object to use (if not provided, a secure default + will be created) + :param standard_compatible: if ``False``, skip the closing handshake when closing + the connection, and don't raise an exception if the server does the same + """ + + def __init__( + self, + connectable: AnyByteStreamConnectable, + *, + hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + standard_compatible: bool = True, + ) -> None: + self.connectable = connectable + self.ssl_context: SSLContext = ssl_context or ssl.create_default_context( + ssl.Purpose.SERVER_AUTH + ) + if not isinstance(self.ssl_context, ssl.SSLContext): + raise TypeError( + "ssl_context must be an instance of ssl.SSLContext, not " + f"{type(self.ssl_context).__name__}" + ) + self.hostname = hostname + self.standard_compatible = standard_compatible + + @override + async def connect(self) -> TLSStream: + stream = await self.connectable.connect() + try: + return await TLSStream.wrap( + stream, + hostname=self.hostname, + ssl_context=self.ssl_context, + standard_compatible=self.standard_compatible, + ) + except BaseException: + await aclose_forcefully(stream) + raise diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/to_interpreter.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/to_interpreter.py new file mode 100644 index 0000000000000000000000000000000000000000..54ef4a8b8c58db9ea05e4188cf6d7899c8ff8c72 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/to_interpreter.py @@ -0,0 +1,246 @@ +from __future__ import annotations + +__all__ = ( + "current_default_interpreter_limiter", + "run_sync", +) + +import atexit +import os +import sys +from collections import deque +from collections.abc import Callable +from typing import Any, Final, TypeVar + +from . import current_time, to_thread +from ._core._exceptions import BrokenWorkerInterpreter +from ._core._synchronization import CapacityLimiter +from .lowlevel import RunVar + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if sys.version_info >= (3, 14): + from concurrent.interpreters import ExecutionFailed, create + + def _interp_call( + func: Callable[..., Any], args: tuple[Any, ...] + ) -> tuple[Any, bool]: + try: + retval = func(*args) + except BaseException as exc: + return exc, True + else: + return retval, False + + class _Worker: + last_used: float = 0 + + def __init__(self) -> None: + self._interpreter = create() + + def destroy(self) -> None: + self._interpreter.close() + + def call( + self, + func: Callable[..., T_Retval], + args: tuple[Any, ...], + ) -> T_Retval: + try: + res, is_exception = self._interpreter.call(_interp_call, func, args) + except ExecutionFailed as exc: + raise BrokenWorkerInterpreter(exc.excinfo) from exc + + if is_exception: + raise res + + return res +elif sys.version_info >= (3, 13): + import _interpqueues + import _interpreters + + UNBOUND: Final = 2 # I have no clue how this works, but it was used in the stdlib + FMT_UNPICKLED: Final = 0 + FMT_PICKLED: Final = 1 + QUEUE_PICKLE_ARGS: Final = (FMT_PICKLED, UNBOUND) + QUEUE_UNPICKLE_ARGS: Final = (FMT_UNPICKLED, UNBOUND) + + _run_func = compile( + """ +import _interpqueues +from _interpreters import NotShareableError +from pickle import loads, dumps, HIGHEST_PROTOCOL + +QUEUE_PICKLE_ARGS = (1, 2) +QUEUE_UNPICKLE_ARGS = (0, 2) + +item = _interpqueues.get(queue_id)[0] +try: + func, args = loads(item) + retval = func(*args) +except BaseException as exc: + is_exception = True + retval = exc +else: + is_exception = False + +try: + _interpqueues.put(queue_id, (retval, is_exception), *QUEUE_UNPICKLE_ARGS) +except NotShareableError: + retval = dumps(retval, HIGHEST_PROTOCOL) + _interpqueues.put(queue_id, (retval, is_exception), *QUEUE_PICKLE_ARGS) + """, + "", + "exec", + ) + + class _Worker: + last_used: float = 0 + + def __init__(self) -> None: + self._interpreter_id = _interpreters.create() + self._queue_id = _interpqueues.create(1, *QUEUE_UNPICKLE_ARGS) + _interpreters.set___main___attrs( + self._interpreter_id, {"queue_id": self._queue_id} + ) + + def destroy(self) -> None: + _interpqueues.destroy(self._queue_id) + _interpreters.destroy(self._interpreter_id) + + def call( + self, + func: Callable[..., T_Retval], + args: tuple[Any, ...], + ) -> T_Retval: + import pickle + + item = pickle.dumps((func, args), pickle.HIGHEST_PROTOCOL) + _interpqueues.put(self._queue_id, item, *QUEUE_PICKLE_ARGS) + exc_info = _interpreters.exec(self._interpreter_id, _run_func) + if exc_info: + raise BrokenWorkerInterpreter(exc_info) + + res = _interpqueues.get(self._queue_id) + (res, is_exception), fmt = res[:2] + if fmt == FMT_PICKLED: + res = pickle.loads(res) + + if is_exception: + raise res + + return res +else: + + class _Worker: + last_used: float = 0 + + def __init__(self) -> None: + raise RuntimeError("subinterpreters require at least Python 3.13") + + def call( + self, + func: Callable[..., T_Retval], + args: tuple[Any, ...], + ) -> T_Retval: + raise NotImplementedError + + def destroy(self) -> None: + pass + + +DEFAULT_CPU_COUNT: Final = 8 # this is just an arbitrarily selected value +MAX_WORKER_IDLE_TIME = ( + 30 # seconds a subinterpreter can be idle before becoming eligible for pruning +) + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + +_idle_workers = RunVar[deque[_Worker]]("_available_workers") +_default_interpreter_limiter = RunVar[CapacityLimiter]("_default_interpreter_limiter") + + +def _stop_workers(workers: deque[_Worker]) -> None: + for worker in workers: + worker.destroy() + + workers.clear() + + +async def run_sync( + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + limiter: CapacityLimiter | None = None, +) -> T_Retval: + """ + Call the given function with the given arguments in a subinterpreter. + + .. warning:: On Python 3.13, the :mod:`concurrent.interpreters` module was not yet + available, so the code path for that Python version relies on an undocumented, + private API. As such, it is recommended to not rely on this function for anything + mission-critical on Python 3.13. + + :param func: a callable + :param args: the positional arguments for the callable + :param limiter: capacity limiter to use to limit the total number of subinterpreters + running (if omitted, the default limiter is used) + :return: the result of the call + :raises BrokenWorkerInterpreter: if there's an internal error in a subinterpreter + + """ + if limiter is None: + limiter = current_default_interpreter_limiter() + + try: + idle_workers = _idle_workers.get() + except LookupError: + idle_workers = deque() + _idle_workers.set(idle_workers) + atexit.register(_stop_workers, idle_workers) + + async with limiter: + try: + worker = idle_workers.pop() + except IndexError: + worker = _Worker() + + try: + return await to_thread.run_sync( + worker.call, + func, + args, + limiter=limiter, + ) + finally: + # Prune workers that have been idle for too long + now = current_time() + while idle_workers: + if now - idle_workers[0].last_used <= MAX_WORKER_IDLE_TIME: + break + + await to_thread.run_sync(idle_workers.popleft().destroy, limiter=limiter) + + worker.last_used = current_time() + idle_workers.append(worker) + + +def current_default_interpreter_limiter() -> CapacityLimiter: + """ + Return the capacity limiter used by default to limit the number of concurrently + running subinterpreters. + + Defaults to the number of CPU cores. + + :return: a capacity limiter object + + """ + try: + return _default_interpreter_limiter.get() + except LookupError: + limiter = CapacityLimiter(os.cpu_count() or DEFAULT_CPU_COUNT) + _default_interpreter_limiter.set(limiter) + return limiter diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/to_process.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/to_process.py new file mode 100644 index 0000000000000000000000000000000000000000..584128ec3b2d4bbd0a9634fc7f2f39caf1fe968d --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/to_process.py @@ -0,0 +1,269 @@ +from __future__ import annotations + +__all__ = ( + "current_default_process_limiter", + "process_worker", + "run_sync", +) + +import os +import pickle +import runpy +import subprocess +import sys +from collections import deque +from collections.abc import Callable +from types import ModuleType +from typing import TypeVar, cast + +from ._core._eventloop import current_time, get_async_backend, get_cancelled_exc_class +from ._core._exceptions import BrokenWorkerProcess +from ._core._subprocesses import open_process +from ._core._synchronization import CapacityLimiter +from ._core._tasks import CancelScope, fail_after +from .abc import ByteReceiveStream, ByteSendStream, Process +from .lowlevel import RunVar, checkpoint_if_cancelled +from .streams.buffered import BufferedByteReceiveStream + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +WORKER_MAX_IDLE_TIME = 300 # 5 minutes + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + +_process_pool_workers: RunVar[set[Process]] = RunVar("_process_pool_workers") +_process_pool_idle_workers: RunVar[deque[tuple[Process, float]]] = RunVar( + "_process_pool_idle_workers" +) +_default_process_limiter: RunVar[CapacityLimiter] = RunVar("_default_process_limiter") + + +async def run_sync( # type: ignore[return] + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + cancellable: bool = False, + limiter: CapacityLimiter | None = None, +) -> T_Retval: + """ + Call the given function with the given arguments in a worker process. + + If the ``cancellable`` option is enabled and the task waiting for its completion is + cancelled, the worker process running it will be abruptly terminated using SIGKILL + (or ``terminateProcess()`` on Windows). + + :param func: a callable + :param args: positional arguments for the callable + :param cancellable: ``True`` to allow cancellation of the operation while it's + running + :param limiter: capacity limiter to use to limit the total amount of processes + running (if omitted, the default limiter is used) + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + :return: an awaitable that yields the return value of the function. + + """ + + async def send_raw_command(pickled_cmd: bytes) -> object: + try: + await stdin.send(pickled_cmd) + response = await buffered.receive_until(b"\n", 50) + status, length = response.split(b" ") + if status not in (b"RETURN", b"EXCEPTION"): + raise RuntimeError( + f"Worker process returned unexpected response: {response!r}" + ) + + pickled_response = await buffered.receive_exactly(int(length)) + except BaseException as exc: + workers.discard(process) + try: + process.kill() + with CancelScope(shield=True): + await process.aclose() + except ProcessLookupError: + pass + + if isinstance(exc, get_cancelled_exc_class()): + raise + else: + raise BrokenWorkerProcess from exc + + retval = pickle.loads(pickled_response) + if status == b"EXCEPTION": + assert isinstance(retval, BaseException) + raise retval + else: + return retval + + # First pickle the request before trying to reserve a worker process + await checkpoint_if_cancelled() + request = pickle.dumps(("run", func, args), protocol=pickle.HIGHEST_PROTOCOL) + + # If this is the first run in this event loop thread, set up the necessary variables + try: + workers = _process_pool_workers.get() + idle_workers = _process_pool_idle_workers.get() + except LookupError: + workers = set() + idle_workers = deque() + _process_pool_workers.set(workers) + _process_pool_idle_workers.set(idle_workers) + get_async_backend().setup_process_pool_exit_at_shutdown(workers) + + async with limiter or current_default_process_limiter(): + # Pop processes from the pool (starting from the most recently used) until we + # find one that hasn't exited yet + process: Process + while idle_workers: + process, _idle_since = idle_workers.pop() + if process.returncode is None: + stdin = cast(ByteSendStream, process.stdin) + buffered = BufferedByteReceiveStream( + cast(ByteReceiveStream, process.stdout) + ) + + # Prune any other workers that have been idle for WORKER_MAX_IDLE_TIME + # seconds or longer + now = current_time() + killed_processes: list[Process] = [] + while idle_workers: + if now - idle_workers[0][1] < WORKER_MAX_IDLE_TIME: + break + + process_to_kill, _idle_since = idle_workers.popleft() + process_to_kill.kill() + workers.remove(process_to_kill) + killed_processes.append(process_to_kill) + + with CancelScope(shield=True): + for killed_process in killed_processes: + await killed_process.aclose() + + break + + workers.remove(process) + else: + command = [sys.executable, "-u", "-m", __name__] + process = await open_process( + command, stdin=subprocess.PIPE, stdout=subprocess.PIPE + ) + try: + stdin = cast(ByteSendStream, process.stdin) + buffered = BufferedByteReceiveStream( + cast(ByteReceiveStream, process.stdout) + ) + with fail_after(20): + message = await buffered.receive(6) + + if message != b"READY\n": + raise BrokenWorkerProcess( + f"Worker process returned unexpected response: {message!r}" + ) + + main_module_path = getattr(sys.modules["__main__"], "__file__", None) + pickled = pickle.dumps( + ("init", sys.path, main_module_path), + protocol=pickle.HIGHEST_PROTOCOL, + ) + await send_raw_command(pickled) + except (BrokenWorkerProcess, get_cancelled_exc_class()): + raise + except BaseException as exc: + process.kill() + raise BrokenWorkerProcess( + "Error during worker process initialization" + ) from exc + + workers.add(process) + + with CancelScope(shield=not cancellable): + try: + return cast(T_Retval, await send_raw_command(request)) + finally: + if process in workers: + idle_workers.append((process, current_time())) + + +def current_default_process_limiter() -> CapacityLimiter: + """ + Return the capacity limiter that is used by default to limit the number of worker + processes. + + :return: a capacity limiter object + + """ + try: + return _default_process_limiter.get() + except LookupError: + limiter = CapacityLimiter(os.cpu_count() or 2) + _default_process_limiter.set(limiter) + return limiter + + +def process_worker() -> None: + # Redirect standard streams to os.devnull so that user code won't interfere with the + # parent-worker communication + stdin = sys.stdin + stdout = sys.stdout + sys.stdin = open(os.devnull) + sys.stdout = open(os.devnull, "w") + sys.stderr = open(os.devnull, "w") + + stdout.buffer.write(b"READY\n") + while True: + retval = exception = None + try: + command, *args = pickle.load(stdin.buffer) + except EOFError: + return + except BaseException as exc: + exception = exc + else: + if command == "run": + func, args = args + try: + retval = func(*args) + except BaseException as exc: + exception = exc + elif command == "init": + main_module_path: str | None + sys.path, main_module_path = args + del sys.modules["__main__"] + if main_module_path and os.path.isfile(main_module_path): + # Load the parent's main module but as __mp_main__ instead of + # __main__ (like multiprocessing does) to avoid infinite recursion + try: + main = ModuleType("__mp_main__") + main_content = runpy.run_path( + main_module_path, run_name="__mp_main__" + ) + main.__dict__.update(main_content) + sys.modules["__main__"] = sys.modules["__mp_main__"] = main + except BaseException as exc: + exception = exc + try: + if exception is not None: + status = b"EXCEPTION" + pickled = pickle.dumps(exception, pickle.HIGHEST_PROTOCOL) + else: + status = b"RETURN" + pickled = pickle.dumps(retval, pickle.HIGHEST_PROTOCOL) + except BaseException as exc: + exception = exc + status = b"EXCEPTION" + pickled = pickle.dumps(exc, pickle.HIGHEST_PROTOCOL) + + stdout.buffer.write(b"%s %d\n" % (status, len(pickled))) + stdout.buffer.write(pickled) + + # Respect SIGTERM + if isinstance(exception, SystemExit): + raise exception + + +if __name__ == "__main__": + process_worker() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/anyio/to_thread.py b/.venvs/kimodo/lib/python3.10/site-packages/anyio/to_thread.py new file mode 100644 index 0000000000000000000000000000000000000000..5cdb7253e3c0a4f99ad2f893d31cc32f8fbda298 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/anyio/to_thread.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +__all__ = ( + "current_default_thread_limiter", + "run_sync", +) + +import sys +from collections.abc import Callable +from typing import TYPE_CHECKING, TypeVar +from warnings import warn + +from ._core._eventloop import get_async_backend + +if TYPE_CHECKING: + from ._core._synchronization import CapacityLimiter + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + + +async def run_sync( + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + abandon_on_cancel: bool = False, + cancellable: bool | None = None, + limiter: CapacityLimiter | None = None, +) -> T_Retval: + """ + Call the given function with the given arguments in a worker thread. + + If the ``abandon_on_cancel`` option is enabled and the task waiting for its + completion is cancelled, the thread will still run its course but its + return value (or any raised exception) will be ignored. + + :param func: a callable + :param args: positional arguments for the callable + :param abandon_on_cancel: ``True`` to abandon the thread (leaving it to run + unchecked on own) if the host task is cancelled, ``False`` to ignore + cancellations in the host task until the operation has completed in the worker + thread + :param cancellable: deprecated alias of ``abandon_on_cancel``; will override + ``abandon_on_cancel`` if both parameters are passed + :param limiter: capacity limiter to use to limit the total amount of threads running + (if omitted, the default limiter is used) + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + :return: an awaitable that yields the return value of the function. + + """ + if cancellable is not None: + abandon_on_cancel = cancellable + warn( + "The `cancellable=` keyword argument to `anyio.to_thread.run_sync` is " + "deprecated since AnyIO 4.1.0; use `abandon_on_cancel=` instead", + DeprecationWarning, + stacklevel=2, + ) + + return await get_async_backend().run_sync_in_worker_thread( + func, args, abandon_on_cancel=abandon_on_cancel, limiter=limiter + ) + + +def current_default_thread_limiter() -> CapacityLimiter: + """ + Return the capacity limiter that is used by default to limit the number of + concurrent threads. + + :return: a capacity limiter object + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().current_default_thread_limiter() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libSvtAv1Enc.4.1.0.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libSvtAv1Enc.4.1.0.dylib new file mode 100644 index 0000000000000000000000000000000000000000..d7ba2e0f67c934e10dbb8e940ea8efe778d9fb1a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libSvtAv1Enc.4.1.0.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d37ff29cf32a19a4a630f789d1ce017c1dcb4613ac352359644597cad02b2653 +size 3794416 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavcodec.62.28.101.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavcodec.62.28.101.dylib new file mode 100644 index 0000000000000000000000000000000000000000..ecbe5478504846632abf5244e51c2bebab3379cb --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavcodec.62.28.101.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba2bb21ad32f296f1f5ab62646d1f9b529906d0b7da24fdf0690b87791902cdf +size 12895552 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavdevice.62.3.101.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavdevice.62.3.101.dylib new file mode 100644 index 0000000000000000000000000000000000000000..ac23a534665bc77f50b374dcc3568cb6faf89cc4 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavdevice.62.3.101.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9399c4cc9c56ffac7301814821ee536c77ac5a75c7125b068f80a5c2988c8311 +size 117504 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavfilter.11.14.101.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavfilter.11.14.101.dylib new file mode 100644 index 0000000000000000000000000000000000000000..e2dd6df33ee910a727532502501e3350b09cb225 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavfilter.11.14.101.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4467faf9553d9e5ff3639a271bdb8ab5bfbf1d39d2e349f651c536800ee95a8 +size 3677904 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavformat.62.12.101.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavformat.62.12.101.dylib new file mode 100644 index 0000000000000000000000000000000000000000..9db7028ab996817caf98119720be012d0e171fb8 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavformat.62.12.101.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a25d9461087f303ec16135acca77e546e50ee39c4f3f1e85f41d52d4bf599a72 +size 2590944 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavutil.60.26.101.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavutil.60.26.101.dylib new file mode 100644 index 0000000000000000000000000000000000000000..26c276c1d3488dad453cdd942c61a5125e145007 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libavutil.60.26.101.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f619ebe41552c778814c764c67f6c284e83dfacb687ed8ed9967e341cf20f426 +size 743952 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libdav1d.7.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libdav1d.7.dylib new file mode 100644 index 0000000000000000000000000000000000000000..34796c71ab716710f6cc04739baa7c05bf2e9295 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libdav1d.7.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:798fc8d5a3adf5f3b3cf3aa8bf895ce2c2b5e802dd93aa0b17048716bcdb1791 +size 733792 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libmp3lame.0.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libmp3lame.0.dylib new file mode 100644 index 0000000000000000000000000000000000000000..89c769b63e36dbabbf53c7e1f834e0a54f7aacde --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libmp3lame.0.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff0d697c6ecc8a636240fc67d567e074341f998867d229c786b13ca6115d68c5 +size 365152 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libopencore-amrnb.0.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libopencore-amrnb.0.dylib new file mode 100644 index 0000000000000000000000000000000000000000..d5f4b809edc56262da0b048440114530fd584f72 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libopencore-amrnb.0.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1801063e7504c8702d2a1cee6e6ef4492d962b9e05cb7fc52515ed415dbc912d +size 267664 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libopencore-amrwb.0.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libopencore-amrwb.0.dylib new file mode 100644 index 0000000000000000000000000000000000000000..9040aaf0b30beeb50627bde38018e82cce201e7c --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libopencore-amrwb.0.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b13a54f6ec3daefa68289b777405ecb06271e09006862bc1f7a1570590220972 +size 134800 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libopus.0.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libopus.0.dylib new file mode 100644 index 0000000000000000000000000000000000000000..6adefcfba0fa8f051bb6aa8383fda49b38f1c9e6 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libopus.0.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dca12b81b778f51dc5cecbc789fffaac472de09b102a164d5348b360b238a202 +size 636800 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libsharpyuv.0.1.2.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libsharpyuv.0.1.2.dylib new file mode 100644 index 0000000000000000000000000000000000000000..82c87a7641b1d0121170499c3a36ceefaa9a4b12 Binary files /dev/null and b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libsharpyuv.0.1.2.dylib differ diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libswresample.6.3.101.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libswresample.6.3.101.dylib new file mode 100644 index 0000000000000000000000000000000000000000..882207d16ab96fb4b2aa150c40462919138a0265 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libswresample.6.3.101.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:769660cae56f9e602877b5574a29150372b3133ef833ec8cd09e9e7f67de2888 +size 121632 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libswscale.9.5.101.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libswscale.9.5.101.dylib new file mode 100644 index 0000000000000000000000000000000000000000..07fcd960244088ef520693b8bc1188a0831e6f22 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libswscale.9.5.101.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:272fea17c904f46a117aafc642e625b7189777bdbfdae02a5f97fb77f82a91c7 +size 1051952 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libvpx.12.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libvpx.12.dylib new file mode 100644 index 0000000000000000000000000000000000000000..a98f3ccba600079738d018df04aaa9413b78afe0 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libvpx.12.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3b6fd016f2da69d992bdd5e35fee0c796766164b544537d722a2044a2f1f366 +size 1711056 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libwebp.7.2.0.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libwebp.7.2.0.dylib new file mode 100644 index 0000000000000000000000000000000000000000..0236e45b048b80c9be0d0471b741ef524aef2e02 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libwebp.7.2.0.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6370143226449531c91631c941b850c537ff1c1862bbeb86a991bd76871420d +size 471632 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libwebpmux.3.1.2.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libwebpmux.3.1.2.dylib new file mode 100644 index 0000000000000000000000000000000000000000..0c83e8caaa9f61a80caaf22eb64a96fe13edf870 Binary files /dev/null and b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libwebpmux.3.1.2.dylib differ diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libx264.165.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libx264.165.dylib new file mode 100644 index 0000000000000000000000000000000000000000..b58fc73b53b4de304342a5cf571f3df380979887 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libx264.165.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59553302c829a9bb3331d59cdc9c7b73379c461fc3926e1af9aed533e807a91b +size 1545728 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libx265.216.dylib b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libx265.216.dylib new file mode 100644 index 0000000000000000000000000000000000000000..a83e8e3cd2bdf1b35fd5b7e329cc2ecf60ff4f6d --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/.dylibs/libx265.216.dylib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30e358958991b579b25d814f6e28bd30c92bd797425b2e23297aadd547543527 +size 6867472 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/__init__.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/__init__.pxd new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/__init__.py b/.venvs/kimodo/lib/python3.10/site-packages/av/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9d8148082752f42a95172f84ccbb678ea313206c --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/__init__.py @@ -0,0 +1,72 @@ +# MUST import the core before anything else in order to initialize the underlying +# library that is being wrapped. +from av._core import time_base, library_versions, ffmpeg_version_info + +# Capture logging (by importing it). +from av import logging + +# For convenience, import all common attributes. +from av.about import __version__ +from av.audio.codeccontext import AudioCodecContext +from av.audio.fifo import AudioFifo +from av.audio.format import AudioFormat +from av.audio.frame import AudioFrame +from av.audio.layout import AudioLayout +from av.audio.resampler import AudioResampler +from av.audio.stream import AudioStream +from av.bitstream import BitStreamFilterContext, bitstream_filters_available +from av.codec.codec import Codec, codecs_available +from av.codec.context import CodecContext +from av.container import open +from av.device import DeviceInfo, enumerate_input_devices, enumerate_output_devices +from av.format import ContainerFormat, formats_available +from av.packet import Packet +from av.error import * # noqa: F403; This is limited to exception types. +from av.video.codeccontext import VideoCodecContext +from av.video.format import VideoFormat +from av.video.frame import VideoFrame +from av.video.stream import VideoStream + +__all__ = ( + "__version__", + "time_base", + "ffmpeg_version_info", + "library_versions", + "AudioCodecContext", + "AudioFifo", + "AudioFormat", + "AudioFrame", + "AudioLayout", + "AudioResampler", + "AudioStream", + "BitStreamFilterContext", + "bitstream_filters_available", + "Codec", + "codecs_available", + "CodecContext", + "open", + "DeviceInfo", + "enumerate_input_devices", + "enumerate_output_devices", + "ContainerFormat", + "formats_available", + "Packet", + "VideoCodecContext", + "VideoFormat", + "VideoFrame", + "VideoStream", +) + + +def get_include() -> str: + """ + Returns the path to the `include` folder to be used when building extensions to av. + """ + import os + + # Installed package + include_path = os.path.join(os.path.dirname(__file__), "include") + if os.path.exists(include_path): + return include_path + # Running from source directory + return os.path.join(os.path.dirname(__file__), os.pardir, "include") diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/__main__.py b/.venvs/kimodo/lib/python3.10/site-packages/av/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..9e2b9d0ac00c1151ed5bb1947dc5d52e357255e9 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/__main__.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import argparse + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--codecs", action="store_true") + parser.add_argument("--hwdevices", action="store_true") + parser.add_argument("--hwconfigs", action="store_true") + parser.add_argument("--version", action="store_true") + args = parser.parse_args() + + if args.version: + import av + import av._core + + print(f"PyAV v{av.__version__}") + + by_config: dict = {} + for libname, config in sorted(av._core.library_meta.items()): + version = config["version"] + if version[0] >= 0: + by_config.setdefault( + (config["configuration"], config["license"]), [] + ).append((libname, config)) + + for (config, license), libs in sorted(by_config.items()): + print("library configuration:", config) + print("library license:", license) + for libname, config in libs: + version = config["version"] + print(f"{libname:<13} {version[0]:3d}.{version[1]:3d}.{version[2]:3d}") + + if args.hwdevices: + from av.codec.hwaccel import hwdevices_available + + print("Hardware device types:") + for x in hwdevices_available(): + print(" ", x) + + if args.hwconfigs: + from av.codec.codec import dump_hwconfigs + + dump_hwconfigs() + + if args.codecs: + from av.codec.codec import dump_codecs + + dump_codecs() + + +if __name__ == "__main__": + main() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/_core.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/_core.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..e2107b8e139f4c039e7564613fd1f4ed3eda38b2 Binary files /dev/null and b/.venvs/kimodo/lib/python3.10/site-packages/av/_core.cpython-310-darwin.so differ diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/_core.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/_core.pxd new file mode 100644 index 0000000000000000000000000000000000000000..43d1e716d2f1699b472eb9d2919c8170d162903e --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/_core.pxd @@ -0,0 +1,9 @@ +cdef extern from "libswscale/swscale.h" nogil: + cdef int swscale_version() + cdef char* swscale_configuration() + cdef char* swscale_license() + +cdef extern from "libswresample/swresample.h" nogil: + cdef int swresample_version() + cdef char* swresample_configuration() + cdef char* swresample_license() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/_core.py b/.venvs/kimodo/lib/python3.10/site-packages/av/_core.py new file mode 100644 index 0000000000000000000000000000000000000000..6ad38df5b20593cba9e59047bd9ef33249560934 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/_core.py @@ -0,0 +1,65 @@ +import cython +import cython.cimports.libav as lib + +lib.avdevice_register_all() + +# Exports. +time_base = lib.AV_TIME_BASE + + +@cython.cfunc +def decode_version(v): + if v < 0: + return (-1, -1, -1) + + major: cython.int = (v >> 16) & 0xFF + minor: cython.int = (v >> 8) & 0xFF + micro: cython.int = (v) & 0xFF + return (major, minor, micro) + + +# Return an informative version string. +# This usually is the actual release version number or a git commit +# description. This string has no fixed format and can change any time. It +# should never be parsed by code. +ffmpeg_version_info = lib.av_version_info() + +library_meta = { + "libavutil": dict( + version=decode_version(lib.avutil_version()), + configuration=lib.avutil_configuration(), + license=lib.avutil_license(), + ), + "libavcodec": dict( + version=decode_version(lib.avcodec_version()), + configuration=lib.avcodec_configuration(), + license=lib.avcodec_license(), + ), + "libavformat": dict( + version=decode_version(lib.avformat_version()), + configuration=lib.avformat_configuration(), + license=lib.avformat_license(), + ), + "libavdevice": dict( + version=decode_version(lib.avdevice_version()), + configuration=lib.avdevice_configuration(), + license=lib.avdevice_license(), + ), + "libavfilter": dict( + version=decode_version(lib.avfilter_version()), + configuration=lib.avfilter_configuration(), + license=lib.avfilter_license(), + ), + "libswscale": dict( + version=decode_version(swscale_version()), + configuration=swscale_configuration(), + license=swscale_license(), + ), + "libswresample": dict( + version=decode_version(swresample_version()), + configuration=swresample_configuration(), + license=swresample_license(), + ), +} + +library_versions = {name: meta["version"] for name, meta in library_meta.items()} diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/_core.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/_core.pyi new file mode 100644 index 0000000000000000000000000000000000000000..0ee0a562677bdb1c4a98c52722af95e0a4f08d00 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/_core.pyi @@ -0,0 +1,12 @@ +from typing import TypedDict + +class _Meta(TypedDict): + version: tuple[int, int, int] + configuration: str + license: str + +library_meta: dict[str, _Meta] +library_versions: dict[str, tuple[int, int, int]] +ffmpeg_version_info: str + +time_base: int diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/about.py b/.venvs/kimodo/lib/python3.10/site-packages/av/about.py new file mode 100644 index 0000000000000000000000000000000000000000..1bef25448ff660edb4b3ed030239a0fe00f8ac11 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/about.py @@ -0,0 +1 @@ +__version__ = "17.1.0" diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/__init__.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/__init__.pxd new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/__init__.py b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f57d62046e523db854310bd48655f40c23951cf0 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/__init__.py @@ -0,0 +1,2 @@ +from .frame import AudioFrame as AudioFrame +from .stream import AudioStream as AudioStream diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/__init__.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/__init__.pyi new file mode 100644 index 0000000000000000000000000000000000000000..daefab6c928b7f0f6751b0c45afc63ea056ea2fd --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/__init__.pyi @@ -0,0 +1,16 @@ +from typing import Literal + +from .frame import AudioFrame +from .stream import AudioStream + +_AudioCodecName = Literal[ + "aac", + "libopus", + "mp2", + "mp3", + "pcm_alaw", + "pcm_mulaw", + "pcm_s16le", +] + +__all__ = ("AudioFrame", "AudioStream") diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/codeccontext.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/codeccontext.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..d8a696dd1c359a1fb97c514488d9009558dc211a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/codeccontext.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8ee7750437fd7e2e6f1f3e9514e1ff45b92d9e6a74d9d0cafb83173b82e0b88 +size 114464 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/codeccontext.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/codeccontext.pxd new file mode 100644 index 0000000000000000000000000000000000000000..55ad15e9f0404d2b77810682774fd25ab434fadb --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/codeccontext.pxd @@ -0,0 +1,11 @@ + +from av.audio.frame cimport AudioFrame +from av.audio.resampler cimport AudioResampler +from av.codec.context cimport CodecContext + + +cdef class AudioCodecContext(CodecContext): + # Hold onto the frames that we will decode until we have a full one. + cdef AudioFrame next_frame + # For encoding. + cdef AudioResampler resampler diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/codeccontext.py b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/codeccontext.py new file mode 100644 index 0000000000000000000000000000000000000000..4e7118d3406c3b78283c10c00de19da99670c584 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/codeccontext.py @@ -0,0 +1,106 @@ +import cython +from cython.cimports import libav as lib +from cython.cimports.av.audio.format import AudioFormat, get_audio_format +from cython.cimports.av.audio.frame import AudioFrame, alloc_audio_frame +from cython.cimports.av.audio.layout import AudioLayout, get_audio_layout +from cython.cimports.av.frame import Frame +from cython.cimports.av.packet import Packet + + +@cython.final +@cython.cclass +class AudioCodecContext(CodecContext): + @cython.cfunc + def _prepare_frames_for_encode(self, input_frame: Frame | None) -> list: + frame: AudioFrame | None = input_frame + allow_var_frame_size: cython.bint = ( + self.ptr.codec.capabilities & lib.AV_CODEC_CAP_VARIABLE_FRAME_SIZE + ) + + # Note that the resampler will simply return an input frame if there is + # no resampling to be done. The control flow was just a little easier this way. + if not self.resampler: + self.resampler = AudioResampler( + format=self.format, + layout=self.layout, + rate=self.ptr.sample_rate, + frame_size=None if allow_var_frame_size else self.ptr.frame_size, + ) + frames = self.resampler.resample(frame) + if input_frame is None: + frames.append(None) # flush if input frame is None + + return frames + + @cython.cfunc + def _alloc_next_frame(self) -> Frame: + return alloc_audio_frame() + + @cython.cfunc + def _setup_decoded_frame(self, frame: Frame, packet: Packet): + CodecContext._setup_decoded_frame(self, frame, packet) + aframe: AudioFrame = frame + aframe._init_user_attributes() + + @property + def frame_size(self): + """ + Number of samples per channel in an audio frame. + + :type: int + """ + return self.ptr.frame_size + + @property + def sample_rate(self): + """ + Sample rate of the audio data, in samples per second. + + :type: int + """ + return self.ptr.sample_rate + + @sample_rate.setter + def sample_rate(self, value: cython.int): + self.ptr.sample_rate = value + + @property + def rate(self): + """Another name for :attr:`sample_rate`.""" + return self.sample_rate + + @rate.setter + def rate(self, value): + self.sample_rate = value + + @property + def channels(self): + return self.layout.nb_channels + + @property + def layout(self): + """ + The audio channel layout. + + :type: AudioLayout + """ + return get_audio_layout(self.ptr.ch_layout) + + @layout.setter + def layout(self, value): + layout: AudioLayout = AudioLayout(value) + self.ptr.ch_layout = layout.layout + + @property + def format(self): + """ + The audio sample format. + + :type: AudioFormat + """ + return get_audio_format(self.ptr.sample_fmt) + + @format.setter + def format(self, value): + format: AudioFormat = AudioFormat(value) + self.ptr.sample_fmt = format.sample_fmt diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/codeccontext.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/codeccontext.pyi new file mode 100644 index 0000000000000000000000000000000000000000..b3ec3ce6e877a88a9fcf7661c9aded27201fc8fb --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/codeccontext.pyi @@ -0,0 +1,29 @@ +from typing import Iterator, Literal + +from av.codec.context import CodecContext +from av.packet import Packet + +from .format import AudioFormat +from .frame import AudioFrame +from .layout import AudioLayout + +class _Format: + def __get__(self, i: object | None, owner: type | None = None) -> AudioFormat: ... + def __set__(self, instance: object, value: AudioFormat | str) -> None: ... + +class _Layout: + def __get__(self, i: object | None, owner: type | None = None) -> AudioLayout: ... + def __set__(self, instance: object, value: AudioLayout | str) -> None: ... + +class AudioCodecContext(CodecContext): + frame_size: int + sample_rate: int + rate: int + type: Literal["audio"] + format: _Format + layout: _Layout + @property + def channels(self) -> int: ... + def encode(self, frame: AudioFrame | None = None) -> list[Packet]: ... + def encode_lazy(self, frame: AudioFrame | None = None) -> Iterator[Packet]: ... + def decode(self, packet: Packet | None = None) -> list[AudioFrame]: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/fifo.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/fifo.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..3ba1877d3e49737d4aee7e1a85fc8b7c6c4e6930 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/fifo.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41b15bd34962bbb9e7f2891f6a272708309a76cbe773e7c4460707145e8e0768 +size 116048 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/fifo.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/fifo.pxd new file mode 100644 index 0000000000000000000000000000000000000000..0ace5e4b1ec84daaeb09f27f6f9689a56d026912 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/fifo.pxd @@ -0,0 +1,19 @@ +cimport libav as lib +from libc.stdint cimport int64_t, uint64_t + +from av.audio.frame cimport AudioFrame + + +cdef class AudioFifo: + + cdef lib.AVAudioFifo *ptr + + cdef AudioFrame template + + cdef readonly uint64_t samples_written + cdef readonly uint64_t samples_read + cdef readonly double pts_per_sample + + cpdef write(self, AudioFrame frame) + cpdef read(self, int samples=*, bint partial=*) + cpdef read_many(self, int samples, bint partial=*) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/fifo.py b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/fifo.py new file mode 100644 index 0000000000000000000000000000000000000000..0fd128ee74e26929be400a5379047c52870d7944 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/fifo.py @@ -0,0 +1,222 @@ +import cython +from cython.cimports.av.audio.frame import alloc_audio_frame +from cython.cimports.av.error import err_check + + +@cython.final +@cython.cclass +class AudioFifo: + """A simple audio sample FIFO (First In First Out) buffer.""" + + def __repr__(self): + try: + result = ( + f"" + ) + except AttributeError: + result = ( + f"" + ) + return result + + def __dealloc__(self): + if self.ptr: + lib.av_audio_fifo_free(self.ptr) + + @cython.ccall + def write(self, frame: AudioFrame | None): + """write(frame) + + Push a frame of samples into the queue. + + :param AudioFrame frame: The frame of samples to push. + + The FIFO will remember the attributes from the first frame, and use those + to populate all output frames. + + If there is a :attr:`~.Frame.pts` and :attr:`~.Frame.time_base` and + :attr:`~.AudioFrame.sample_rate`, then the FIFO will assert that the incoming + timestamps are continuous. + + """ + + if frame is None: + raise TypeError("AudioFifo must be given an AudioFrame.") + + if not frame.ptr.nb_samples: + return + + if not self.ptr: + # Hold onto a copy of the attributes of the first frame to populate + # output frames with. + self.template = alloc_audio_frame() + self.template._copy_internal_attributes(frame) + self.template._init_user_attributes() + + # Figure out our "time_base". + if frame._time_base.num and frame.ptr.sample_rate: + self.pts_per_sample = frame._time_base.den / float(frame._time_base.num) + self.pts_per_sample /= frame.ptr.sample_rate + else: + self.pts_per_sample = 0 + + self.ptr = lib.av_audio_fifo_alloc( + cython.cast(lib.AVSampleFormat, frame.ptr.format), + frame.layout.nb_channels, + frame.ptr.nb_samples + * 2, # Just a default number of samples; it will adjust. + ) + + if not self.ptr: + raise RuntimeError("Could not allocate AVAudioFifo.") + + # Make sure nothing changed. + elif ( + frame.ptr.format != self.template.ptr.format + or frame.ptr.sample_rate != self.template.ptr.sample_rate + or ( + frame._time_base.num + and self.template._time_base.num + and ( + frame._time_base.num != self.template._time_base.num + or frame._time_base.den != self.template._time_base.den + ) + ) + ): + raise ValueError("Frame does not match AudioFifo parameters.") + + # Assert that the PTS are what we expect. + expected_pts = cython.declare(int64_t) + if self.pts_per_sample and frame.ptr.pts != lib.AV_NOPTS_VALUE: + expected_pts = cython.cast( + int64_t, self.pts_per_sample * self.samples_written + ) + if frame.ptr.pts != expected_pts: + raise ValueError( + "Frame.pts (%d) != expected (%d); fix or set to None." + % (frame.ptr.pts, expected_pts) + ) + + err_check( + lib.av_audio_fifo_write( + self.ptr, + cython.cast(cython.pointer[cython.p_void], frame.ptr.extended_data), + frame.ptr.nb_samples, + ) + ) + + self.samples_written += frame.ptr.nb_samples + + @cython.ccall + def read(self, samples: cython.int = 0, partial: cython.bint = False): + """read(samples=0, partial=False) + + Read samples from the queue. + + :param int samples: The number of samples to pull; 0 gets all. + :param bool partial: Allow returning less than requested. + :returns: New :class:`AudioFrame` or ``None`` (if empty). + + If the incoming frames had valid a :attr:`~.Frame.time_base`, + :attr:`~.AudioFrame.sample_rate` and :attr:`~.Frame.pts`, the returned frames + will have accurate timing. + + """ + + if not self.ptr: + return + + buffered_samples: cython.int = lib.av_audio_fifo_size(self.ptr) + if buffered_samples < 1: + return + + samples = samples or buffered_samples + + if buffered_samples < samples: + if partial: + samples = buffered_samples + else: + return + + frame: AudioFrame = alloc_audio_frame() + frame._copy_internal_attributes(self.template) + frame._init( + cython.cast(lib.AVSampleFormat, self.template.ptr.format), + cython.cast(lib.AVChannelLayout, self.template.ptr.ch_layout), + samples, + 1, # Align? + ) + + err_check( + lib.av_audio_fifo_read( + self.ptr, + cython.cast(cython.pointer[cython.p_void], frame.ptr.extended_data), + samples, + ) + ) + + if self.pts_per_sample: + frame.ptr.pts = cython.cast( + uint64_t, self.pts_per_sample * self.samples_read + ) + else: + frame.ptr.pts = lib.AV_NOPTS_VALUE + + self.samples_read += samples + return frame + + @cython.ccall + def read_many(self, samples: cython.int, partial: cython.bint = False): + """read_many(samples, partial=False) + + Read as many frames as we can. + + :param int samples: How large for the frames to be. + :param bool partial: If we should return a partial frame. + :returns: A ``list`` of :class:`AudioFrame`. + + """ + + frame: AudioFrame + frames: list = [] + while True: + frame = self.read(samples, partial=partial) + if frame is not None: + frames.append(frame) + else: + break + + return frames + + @property + def format(self): + """The :class:`.AudioFormat` of this FIFO.""" + if not self.ptr: + raise AttributeError( + f"'{__name__}.AudioFifo' object has no attribute 'format'" + ) + return self.template.format + + @property + def layout(self): + """The :class:`.AudioLayout` of this FIFO.""" + if not self.ptr: + raise AttributeError( + f"'{__name__}.AudioFifo' object has no attribute 'layout'" + ) + return self.template.layout + + @property + def sample_rate(self): + if not self.ptr: + raise AttributeError( + f"'{__name__}.AudioFifo' object has no attribute 'sample_rate'" + ) + return self.template.sample_rate + + @property + def samples(self): + """Number of audio samples (per channel) in the buffer.""" + return lib.av_audio_fifo_size(self.ptr) if self.ptr else 0 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/fifo.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/fifo.pyi new file mode 100644 index 0000000000000000000000000000000000000000..085ed4bba1039b77675da29e26ef9ccbd539c8e1 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/fifo.pyi @@ -0,0 +1,22 @@ +from .format import AudioFormat +from .frame import AudioFrame +from .layout import AudioLayout + +class AudioFifo: + def write(self, frame: AudioFrame) -> None: ... + def read(self, samples: int = 0, partial: bool = False) -> AudioFrame | None: ... + def read_many(self, samples: int, partial: bool = False) -> list[AudioFrame]: ... + @property + def format(self) -> AudioFormat: ... + @property + def layout(self) -> AudioLayout: ... + @property + def sample_rate(self) -> int: ... + @property + def samples(self) -> int: ... + @property + def samples_written(self) -> int: ... + @property + def samples_read(self) -> int: ... + @property + def pts_per_sample(self) -> float: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/format.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/format.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..251a0582c453b3a56cc59fcc5fc56729952958e2 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/format.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d95de2f6aab81ac33dc486f2b2fb9884f7528153c52a6cbac09362cc374b80b +size 113728 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/format.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/format.pxd new file mode 100644 index 0000000000000000000000000000000000000000..c4d4bc552a5a9736ab87a83817a97f6ca62d6b2a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/format.pxd @@ -0,0 +1,7 @@ +cimport libav as lib + + +cdef class AudioFormat: + cdef lib.AVSampleFormat sample_fmt + +cdef AudioFormat get_audio_format(lib.AVSampleFormat format) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/format.py b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/format.py new file mode 100644 index 0000000000000000000000000000000000000000..d398f6924c491e5a86c67dcd8e5054e4f327b57b --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/format.py @@ -0,0 +1,145 @@ +import sys + +import cython + +container_format_postfix = cython.declare( + str, "le" if sys.byteorder == "little" else "be" +) +_cinit_bypass_sentinel = cython.declare(object, object()) + + +@cython.cfunc +def get_audio_format(c_format: lib.AVSampleFormat) -> AudioFormat: + """Get an AudioFormat without going through a string.""" + + if c_format < 0: + return None + + format: AudioFormat = AudioFormat(_cinit_bypass_sentinel) + format.sample_fmt = c_format + return format + + +@cython.final +@cython.cclass +class AudioFormat: + """Descriptor of audio formats.""" + + def __cinit__(self, name): + if name is _cinit_bypass_sentinel: + return + + sample_fmt: lib.AVSampleFormat + if isinstance(name, AudioFormat): + sample_fmt = cython.cast(AudioFormat, name).sample_fmt + else: + sample_fmt = lib.av_get_sample_fmt(name) + + if sample_fmt < 0: + raise ValueError(f"Not a sample format: {name!r}") + + self.sample_fmt = sample_fmt + + def __repr__(self): + return f"" + + @property + def name(self): + """Canonical name of the sample format. + + >>> AudioFormat('s16p').name + 's16p' + + """ + return lib.av_get_sample_fmt_name(self.sample_fmt) + + @property + def bytes(self): + """Number of bytes per sample. + + >>> AudioFormat('s16p').bytes + 2 + + """ + return lib.av_get_bytes_per_sample(self.sample_fmt) + + @property + def bits(self): + """Number of bits per sample. + + >>> AudioFormat('s16p').bits + 16 + + """ + return lib.av_get_bytes_per_sample(self.sample_fmt) << 3 + + @property + def is_planar(self): + """Is this a planar format? + + Strictly opposite of :attr:`is_packed`. + + """ + return bool(lib.av_sample_fmt_is_planar(self.sample_fmt)) + + @property + def is_packed(self): + """Is this a packed format? + + Strictly opposite of :attr:`is_planar`. + + """ + return not lib.av_sample_fmt_is_planar(self.sample_fmt) + + @property + def planar(self): + """The planar variant of this format. + + Is itself when planar: + + >>> fmt = AudioFormat('s16p') + >>> fmt.planar is fmt + True + + """ + if self.is_planar: + return self + return get_audio_format(lib.av_get_planar_sample_fmt(self.sample_fmt)) + + @property + def packed(self): + """The packed variant of this format. + + Is itself when packed: + + >>> fmt = AudioFormat('s16') + >>> fmt.packed is fmt + True + + """ + if self.is_packed: + return self + return get_audio_format(lib.av_get_packed_sample_fmt(self.sample_fmt)) + + @property + def container_name(self): + """The name of a :class:`ContainerFormat` which directly accepts this data. + + :raises ValueError: when planar, since there are no such containers. + + """ + if self.is_planar: + raise ValueError("no planar container formats") + + if self.sample_fmt == lib.AV_SAMPLE_FMT_U8: + return "u8" + elif self.sample_fmt == lib.AV_SAMPLE_FMT_S16: + return "s16" + container_format_postfix + elif self.sample_fmt == lib.AV_SAMPLE_FMT_S32: + return "s32" + container_format_postfix + elif self.sample_fmt == lib.AV_SAMPLE_FMT_FLT: + return "f32" + container_format_postfix + elif self.sample_fmt == lib.AV_SAMPLE_FMT_DBL: + return "f64" + container_format_postfix + + raise ValueError("unknown layout") diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/format.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/format.pyi new file mode 100644 index 0000000000000000000000000000000000000000..5f7e322edc85d831b5b6cc9979fd4dfca4b994c1 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/format.pyi @@ -0,0 +1,11 @@ +class AudioFormat: + name: str + bytes: int + bits: int + is_planar: bool + is_packed: bool + planar: AudioFormat + packed: AudioFormat + container_name: str + + def __init__(self, name: str | AudioFormat) -> None: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/frame.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/frame.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..0b7658a37089de32076a0101372cc5c421f8298f --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/frame.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12c0f7e6bdd8c42ad5212c95baeb85a05b5f023011d478463ad5d26d2afbcfd0 +size 136672 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/frame.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/frame.pxd new file mode 100644 index 0000000000000000000000000000000000000000..398d76d33ea1173e31a5e13809ba3910385572b3 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/frame.pxd @@ -0,0 +1,31 @@ +cimport libav as lib +from libc.stdint cimport uint8_t, uint64_t + +from av.audio.format cimport AudioFormat +from av.audio.layout cimport AudioLayout +from av.frame cimport Frame + + +cdef class AudioFrame(Frame): + # For raw storage of the frame's data; don't ever touch this. + cdef uint8_t *_buffer + cdef size_t _buffer_size + + cdef readonly AudioLayout layout + """ + The audio channel layout. + + :type: AudioLayout + """ + + cdef readonly AudioFormat format + """ + The audio sample format. + + :type: AudioFormat + """ + + cdef _init(self, lib.AVSampleFormat format, lib.AVChannelLayout layout, unsigned int nb_samples, unsigned int align) + cdef _init_user_attributes(self) + +cdef AudioFrame alloc_audio_frame() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/frame.py b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/frame.py new file mode 100644 index 0000000000000000000000000000000000000000..c843c9efd48d4f3a959f6e16de22dbb8350fb181 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/frame.py @@ -0,0 +1,206 @@ +import cython +from cython.cimports.av.audio.format import get_audio_format +from cython.cimports.av.audio.layout import get_audio_layout +from cython.cimports.av.audio.plane import AudioPlane +from cython.cimports.av.error import err_check +from cython.cimports.av.utils import check_ndarray + +_cinit_bypass_sentinel = cython.declare(object, object()) + + +@cython.cfunc +def alloc_audio_frame() -> AudioFrame: + return AudioFrame(_cinit_bypass_sentinel) + + +format_dtypes = { + "dbl": "f8", + "dblp": "f8", + "flt": "f4", + "fltp": "f4", + "s16": "i2", + "s16p": "i2", + "s32": "i4", + "s32p": "i4", + "u8": "u1", + "u8p": "u1", +} + + +@cython.final +@cython.cclass +class AudioFrame(Frame): + """A frame of audio.""" + + def __cinit__(self, format="s16", layout="stereo", samples=0, align=1): + if format is _cinit_bypass_sentinel: + return + + cy_format: AudioFormat = AudioFormat(format) + cy_layout: AudioLayout = AudioLayout(layout) + self._init(cy_format.sample_fmt, cy_layout.layout, samples, align) + + @cython.cfunc + def _init( + self, + format: lib.AVSampleFormat, + layout: lib.AVChannelLayout, + nb_samples: cython.uint, + align: cython.uint, + ): + self.ptr.nb_samples = nb_samples + self.ptr.format = format + self.ptr.ch_layout = layout + + # Sometimes this is called twice. Oh well. + self._init_user_attributes() + + if self.layout.nb_channels != 0 and nb_samples: + # Cleanup the old buffer. + lib.av_freep(cython.address(self._buffer)) + + # Get a new one. + self._buffer_size = err_check( + lib.av_samples_get_buffer_size( + cython.NULL, self.layout.nb_channels, nb_samples, format, align + ) + ) + self._buffer = cython.cast( + cython.pointer[uint8_t], lib.av_malloc(self._buffer_size) + ) + if not self._buffer: + raise MemoryError("cannot allocate AudioFrame buffer") + + # Connect the data pointers to the buffer. + err_check( + lib.avcodec_fill_audio_frame( + self.ptr, + self.layout.nb_channels, + cython.cast(lib.AVSampleFormat, self.ptr.format), + self._buffer, + self._buffer_size, + align, + ) + ) + + def __dealloc__(self): + lib.av_freep(cython.address(self._buffer)) + + @cython.cfunc + def _init_user_attributes(self): + self.layout = get_audio_layout(self.ptr.ch_layout) + self.format = get_audio_format(cython.cast(lib.AVSampleFormat, self.ptr.format)) + + def __repr__(self): + return ( + f"" + ) + + @staticmethod + def from_ndarray(array, format="s16", layout="stereo"): + """ + Construct a frame from a numpy array. + """ + import numpy as np + + py_format = format if isinstance(format, AudioFormat) else AudioFormat(format) + py_layout = layout if isinstance(layout, AudioLayout) else AudioLayout(layout) + format = py_format.name + + # map avcodec type to numpy type + try: + dtype = np.dtype(format_dtypes[format]) + except KeyError: + raise ValueError( + f"Conversion from numpy array with format `{format}` is not yet supported" + ) + + # check input format + nb_channels = py_layout.nb_channels + check_ndarray(array, dtype, 2) + if py_format.is_planar: + if array.shape[0] != nb_channels: + raise ValueError( + f"Expected planar `array.shape[0]` to equal `{nb_channels}` but got `{array.shape[0]}`" + ) + samples = array.shape[1] + else: + if array.shape[0] != 1: + raise ValueError( + f"Expected packed `array.shape[0]` to equal `1` but got `{array.shape[0]}`" + ) + samples = array.shape[1] // nb_channels + + frame = AudioFrame(format=py_format, layout=py_layout, samples=samples) + for i, plane in enumerate(frame.planes): + plane.update(array[i, :]) + return frame + + @property + def planes(self): + """ + A tuple of :class:`~av.audio.plane.AudioPlane`. + + :type: tuple + """ + plane_count: cython.int = 0 + while self.ptr.extended_data[plane_count]: + plane_count += 1 + + return tuple([AudioPlane(self, i) for i in range(plane_count)]) + + @property + def samples(self): + """ + Number of audio samples (per channel). + + :type: int + """ + return self.ptr.nb_samples + + @property + def sample_rate(self): + """ + Sample rate of the audio data, in samples per second. + + :type: int + """ + return self.ptr.sample_rate + + @sample_rate.setter + def sample_rate(self, value): + self.ptr.sample_rate = value + + @property + def rate(self): + """Another name for :attr:`sample_rate`.""" + return self.ptr.sample_rate + + @rate.setter + def rate(self, value): + self.ptr.sample_rate = value + + def to_ndarray(self): + """Get a numpy array of this frame. + + .. note:: Numpy must be installed. + + """ + import numpy as np + + try: + dtype = np.dtype(format_dtypes[self.format.name]) + except KeyError: + raise ValueError( + f"Conversion from {self.format.name!r} format to numpy array is not supported." + ) + + if self.format.is_planar: + count = self.samples + else: + count = self.samples * self.layout.nb_channels + + return np.vstack( + [np.frombuffer(x, dtype=dtype, count=count) for x in self.planes] + ) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/frame.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/frame.pyi new file mode 100644 index 0000000000000000000000000000000000000000..6aeb86b4d27487dd44b6a8bd91f9998462c56af9 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/frame.pyi @@ -0,0 +1,49 @@ +from typing import Any, Union + +import numpy as np + +from av.frame import Frame + +from .format import AudioFormat +from .layout import AudioLayout +from .plane import AudioPlane + +format_dtypes: dict[str, str] +_SupportedNDarray = Union[ + np.ndarray[Any, np.dtype[np.float64]], # f8 + np.ndarray[Any, np.dtype[np.float32]], # f4 + np.ndarray[Any, np.dtype[np.int32]], # i4 + np.ndarray[Any, np.dtype[np.int16]], # i2 + np.ndarray[Any, np.dtype[np.uint8]], # u1 +] + +class _Format: + def __get__(self, i: object | None, owner: type | None = None) -> AudioFormat: ... + def __set__(self, instance: object, value: AudioFormat | str) -> None: ... + +class _Layout: + def __get__(self, i: object | None, owner: type | None = None) -> AudioLayout: ... + def __set__(self, instance: object, value: AudioLayout | str) -> None: ... + +class AudioFrame(Frame): + planes: tuple[AudioPlane, ...] + samples: int + sample_rate: int + rate: int + format: _Format + layout: _Layout + + def __init__( + self, + format: AudioFormat | str = "s16", + layout: AudioLayout | str = "stereo", + samples: int = 0, + align: int = 1, + ) -> None: ... + @staticmethod + def from_ndarray( + array: _SupportedNDarray, + format: AudioFormat | str = "s16", + layout: AudioLayout | str = "stereo", + ) -> AudioFrame: ... + def to_ndarray(self) -> _SupportedNDarray: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/layout.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/layout.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..7d4a6e4e0f0cffc115121ea6861453969b1dd888 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/layout.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5015781fef39b0d3cb9298b207e654c780a8e5ca9d6dfff1d184e47b927cfca8 +size 120880 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/layout.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/layout.pxd new file mode 100644 index 0000000000000000000000000000000000000000..d7e991e95586e41b6cafe444aab25c010d20d330 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/layout.pxd @@ -0,0 +1,7 @@ +cimport libav as lib + + +cdef class AudioLayout: + cdef lib.AVChannelLayout layout + +cdef AudioLayout get_audio_layout(lib.AVChannelLayout c_layout) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/layout.py b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/layout.py new file mode 100644 index 0000000000000000000000000000000000000000..f3adb3514c5eeecc5f03d3f39e49c0c18fd55ee2 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/layout.py @@ -0,0 +1,109 @@ +from dataclasses import dataclass + +import cython +from cython.cimports import libav as lib +from cython.cimports.cpython.bytes import PyBytes_FromStringAndSize + + +@dataclass +class AudioChannel: + name: str + description: str + + def __repr__(self): + return f"" + + +_cinit_bypass_sentinel = cython.declare(object, object()) + + +@cython.cfunc +def get_audio_layout(c_layout: lib.AVChannelLayout) -> AudioLayout: + """Get an AudioLayout from Cython land.""" + layout: AudioLayout = AudioLayout(_cinit_bypass_sentinel) + layout.layout = c_layout + return layout + + +@cython.final +@cython.cclass +class AudioLayout: + def __dealloc__(self): + lib.av_channel_layout_uninit(cython.address(self.layout)) + + def __cinit__(self, layout): + if layout is _cinit_bypass_sentinel: + return + + if type(layout) is str: + ret = lib.av_channel_layout_from_string(cython.address(c_layout), layout) + if ret != 0: + raise ValueError(f"Invalid layout: {layout}") + elif isinstance(layout, AudioLayout): + c_layout = cython.cast(AudioLayout, layout).layout + else: + raise TypeError( + f"layout must be of type: string | av.AudioLayout, got {type(layout)}" + ) + + self.layout = c_layout + + def __repr__(self): + return f"" + + def __eq__(self, other): + if not isinstance(other, AudioLayout): + return False + c_other: lib.AVChannelLayout = cython.cast(AudioLayout, other).layout + return ( + lib.av_channel_layout_compare( + cython.address(self.layout), cython.address(c_other) + ) + == 0 + ) + + @property + def nb_channels(self): + return self.layout.nb_channels + + @property + def channels(self): + buf: cython.char[16] + buf2: cython.char[128] + + results: list = [] + for index in range(self.layout.nb_channels): + size = lib.av_channel_name( + buf, + cython.sizeof(buf), + lib.av_channel_layout_channel_from_index( + cython.address(self.layout), index + ), + ) + size2 = lib.av_channel_description( + buf2, + cython.sizeof(buf2), + lib.av_channel_layout_channel_from_index( + cython.address(self.layout), index + ), + ) + results.append( + AudioChannel( + PyBytes_FromStringAndSize(buf, size - 1).decode("utf-8"), + PyBytes_FromStringAndSize(buf2, size2 - 1).decode("utf-8"), + ) + ) + + return tuple(results) + + @property + def name(self) -> str: + """The canonical name of the audio layout.""" + layout_name: cython.char[129] + ret: cython.int = lib.av_channel_layout_describe( + cython.address(self.layout), layout_name, cython.sizeof(layout_name) + ) + if ret < 0: + raise RuntimeError(f"Failed to get layout name: {ret}") + + return layout_name diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/layout.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/layout.pyi new file mode 100644 index 0000000000000000000000000000000000000000..9fdf0ac1531ff4d74ac60ee191ab899755443cf1 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/layout.pyi @@ -0,0 +1,12 @@ +from dataclasses import dataclass + +class AudioLayout: + name: str + nb_channels: int + channels: tuple[AudioChannel, ...] + def __init__(self, layout: str | AudioLayout): ... + +@dataclass +class AudioChannel: + name: str + description: str diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/plane.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/plane.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..0d4c3f437734d9edab252777feee2447ecb744dd Binary files /dev/null and b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/plane.cpython-310-darwin.so differ diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/plane.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/plane.pxd new file mode 100644 index 0000000000000000000000000000000000000000..de912ac2216b9ff4e9bf3bdda8ee93763e870a4c --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/plane.pxd @@ -0,0 +1,6 @@ +from av.plane cimport Plane + + +cdef class AudioPlane(Plane): + cdef readonly size_t buffer_size + cdef size_t _buffer_size(self) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/plane.py b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/plane.py new file mode 100644 index 0000000000000000000000000000000000000000..e68b5403e1b2d494a1b7d23e3a90ba9bc265d6a3 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/plane.py @@ -0,0 +1,14 @@ +import cython +from cython.cimports.av.audio.frame import AudioFrame + + +@cython.final +@cython.cclass +class AudioPlane(Plane): + def __cinit__(self, frame: AudioFrame, index: cython.int): + # Only the first linesize is ever populated, but it applies to every plane. + self.buffer_size = self.frame.ptr.linesize[0] + + @cython.cfunc + def _buffer_size(self) -> cython.size_t: + return self.buffer_size diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/plane.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/plane.pyi new file mode 100644 index 0000000000000000000000000000000000000000..64524dcdb5170ebfbee5aaafff86f37f9a476444 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/plane.pyi @@ -0,0 +1,4 @@ +from av.plane import Plane + +class AudioPlane(Plane): + buffer_size: int diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/resampler.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/resampler.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..ebe6961b347a7878c34e704ed056361809910a4a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/resampler.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70654aa868175176f702a4f11062a6e940563c801cea995f1d07ec58a2df290d +size 133200 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/resampler.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/resampler.pxd new file mode 100644 index 0000000000000000000000000000000000000000..20b74186ed1e80fcb879f5f341a7e10e6846825b --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/resampler.pxd @@ -0,0 +1,18 @@ +from av.audio.format cimport AudioFormat +from av.audio.frame cimport AudioFrame +from av.audio.layout cimport AudioLayout +from av.filter.graph cimport Graph + + +cdef class AudioResampler: + cdef readonly bint is_passthrough + cdef AudioFrame template + + # Destination descriptors + cdef readonly AudioFormat format + cdef readonly AudioLayout layout + cdef readonly int rate + cdef readonly unsigned int frame_size + + cdef Graph graph + cpdef list resample(self, AudioFrame) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/resampler.py b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/resampler.py new file mode 100644 index 0000000000000000000000000000000000000000..fb6d989108854fb9767b8c4ac7b44522b723d8cc --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/resampler.py @@ -0,0 +1,122 @@ +from errno import EAGAIN + +import cython +from cython.cimports.av.filter.graph import Graph + +from av.error import FFmpegError + + +@cython.final +@cython.cclass +class AudioResampler: + """AudioResampler(format=None, layout=None, rate=None) + + :param AudioFormat format: The target format, or string that parses to one + (e.g. ``"s16"``). + :param AudioLayout layout: The target layout, or an int/string that parses + to one (e.g. ``"stereo"``). + :param int rate: The target sample rate. + """ + + def __cinit__(self, format=None, layout=None, rate=None, frame_size=None): + if format is not None: + self.format = ( + format if isinstance(format, AudioFormat) else AudioFormat(format) + ) + + if layout is not None: + self.layout = AudioLayout(layout) + + self.rate = int(rate) if rate else 0 + self.frame_size = int(frame_size) if frame_size else 0 + self.graph = None + + @cython.ccall + def resample(self, frame: AudioFrame | None) -> list: + """resample(frame) + + Convert the ``sample_rate``, ``channel_layout`` and/or ``format`` of + a :class:`~.AudioFrame`. + + :param AudioFrame frame: The frame to convert or `None` to flush. + :returns: A list of :class:`AudioFrame` in new parameters. If the nothing is to be done return the same frame + as a single element list. + + """ + # We don't have any input, so don't bother even setting up. + if not self.graph and frame is None: + return [] + + # Shortcut for passthrough. + if self.is_passthrough: + return [frame] + + # Take source settings from the first frame. + if not self.graph: + self.template = frame + + # Set some default descriptors. + self.format = self.format or frame.format + self.layout = self.layout or frame.layout + self.rate = self.rate or frame.sample_rate + + # Check if we can passthrough or if there is actually work to do. + if ( + frame.format.sample_fmt == self.format.sample_fmt + and frame.layout == self.layout + and frame.sample_rate == self.rate + and self.frame_size == 0 + ): + self.is_passthrough = True + return [frame] + + # handle resampling with aformat filter + # (similar to configure_output_audio_filter from ffmpeg) + self.graph = Graph() + extra_args = {} + if frame.time_base is not None: + extra_args["time_base"] = f"{frame.time_base}" + + abuffer = self.graph.add( + "abuffer", + sample_rate=f"{frame.sample_rate}", + sample_fmt=AudioFormat(frame.format).name, + channel_layout=frame.layout.name, + **extra_args, + ) + aformat = self.graph.add( + "aformat", + sample_rates=f"{self.rate}", + sample_fmts=self.format.name, + channel_layouts=self.layout.name, + ) + abuffersink = self.graph.add("abuffersink") + abuffer.link_to(aformat) + aformat.link_to(abuffersink) + self.graph.configure() + + if self.frame_size > 0: + self.graph.set_audio_frame_size(self.frame_size) + + if frame is not None: + if ( + frame.format.sample_fmt != self.template.format.sample_fmt + or frame.layout != self.template.layout + or frame.sample_rate != self.template.rate + ): + raise ValueError("Frame does not match AudioResampler setup.") + + self.graph.push(frame) + + output: list = [] + while True: + try: + output.append(self.graph.pull()) + except EOFError: + break + except FFmpegError as e: + if e.errno != EAGAIN: + raise + break + + return output diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/resampler.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/resampler.pyi new file mode 100644 index 0000000000000000000000000000000000000000..cbf2134aa13911ebf13c7e2e676fadb96230f530 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/resampler.pyi @@ -0,0 +1,20 @@ +from av.filter.graph import Graph + +from .format import AudioFormat +from .frame import AudioFrame +from .layout import AudioLayout + +class AudioResampler: + rate: int + frame_size: int + format: AudioFormat + graph: Graph | None + + def __init__( + self, + format: str | int | AudioFormat | None = None, + layout: str | int | AudioLayout | None = None, + rate: int | None = None, + frame_size: int | None = None, + ) -> None: ... + def resample(self, frame: AudioFrame | None) -> list[AudioFrame]: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/stream.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/stream.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..72b5767e569bbf58a6a0a3a062a41ea939b84096 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/stream.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e81f7c9123dc416c3f91dba0a8e5eb6bde78bfc37d1ac1bde4817aa5b8fc158f +size 114192 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/stream.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/stream.pxd new file mode 100644 index 0000000000000000000000000000000000000000..8462061f8e0da6cd418527a73ac80d8ce59d7f0e --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/stream.pxd @@ -0,0 +1,9 @@ +from av.packet cimport Packet +from av.stream cimport Stream + +from .frame cimport AudioFrame + + +cdef class AudioStream(Stream): + cpdef encode(self, AudioFrame frame=?) + cpdef decode(self, Packet packet=?) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/stream.py b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/stream.py new file mode 100644 index 0000000000000000000000000000000000000000..38009db199591341eca77f637dd76eb78fdeaace --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/stream.py @@ -0,0 +1,53 @@ +import cython +from cython.cimports.av.audio.frame import AudioFrame +from cython.cimports.av.packet import Packet + + +@cython.final +@cython.cclass +class AudioStream(Stream): + def __repr__(self): + if self.codec_context is None: + return f" at 0x{id(self):x}>" + form = self.format.name if self.format else None + return ( + f"" + ) + + def __getattr__(self, name): + if self.codec_context is None: + raise AttributeError( + f"'{type(self).__name__}' object has no attribute '{name}'" + ) + return getattr(self.codec_context, name) + + @cython.ccall + def encode(self, frame: AudioFrame | None = None): + """ + Encode an :class:`.AudioFrame` and return a list of :class:`.Packet`. + + :rtype: list[Packet] + + .. seealso:: This is mostly a passthrough to :meth:`.CodecContext.encode`. + """ + + packets = self.codec_context.encode(frame) + packet: Packet + for packet in packets: + packet._stream = self + packet.ptr.stream_index = self.ptr.index + + return packets + + @cython.ccall + def decode(self, packet: Packet | None = None): + """ + Decode a :class:`.Packet` and return a list of :class:`.AudioFrame`. + + :rtype: list[AudioFrame] + + .. seealso:: This is a passthrough to :meth:`.CodecContext.decode`. + """ + + return self.codec_context.decode(packet) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/audio/stream.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/stream.pyi new file mode 100644 index 0000000000000000000000000000000000000000..f92fb52ba2a941f1f539b690bb3b26fc434a8ec3 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/audio/stream.pyi @@ -0,0 +1,32 @@ +from typing import Literal + +from av.packet import Packet +from av.stream import Stream + +from .codeccontext import AudioCodecContext +from .format import AudioFormat +from .frame import AudioFrame +from .layout import AudioLayout + +class _Format: + def __get__(self, i: object | None, owner: type | None = None) -> AudioFormat: ... + def __set__(self, instance: object, value: AudioFormat | str) -> None: ... + +class _Layout: + def __get__(self, i: object | None, owner: type | None = None) -> AudioLayout: ... + def __set__(self, instance: object, value: AudioLayout | str) -> None: ... + +class AudioStream(Stream): + codec_context: AudioCodecContext + def encode(self, frame: AudioFrame | None = None) -> list[Packet]: ... + def decode(self, packet: Packet | None = None) -> list[AudioFrame]: ... + + # From codec context + frame_size: int + sample_rate: int + bit_rate: int + rate: int + channels: int + type: Literal["audio"] + format: _Format + layout: _Layout diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/bitstream.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/bitstream.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..356cb0aaef960dd608772861021d1f653d6656a5 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/bitstream.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce6512a50453826b27144f265b676b9c213a60dd04bf12e76e9b5bbf342c6ca3 +size 112608 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/bitstream.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/bitstream.pxd new file mode 100644 index 0000000000000000000000000000000000000000..dbb89c9844551a1b17f839e03604b7821294f1a0 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/bitstream.pxd @@ -0,0 +1,11 @@ +cimport libav as lib + +from av.packet cimport Packet + + +cdef class BitStreamFilterContext: + + cdef lib.AVBSFContext *ptr + + cpdef filter(self, Packet packet=?) + cpdef flush(self) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/bitstream.py b/.venvs/kimodo/lib/python3.10/site-packages/av/bitstream.py new file mode 100644 index 0000000000000000000000000000000000000000..db808ab350cdd839f04a58637b6f3d93ebac1aa3 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/bitstream.py @@ -0,0 +1,116 @@ +import cython +import cython.cimports.libav as lib +from cython.cimports.av.error import err_check +from cython.cimports.av.packet import Packet +from cython.cimports.av.stream import Stream +from cython.cimports.libc.errno import EAGAIN + + +@cython.final +@cython.cclass +class BitStreamFilterContext: + """ + Initializes a bitstream filter: a way to directly modify packet data. + + Wraps :ffmpeg:`AVBSFContext` + + :param Stream in_stream: A stream that defines the input codec for the bitfilter. + :param Stream out_stream: A stream whose codec is overwritten using the output parameters from the bitfilter. + """ + + def __cinit__( + self, + filter_description, + in_stream: Stream | None = None, + out_stream: Stream | None = None, + ): + res: cython.int + filter_str: cython.p_char = filter_description + + with cython.nogil: + res = lib.av_bsf_list_parse_str(filter_str, cython.address(self.ptr)) + err_check(res) + + if in_stream is not None: + with cython.nogil: + res = lib.avcodec_parameters_copy( + self.ptr.par_in, in_stream.ptr.codecpar + ) + err_check(res) + + with cython.nogil: + res = lib.av_bsf_init(self.ptr) + err_check(res) + + if out_stream is not None: + with cython.nogil: + res = lib.avcodec_parameters_copy( + out_stream.ptr.codecpar, self.ptr.par_out + ) + err_check(res) + lib.avcodec_parameters_to_context( + out_stream.codec_context.ptr, out_stream.ptr.codecpar + ) + + def __dealloc__(self): + if self.ptr: + lib.av_bsf_free(cython.address(self.ptr)) + + @cython.ccall + def filter(self, packet: Packet | None = None): + """ + Processes a packet based on the filter_description set during initialization. + Multiple packets may be created. + + :type: list[Packet] + """ + res: cython.int + new_packet: Packet + + with cython.nogil: + res = lib.av_bsf_send_packet( + self.ptr, packet.ptr if packet is not None else cython.NULL + ) + err_check(res) + + output: list = [] + while True: + new_packet = Packet() + with cython.nogil: + res = lib.av_bsf_receive_packet(self.ptr, new_packet.ptr) + + if res == -EAGAIN or res == lib.AVERROR_EOF: + return output + + err_check(res) + if res: + return output + + output.append(new_packet) + + @cython.ccall + def flush(self): + """ + Reset the internal state of the filter. + Should be called e.g. when seeking. + Can be used to make the filter usable again after draining it with EOF marker packet. + """ + lib.av_bsf_flush(self.ptr) + + +@cython.cfunc +def get_filter_names() -> set: + names: set = set() + ptr: cython.pointer[cython.const[lib.AVBitStreamFilter]] + opaque: cython.p_void = cython.NULL + while True: + ptr = lib.av_bsf_iterate(cython.address(opaque)) + if ptr: + names.add(ptr.name) + else: + break + + return names + + +bitstream_filters_available = get_filter_names() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/bitstream.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/bitstream.pyi new file mode 100644 index 0000000000000000000000000000000000000000..477c65f2d2356b2f9fd160d24af5b10cfae39c7f --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/bitstream.pyi @@ -0,0 +1,14 @@ +from .packet import Packet +from .stream import Stream + +class BitStreamFilterContext: + def __init__( + self, + filter_description: str | bytes, + in_stream: Stream | None = None, + out_stream: Stream | None = None, + ): ... + def filter(self, packet: Packet | None) -> list[Packet]: ... + def flush(self) -> None: ... + +bitstream_filters_available: set[str] diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/buffer.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/buffer.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..de96f8224043651cadfd0cba4527f105a8956d6e --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/buffer.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e8aa54ab1fb627715e271e5b929364560f8aa56a38c2c2957c21ec5530c3064 +size 116576 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/buffer.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/buffer.pxd new file mode 100644 index 0000000000000000000000000000000000000000..07ae34ae512fff2cfbc2dab7dd1222fe6946bd03 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/buffer.pxd @@ -0,0 +1,16 @@ +from cpython.buffer cimport Py_buffer + + +cdef class ByteSource: + cdef object owner + cdef bint has_view + cdef Py_buffer view + cdef unsigned char *ptr + cdef size_t length + +cdef ByteSource bytesource(object, bint allow_none=*) + +cdef class Buffer: + cdef size_t _buffer_size(self) + cdef void* _buffer_ptr(self) + cdef bint _buffer_writable(self) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/buffer.py b/.venvs/kimodo/lib/python3.10/site-packages/av/buffer.py new file mode 100644 index 0000000000000000000000000000000000000000..3fefceee46f11c16b7e61f53f83f9dbd47881eab --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/buffer.py @@ -0,0 +1,100 @@ +import cython +from cython.cimports.cpython import PyBUF_WRITABLE, PyBuffer_FillInfo +from cython.cimports.cpython.buffer import ( + PyBUF_SIMPLE, + PyBuffer_Release, + PyObject_CheckBuffer, + PyObject_GetBuffer, +) +from cython.cimports.libc.string import memcpy + + +@cython.final +@cython.cclass +class ByteSource: + def __cinit__(self, owner): + self.owner = owner + + try: + self.ptr = owner + except TypeError: + pass + else: + self.length = len(owner) + return + + if PyObject_CheckBuffer(owner): + # Can very likely use PyBUF_ND instead of PyBUF_SIMPLE + res = PyObject_GetBuffer(owner, cython.address(self.view), PyBUF_SIMPLE) + if not res: + self.has_view = True + self.ptr = cython.cast(cython.p_uchar, self.view.buf) + self.length = self.view.len + return + + raise TypeError("expected bytes, bytearray or memoryview") + + def __dealloc__(self): + if self.has_view: + PyBuffer_Release(cython.address(self.view)) + + +@cython.cfunc +def bytesource(obj, allow_none: cython.bint = False) -> ByteSource | None: + if allow_none and obj is None: + return None + elif isinstance(obj, ByteSource): + return obj + else: + return ByteSource(obj) + + +@cython.cclass +class Buffer: + """A base class for PyAV objects which support the buffer protocol, such + as :class:`.Packet` and :class:`.Plane`. + + """ + + @cython.cfunc + def _buffer_size(self) -> cython.size_t: + return 0 + + def _buffer_ptr(self) -> cython.p_void: + return cython.NULL + + def _buffer_writable(self) -> cython.bint: + return True + + def __getbuffer__(self, view: cython.pointer[Py_buffer], flags: cython.int): + if flags & PyBUF_WRITABLE and not self._buffer_writable(): + raise ValueError("buffer is not writable") + + PyBuffer_FillInfo(view, self, self._buffer_ptr(), self._buffer_size(), 0, flags) + + @property + def buffer_size(self): + return self._buffer_size() + + @property + def buffer_ptr(self): + """The memory address of the buffer.""" + return cython.cast(cython.size_t, self._buffer_ptr()) + + def update(self, input): + """Replace the data in this object with the given buffer. + + Accepts anything that supports the `buffer protocol `_, + e.g. bytes, NumPy arrays, other :class:`Buffer` objects, etc.. + + """ + if not self._buffer_writable(): + raise ValueError("buffer is not writable") + + source: ByteSource = bytesource(input) + size: cython.size_t = self._buffer_size() + + if source.length != size: + raise ValueError(f"got {source.length} bytes; need {size} bytes") + + memcpy(self._buffer_ptr(), source.ptr, size) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/buffer.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/buffer.pyi new file mode 100644 index 0000000000000000000000000000000000000000..bc1090d1deac54dcfc364fea49ee31bca526c6f8 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/buffer.pyi @@ -0,0 +1,9 @@ +# When Python 3.12 becomes our lowest supported version, we could make this +# class inherit `collections.abc.Buffer`. + +class Buffer: + buffer_size: int + buffer_ptr: int + def update(self, input: bytes) -> None: ... + def __buffer__(self, flags: int) -> memoryview: ... + def __bytes__(self) -> bytes: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/__init__.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/__init__.pxd new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/__init__.py b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f8d4d75ae27afdf21c804ac122939d88c141ddd9 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/__init__.py @@ -0,0 +1,17 @@ +from .codec import ( + Capabilities, + Codec, + Properties, + codecs_available, + find_best_pix_fmt_of_list, +) +from .context import CodecContext + +__all__ = ( + "Capabilities", + "Codec", + "Properties", + "codecs_available", + "find_best_pix_fmt_of_list", + "CodecContext", +) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/codec.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/codec.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..b8a64223ad5269b753846011f51177180ace8248 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/codec.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed720d72b8aef8b75c90c19e209af04e627b1a9b13ef9e2ecc055de2f80d0bdc +size 155760 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/codec.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/codec.pxd new file mode 100644 index 0000000000000000000000000000000000000000..576c659b46939204543b2313f33de1d6ae3ac2dd --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/codec.pxd @@ -0,0 +1,15 @@ +cimport libav as lib + + +cdef class Codec: + + cdef const lib.AVCodec *ptr + cdef const lib.AVCodecDescriptor *desc + cdef readonly bint is_encoder + + cdef tuple _hardware_configs + + cdef _init(self, name=?) + + +cdef Codec wrap_codec(const lib.AVCodec *ptr) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/codec.py b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/codec.py new file mode 100644 index 0000000000000000000000000000000000000000..aff456ecbfce79f9ac345a36ab8f9225c66e00ce --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/codec.py @@ -0,0 +1,476 @@ +from enum import Flag, IntEnum + +import cython +from cython.cimports import libav as lib +from cython.cimports.av.audio.format import get_audio_format +from cython.cimports.av.codec.hwaccel import wrap_hwconfig +from cython.cimports.av.utils import avrational_to_fraction +from cython.cimports.av.video.format import VideoFormat, get_pix_fmt, get_video_format +from cython.cimports.libc.stdlib import free, malloc + +_cinit_sentinel = cython.declare(object, object()) + + +@cython.cfunc +def wrap_codec(ptr: cython.pointer[cython.const[lib.AVCodec]]) -> Codec: + codec: Codec = Codec(_cinit_sentinel) + codec.ptr = ptr + codec.is_encoder = lib.av_codec_is_encoder(ptr) + codec._init() + return codec + + +class Properties(Flag): + NONE = 0 + INTRA_ONLY = lib.AV_CODEC_PROP_INTRA_ONLY + LOSSY = lib.AV_CODEC_PROP_LOSSY + LOSSLESS = lib.AV_CODEC_PROP_LOSSLESS + REORDER = lib.AV_CODEC_PROP_REORDER + BITMAP_SUB = lib.AV_CODEC_PROP_BITMAP_SUB + TEXT_SUB = lib.AV_CODEC_PROP_TEXT_SUB + + +class Capabilities(IntEnum): + none = 0 + draw_horiz_band = lib.AV_CODEC_CAP_DRAW_HORIZ_BAND + dr1 = lib.AV_CODEC_CAP_DR1 + hwaccel = 1 << 4 + delay = lib.AV_CODEC_CAP_DELAY + small_last_frame = lib.AV_CODEC_CAP_SMALL_LAST_FRAME + hwaccel_vdpau = 1 << 7 + experimental = lib.AV_CODEC_CAP_EXPERIMENTAL + channel_conf = lib.AV_CODEC_CAP_CHANNEL_CONF + neg_linesizes = 1 << 11 + frame_threads = lib.AV_CODEC_CAP_FRAME_THREADS + slice_threads = lib.AV_CODEC_CAP_SLICE_THREADS + param_change = lib.AV_CODEC_CAP_PARAM_CHANGE + auto_threads = lib.AV_CODEC_CAP_OTHER_THREADS + variable_frame_size = lib.AV_CODEC_CAP_VARIABLE_FRAME_SIZE + avoid_probing = lib.AV_CODEC_CAP_AVOID_PROBING + hardware = lib.AV_CODEC_CAP_HARDWARE + hybrid = lib.AV_CODEC_CAP_HYBRID + encoder_reordered_opaque = 1 << 20 + encoder_flush = 1 << 21 + encoder_recon_frame = 1 << 22 + + +class UnknownCodecError(ValueError): + pass + + +@cython.final +@cython.cclass +class Codec: + """Codec(name, mode='r') + + :param str name: The codec name. + :param str mode: ``'r'`` for decoding or ``'w'`` for encoding. + + This object exposes information about an available codec, and an avenue to + create a :class:`.CodecContext` to encode/decode directly. + + :: + + >>> codec = Codec('mpeg4', 'r') + >>> codec.name + 'mpeg4' + >>> codec.type + 'video' + >>> codec.is_encoder + False + + """ + + def __cinit__(self, name, mode="r"): + if name is _cinit_sentinel: + return + + if mode == "w": + self.ptr = lib.avcodec_find_encoder_by_name(name) + if not self.ptr: + self.desc = lib.avcodec_descriptor_get_by_name(name) + if self.desc: + self.ptr = lib.avcodec_find_encoder(self.desc.id) + + elif mode == "r": + self.ptr = lib.avcodec_find_decoder_by_name(name) + if not self.ptr: + self.desc = lib.avcodec_descriptor_get_by_name(name) + if self.desc: + self.ptr = lib.avcodec_find_decoder(self.desc.id) + + else: + raise ValueError('Invalid mode; must be "r" or "w".', mode) + + self._init(name) + + # Sanity check. + if (mode == "w") != self.is_encoder: + raise RuntimeError("Found codec does not match mode.", name, mode) + + @cython.cfunc + def _init(self, name=None): + if not self.ptr: + raise UnknownCodecError(name) + + if not self.desc: + self.desc = lib.avcodec_descriptor_get(self.ptr.id) + if not self.desc: + raise RuntimeError("No codec descriptor for %r." % name) + + self.is_encoder = lib.av_codec_is_encoder(self.ptr) + + # Sanity check. + if self.is_encoder and lib.av_codec_is_decoder(self.ptr): + raise RuntimeError("%s is both encoder and decoder.") + + def __repr__(self): + mode = self.mode + return f"" + + def create(self, kind=None): + """Create a :class:`.CodecContext` for this codec. + + :param str kind: Gives a hint to static type checkers for what exact CodecContext is used. + """ + from .context import CodecContext + + return CodecContext.create(self) + + @property + def mode(self): + return "w" if self.is_encoder else "r" + + @property + def is_decoder(self): + return not self.is_encoder + + @property + def name(self): + return self.ptr.name or "" + + @property + def canonical_name(self): + """ + Returns the name of the codec, not a specific encoder. + """ + return lib.avcodec_get_name(self.ptr.id) + + @property + def long_name(self): + return self.ptr.long_name or "" + + @property + def type(self): + """ + The media type of this codec. + + E.g: ``'audio'``, ``'video'``, ``'subtitle'``. + + """ + media_type = lib.av_get_media_type_string(self.ptr.type) + return "unknown" if media_type == cython.NULL else media_type + + @property + def id(self): + return self.ptr.id + + @property + def frame_rates(self): + """A list of supported frame rates (:class:`fractions.Fraction`), or ``None``.""" + out: cython.pointer[cython.const[cython.void]] = cython.NULL + num: cython.int = 0 + lib.avcodec_get_supported_config( + cython.NULL, + self.ptr, + lib.AV_CODEC_CONFIG_FRAME_RATE, + 0, + cython.address(out), + cython.address(num), + ) + if not out: + return + rates = cython.cast(cython.pointer[lib.AVRational], out) + return [avrational_to_fraction(cython.address(rates[i])) for i in range(num)] + + @property + def audio_rates(self): + """A list of supported audio sample rates (``int``), or ``None``.""" + out: cython.pointer[cython.const[cython.void]] = cython.NULL + num: cython.int = 0 + lib.avcodec_get_supported_config( + cython.NULL, + self.ptr, + lib.AV_CODEC_CONFIG_SAMPLE_RATE, + 0, + cython.address(out), + cython.address(num), + ) + if not out: + return + rates = cython.cast(cython.pointer[cython.int], out) + return [rates[i] for i in range(num)] + + @property + def video_formats(self): + """A list of supported :class:`.VideoFormat`, or ``None``.""" + out: cython.pointer[cython.const[cython.void]] = cython.NULL + num: cython.int = 0 + lib.avcodec_get_supported_config( + cython.NULL, + self.ptr, + lib.AV_CODEC_CONFIG_PIX_FORMAT, + 0, + cython.address(out), + cython.address(num), + ) + if not out: + return + fmts = cython.cast(cython.pointer[lib.AVPixelFormat], out) + return [get_video_format(fmts[i], 0, 0) for i in range(num)] + + @property + def audio_formats(self): + """A list of supported :class:`.AudioFormat`, or ``None``.""" + out: cython.pointer[cython.const[cython.void]] = cython.NULL + num: cython.int = 0 + lib.avcodec_get_supported_config( + cython.NULL, + self.ptr, + lib.AV_CODEC_CONFIG_SAMPLE_FORMAT, + 0, + cython.address(out), + cython.address(num), + ) + if not out: + return + fmts = cython.cast(cython.pointer[lib.AVSampleFormat], out) + return [get_audio_format(fmts[i]) for i in range(num)] + + @property + def hardware_configs(self): + if self._hardware_configs: + return self._hardware_configs + ret: list = [] + i: cython.int = 0 + ptr: cython.pointer[cython.const[lib.AVCodecHWConfig]] + while True: + ptr = lib.avcodec_get_hw_config(self.ptr, i) + if not ptr: + break + ret.append(wrap_hwconfig(ptr)) + i += 1 + self._hardware_configs = tuple(ret) + return self._hardware_configs + + @property + def properties(self): + return self.desc.props + + @property + def intra_only(self): + return bool(self.desc.props & lib.AV_CODEC_PROP_INTRA_ONLY) + + @property + def lossy(self): + return bool(self.desc.props & lib.AV_CODEC_PROP_LOSSY) + + @property + def lossless(self): + return bool(self.desc.props & lib.AV_CODEC_PROP_LOSSLESS) + + @property + def reorder(self): + return bool(self.desc.props & lib.AV_CODEC_PROP_REORDER) + + @property + def bitmap_sub(self): + return bool(self.desc.props & lib.AV_CODEC_PROP_BITMAP_SUB) + + @property + def text_sub(self): + return bool(self.desc.props & lib.AV_CODEC_PROP_TEXT_SUB) + + @property + def capabilities(self): + """ + Get the capabilities bitmask of the codec. + + This method returns an integer representing the codec capabilities bitmask, + which can be used to check specific codec features by performing bitwise + operations with the Capabilities enum values. + + :example: + + .. code-block:: python + + from av.codec import Codec, Capabilities + + codec = Codec("h264", "w") + + # Check if the codec can be fed a final frame with a smaller size. + # This can be used to prevent truncation of the last audio samples. + small_last_frame = bool(codec.capabilities & Capabilities.small_last_frame) + + :rtype: int + """ + return self.ptr.capabilities + + @property + def experimental(self): + """ + Check if codec is experimental and is thus avoided in favor of non experimental encoders. + + :rtype: bool + """ + return bool(self.ptr.capabilities & lib.AV_CODEC_CAP_EXPERIMENTAL) + + @property + def delay(self): + """ + If true, encoder or decoder requires flushing with `None` at the end in order to give the complete and correct output. + + :rtype: bool + """ + return bool(self.ptr.capabilities & lib.AV_CODEC_CAP_DELAY) + + +@cython.cfunc +def get_codec_names(): + names: cython.set = set() + ptr = cython.declare(cython.pointer[cython.const[lib.AVCodec]]) + opaque: cython.p_void = cython.NULL + while True: + ptr = lib.av_codec_iterate(cython.address(opaque)) + if ptr: + names.add(ptr.name) + else: + break + return names + + +codecs_available = get_codec_names() + + +def dump_codecs(): + """Print information about available codecs.""" + + print( + """Codecs: + D..... = Decoding supported + .E.... = Encoding supported + ..V... = Video codec + ..A... = Audio codec + ..S... = Subtitle codec + ...I.. = Intra frame-only codec + ....L. = Lossy compression + .....S = Lossless compression + ------""" + ) + + for name in sorted(codecs_available): + try: + e_codec = Codec(name, "w") + except ValueError: + e_codec = None + + try: + d_codec = Codec(name, "r") + except ValueError: + d_codec = None + + # TODO: Assert these always have the same properties. + codec = e_codec or d_codec + + try: + print( + " %s%s%s%s%s%s %-18s %s" + % ( + ".D"[bool(d_codec)], + ".E"[bool(e_codec)], + codec.type[0].upper(), + ".I"[codec.intra_only], + ".L"[codec.lossy], + ".S"[codec.lossless], + codec.name, + codec.long_name, + ) + ) + except Exception as e: + print(f"...... {codec.name:<18} ERROR: {e}") + + +def dump_hwconfigs(): + print("Hardware configs:") + for name in sorted(codecs_available): + try: + codec = Codec(name, "r") + except ValueError: + continue + + configs = codec.hardware_configs + if not configs: + continue + + print(" ", codec.name) + for config in configs: + print(" ", config) + + +def find_best_pix_fmt_of_list(pix_fmts, src_pix_fmt, has_alpha=False): + """ + Find the best pixel format to convert to given a source format. + + Wraps :ffmpeg:`avcodec_find_best_pix_fmt_of_list`. + + :param pix_fmts: Iterable of pixel formats to choose from (str or VideoFormat). + :param src_pix_fmt: Source pixel format (str or VideoFormat). + :param bool has_alpha: Whether the source alpha channel is used. + :return: (best_format, loss) + :rtype: (VideoFormat | None, int) + """ + src: lib.AVPixelFormat + best: lib.AVPixelFormat + c_list: cython.pointer[lib.AVPixelFormat] = cython.NULL + n: cython.Py_ssize_t + i: cython.Py_ssize_t + item: object + c_loss: cython.int + + if pix_fmts is None: + raise TypeError("pix_fmts must not be None") + + pix_fmts = tuple(pix_fmts) + if not pix_fmts: + return None, 0 + + if isinstance(src_pix_fmt, VideoFormat): + src = cython.cast(VideoFormat, src_pix_fmt).pix_fmt + else: + src = get_pix_fmt(cython.cast(str, src_pix_fmt)) + + n = len(pix_fmts) + c_list = cython.cast( + cython.pointer[lib.AVPixelFormat], + malloc((n + 1) * cython.sizeof(lib.AVPixelFormat)), + ) + if c_list == cython.NULL: + raise MemoryError() + + try: + for i in range(n): + item = pix_fmts[i] + if isinstance(item, VideoFormat): + c_list[i] = cython.cast(VideoFormat, item).pix_fmt + else: + c_list[i] = get_pix_fmt(cython.cast(str, item)) + c_list[n] = lib.AV_PIX_FMT_NONE + + c_loss = 0 + best = lib.avcodec_find_best_pix_fmt_of_list( + c_list, src, 1 if has_alpha else 0, cython.address(c_loss) + ) + return get_video_format(best, 0, 0), c_loss + finally: + if c_list != cython.NULL: + free(c_list) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/codec.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/codec.pyi new file mode 100644 index 0000000000000000000000000000000000000000..1459bc77763ae20ce14bb2d74aa5ed3c0452a5a4 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/codec.pyi @@ -0,0 +1,135 @@ +from enum import Flag, IntEnum +from fractions import Fraction +from typing import ClassVar, Literal, Sequence, cast, overload + +from av.audio.codeccontext import AudioCodecContext +from av.audio.format import AudioFormat +from av.subtitles.codeccontext import SubtitleCodecContext +from av.video.codeccontext import VideoCodecContext +from av.video.format import VideoFormat + +from .context import CodecContext + +class Properties(Flag): + NONE = cast(ClassVar[Properties], ...) + INTRA_ONLY = cast(ClassVar[Properties], ...) + LOSSY = cast(ClassVar[Properties], ...) + LOSSLESS = cast(ClassVar[Properties], ...) + REORDER = cast(ClassVar[Properties], ...) + BITMAP_SUB = cast(ClassVar[Properties], ...) + TEXT_SUB = cast(ClassVar[Properties], ...) + +class Capabilities(IntEnum): + none = cast(int, ...) + draw_horiz_band = cast(int, ...) + dr1 = cast(int, ...) + hwaccel = cast(int, ...) + delay = cast(int, ...) + small_last_frame = cast(int, ...) + hwaccel_vdpau = cast(int, ...) + subframes = cast(int, ...) + experimental = cast(int, ...) + channel_conf = cast(int, ...) + neg_linesizes = cast(int, ...) + frame_threads = cast(int, ...) + slice_threads = cast(int, ...) + param_change = cast(int, ...) + auto_threads = cast(int, ...) + variable_frame_size = cast(int, ...) + avoid_probing = cast(int, ...) + hardware = cast(int, ...) + hybrid = cast(int, ...) + encoder_reordered_opaque = cast(int, ...) + encoder_flush = cast(int, ...) + encoder_recon_frame = cast(int, ...) + +class UnknownCodecError(ValueError): ... + +class Codec: + @property + def is_encoder(self) -> bool: ... + @property + def is_decoder(self) -> bool: ... + @property + def mode(self) -> Literal["r", "w"]: ... + @property + def name(self) -> str: ... + @property + def canonical_name(self) -> str: ... + @property + def long_name(self) -> str: ... + @property + def type( + self, + ) -> Literal["video", "audio", "data", "subtitle", "attachment", "unknown"]: ... + @property + def id(self) -> int: ... + frame_rates: list[Fraction] | None + audio_rates: list[int] | None + video_formats: list[VideoFormat] | None + audio_formats: list[AudioFormat] | None + + @property + def properties(self) -> int: ... + @property + def intra_only(self) -> bool: ... + @property + def lossy(self) -> bool: ... + @property + def lossless(self) -> bool: ... + @property + def reorder(self) -> bool: ... + @property + def bitmap_sub(self) -> bool: ... + @property + def text_sub(self) -> bool: ... + @property + def capabilities(self) -> int: ... + @property + def experimental(self) -> bool: ... + @property + def delay(self) -> bool: ... + def __init__(self, name: str, mode: Literal["r", "w"] = "r") -> None: ... + @overload + def create(self, kind: Literal["video"]) -> VideoCodecContext: ... + @overload + def create(self, kind: Literal["audio"]) -> AudioCodecContext: ... + @overload + def create(self, kind: Literal["subtitle"]) -> SubtitleCodecContext: ... + @overload + def create(self, kind: None = None) -> CodecContext: ... + @overload + def create( + self, kind: Literal["video", "audio", "subtitle"] | None = None + ) -> ( + VideoCodecContext | AudioCodecContext | SubtitleCodecContext | CodecContext + ): ... + +codecs_available: set[str] + +def dump_codecs() -> None: ... +def dump_hwconfigs() -> None: ... + +PixFmtLike = str | VideoFormat + +def find_best_pix_fmt_of_list( + pix_fmts: Sequence[PixFmtLike], + src_pix_fmt: PixFmtLike, + has_alpha: bool = False, +) -> tuple[VideoFormat | None, int]: + """ + Find the best pixel format to convert to given a source format. + + Wraps :ffmpeg:`avcodec_find_best_pix_fmt_of_list`. + + :param pix_fmts: Iterable of pixel formats to choose from (str or VideoFormat). + :param src_pix_fmt: Source pixel format (str or VideoFormat). + :param bool has_alpha: Whether the source alpha channel is used. + :return: (best_format, loss): best_format is the best matching pixel format from + the list, or None if no suitable format was found; loss is Combination of flags informing you what kind of losses will occur. + :rtype: (VideoFormat | None, int) + + Note on loss: it is a bitmask of FFmpeg loss flags describing what kinds of information would be lost converting from src_pix_fmt to best_format (e.g. loss of alpha, chroma, colorspace, resolution, bit depth, etc.). Multiple losses can be present at once, so the value is meant to be interpreted with bitwise & against FFmpeg's FF_LOSS_* constants. + For exact behavior see: libavutil/pixdesc.c/get_pix_fmt_score() in ffmpeg source code. + """ + ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/context.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/context.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..2ea0fed0d79f6986ffb63f171cead96ff10cb8f4 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/context.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0aabc0090a1fd857932231c3e779b9d5004083b2929da194d2ee138e39df747 +size 201488 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/context.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/context.pxd new file mode 100644 index 0000000000000000000000000000000000000000..cc12836d728cc8992d1b0da0d7e48b38ce252210 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/context.pxd @@ -0,0 +1,64 @@ +cimport libav as lib +from libc.stdint cimport int64_t, uint8_t + +from av.buffer cimport ByteSource +from av.codec.codec cimport Codec +from av.codec.hwaccel cimport HWAccel +from av.frame cimport Frame +from av.packet cimport Packet + + +cdef class CodecContext: + cdef lib.AVCodecContext *ptr + + # Used as a signal that this is within a stream, and also for us to access that + # stream. This is set "manually" by the stream after constructing this object. + cdef int stream_index + + cdef lib.AVCodecParserContext *parser + cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel) + + # Public API. + cdef readonly bint is_open + cdef readonly Codec codec + cdef readonly HWAccel hwaccel + cdef public dict options + cpdef open(self, bint strict=?) + + # Wraps both versions of the transcode API, returning lists. + cpdef encode(self, Frame frame=?) + cpdef decode(self, Packet packet=?) + cpdef flush_buffers(self) + + # Used by hardware-accelerated decode. + cdef HWAccel hwaccel_ctx + + cdef uint8_t _ctxflags # ctxEnum: template_initialized + # True when created via add_stream_from_template(); start_encoding() skips + # avcodec_open2() and lets encode()/decode() open the codec lazily if needed. + + # Used by both transcode APIs to setup user-land objects. + # TODO: Remove the `Packet` from `_setup_decoded_frame` (because flushing packets + # are bogus). It should take all info it needs from the context and/or stream. + cdef _prepare_and_time_rebase_frames_for_encode(self, Frame frame) + cdef list _prepare_frames_for_encode(self, Frame frame) + cdef _setup_encoded_packet(self, Packet) + cdef _setup_decoded_frame(self, Frame, Packet) + + # Implemented by base for the generic send/recv API. + # Note that the user cannot send without receiving. This is because + # `_prepare_frames_for_encode` may expand a frame into multiple (e.g. when + # resampling audio to a higher rate but with fixed size frames), and the + # send/recv buffer may be limited to a single frame. Ergo, we need to flush + # the buffer as often as possible. + cdef _recv_packet(self) + cdef _recv_frame(self) + + cdef _transfer_hwframe(self, Frame frame) + + # Implemented by children for the generic send/recv API, so we have the + # correct subclass of Frame. + cdef Frame _next_frame + cdef Frame _alloc_next_frame(self) + +cdef CodecContext wrap_codec_context(lib.AVCodecContext*, const lib.AVCodec*, HWAccel hwaccel) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/context.py b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/context.py new file mode 100644 index 0000000000000000000000000000000000000000..2c82f2de8e2af545e1e5b8b73b626347dffefed7 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/context.py @@ -0,0 +1,747 @@ +from enum import Flag, IntEnum + +import cython +from cython.cimports import libav as lib +from cython.cimports.av.buffer import ByteSource, bytesource +from cython.cimports.av.codec.codec import Codec, wrap_codec +from cython.cimports.av.dictionary import Dictionary +from cython.cimports.av.error import err_check +from cython.cimports.av.packet import Packet +from cython.cimports.av.utils import avrational_to_fraction, to_avrational +from cython.cimports.libc.errno import EAGAIN +from cython.cimports.libc.stdint import uint8_t +from cython.cimports.libc.string import memcpy + +_cinit_sentinel = cython.declare(object, object()) + + +@cython.cfunc +def wrap_codec_context( + c_ctx: cython.pointer[lib.AVCodecContext], + c_codec: cython.pointer[cython.const[lib.AVCodec]], + hwaccel: HWAccel, +) -> CodecContext: + """Build an bv.CodecContext for an existing AVCodecContext.""" + py_ctx: CodecContext + + if c_ctx.codec_type == lib.AVMEDIA_TYPE_VIDEO: + from av.video.codeccontext import VideoCodecContext + + py_ctx = VideoCodecContext(_cinit_sentinel) + elif c_ctx.codec_type == lib.AVMEDIA_TYPE_AUDIO: + from av.audio.codeccontext import AudioCodecContext + + py_ctx = AudioCodecContext(_cinit_sentinel) + elif c_ctx.codec_type == lib.AVMEDIA_TYPE_SUBTITLE: + from av.subtitles.codeccontext import SubtitleCodecContext + + py_ctx = SubtitleCodecContext(_cinit_sentinel) + else: + py_ctx = CodecContext(_cinit_sentinel) + + py_ctx._init(c_ctx, c_codec, hwaccel) + + return py_ctx + + +class ThreadType(Flag): + NONE = 0 + FRAME: "Decode more than one frame at once" = lib.FF_THREAD_FRAME + SLICE: "Decode more than one part of a single frame at once" = lib.FF_THREAD_SLICE + AUTO: "Decode using both FRAME and SLICE methods." = ( + lib.FF_THREAD_SLICE | lib.FF_THREAD_FRAME + ) + + +class Flags(IntEnum): + unaligned = lib.AV_CODEC_FLAG_UNALIGNED + qscale = lib.AV_CODEC_FLAG_QSCALE + four_mv = lib.AV_CODEC_FLAG_4MV + output_corrupt = lib.AV_CODEC_FLAG_OUTPUT_CORRUPT + qpel = lib.AV_CODEC_FLAG_QPEL + recon_frame = lib.AV_CODEC_FLAG_RECON_FRAME + copy_opaque = lib.AV_CODEC_FLAG_COPY_OPAQUE + frame_duration = lib.AV_CODEC_FLAG_FRAME_DURATION + pass1 = lib.AV_CODEC_FLAG_PASS1 + pass2 = lib.AV_CODEC_FLAG_PASS2 + loop_filter = lib.AV_CODEC_FLAG_LOOP_FILTER + gray = lib.AV_CODEC_FLAG_GRAY + psnr = lib.AV_CODEC_FLAG_PSNR + interlaced_dct = lib.AV_CODEC_FLAG_INTERLACED_DCT + low_delay = lib.AV_CODEC_FLAG_LOW_DELAY + global_header = lib.AV_CODEC_FLAG_GLOBAL_HEADER + bitexact = lib.AV_CODEC_FLAG_BITEXACT + ac_pred = lib.AV_CODEC_FLAG_AC_PRED + interlaced_me = lib.AV_CODEC_FLAG_INTERLACED_ME + closed_gop = lib.AV_CODEC_FLAG_CLOSED_GOP + + +class Flags2(IntEnum): + fast = lib.AV_CODEC_FLAG2_FAST + no_output = lib.AV_CODEC_FLAG2_NO_OUTPUT + local_header = lib.AV_CODEC_FLAG2_LOCAL_HEADER + chunks = lib.AV_CODEC_FLAG2_CHUNKS + ignore_crop = lib.AV_CODEC_FLAG2_IGNORE_CROP + show_all = lib.AV_CODEC_FLAG2_SHOW_ALL + export_mvs = lib.AV_CODEC_FLAG2_EXPORT_MVS + skip_manual = lib.AV_CODEC_FLAG2_SKIP_MANUAL + ro_flush_noop = lib.AV_CODEC_FLAG2_RO_FLUSH_NOOP + + +@cython.cclass +class CodecContext: + @staticmethod + def create(codec, mode=None, hwaccel=None): + cy_codec: Codec = codec if isinstance(codec, Codec) else Codec(codec, mode) + c_ctx: cython.pointer[lib.AVCodecContext] = lib.avcodec_alloc_context3( + cy_codec.ptr + ) + return wrap_codec_context(c_ctx, cy_codec.ptr, hwaccel) + + def __cinit__(self, sentinel=None, *args, **kwargs): + if sentinel is not _cinit_sentinel: + raise RuntimeError("Cannot instantiate CodecContext") + + self.options = {} + self.stream_index = -1 # This is set by the container immediately. + self.is_open = False + + @cython.cfunc + def _init( + self, + ptr: cython.pointer[lib.AVCodecContext], + codec: cython.pointer[cython.const[lib.AVCodec]], + hwaccel: HWAccel, + ): + self.ptr = ptr + if self.ptr.codec and codec and self.ptr.codec != codec: + raise RuntimeError("Wrapping CodecContext with mismatched codec.") + self.codec = wrap_codec(codec if codec != cython.NULL else self.ptr.codec) + self.hwaccel = hwaccel + + # Set reasonable threading defaults. + self.ptr.thread_count = 0 # use as many threads as there are CPUs. + self.ptr.thread_type = 0x02 # thread within a frame. Does not change the API. + + @property + def flags(self): + """ + Get and set the flags bitmask of CodecContext. + + :rtype: int + """ + return self.ptr.flags + + @flags.setter + def flags(self, value: cython.int): + self.ptr.flags = value + + @property + def qscale(self): + """ + Use fixed qscale. + + :rtype: bool + """ + return bool(self.ptr.flags & lib.AV_CODEC_FLAG_QSCALE) + + @qscale.setter + def qscale(self, value): + if value: + self.ptr.flags |= lib.AV_CODEC_FLAG_QSCALE + else: + self.ptr.flags &= ~lib.AV_CODEC_FLAG_QSCALE + + @property + def copy_opaque(self): + return bool(self.ptr.flags & lib.AV_CODEC_FLAG_COPY_OPAQUE) + + @copy_opaque.setter + def copy_opaque(self, value): + if value: + self.ptr.flags |= lib.AV_CODEC_FLAG_COPY_OPAQUE + else: + self.ptr.flags &= ~lib.AV_CODEC_FLAG_COPY_OPAQUE + + @property + def flags2(self): + """ + Get and set the flags2 bitmask of CodecContext. + + :rtype: int + """ + return self.ptr.flags2 + + @flags2.setter + def flags2(self, value: cython.int): + self.ptr.flags2 = value + + @property + def extradata(self): + if self.ptr is cython.NULL: + return None + if self.ptr.extradata_size > 0: + return cython.cast( + bytes, + cython.cast(cython.pointer[uint8_t], self.ptr.extradata)[ + : self.ptr.extradata_size + ], + ) + return None + + @extradata.setter + def extradata(self, data): + if data is None: + lib.av_freep(cython.address(self.ptr.extradata)) + self.ptr.extradata_size = 0 + else: + source = bytesource(data) + self.ptr.extradata = cython.cast( + cython.pointer[uint8_t], + lib.av_realloc( + self.ptr.extradata, source.length + lib.AV_INPUT_BUFFER_PADDING_SIZE + ), + ) + if not self.ptr.extradata: + raise MemoryError("Cannot allocate extradata") + memcpy(self.ptr.extradata, source.ptr, source.length) + self.ptr.extradata_size = source.length + + @property + def extradata_size(self): + return self.ptr.extradata_size + + @property + def is_encoder(self): + if self.ptr is cython.NULL: + return False + return lib.av_codec_is_encoder(self.ptr.codec) + + @property + def is_decoder(self): + if self.ptr is cython.NULL: + return False + return lib.av_codec_is_decoder(self.ptr.codec) + + @cython.ccall + def open(self, strict: cython.bint = True): + if self.is_open: + if strict: + raise ValueError("CodecContext is already open.") + return + + options: Dictionary = Dictionary() + options.update(self.options or {}) + + if not self.ptr.time_base.num and self.is_encoder: + if self.type == "video": + self.ptr.time_base.num = self.ptr.framerate.den or 1 + self.ptr.time_base.den = self.ptr.framerate.num or lib.AV_TIME_BASE + elif self.type == "audio": + self.ptr.time_base.num = 1 + self.ptr.time_base.den = self.ptr.sample_rate + else: + self.ptr.time_base.num = 1 + self.ptr.time_base.den = lib.AV_TIME_BASE + + err_check( + lib.avcodec_open2(self.ptr, self.codec.ptr, cython.address(options.ptr)), + f'avcodec_open2("{self.codec.name}", {self.options})', + ) + self.is_open = True + self.options = dict(options) + + def __dealloc__(self): + if self.ptr: + lib.av_freep(cython.address(self.ptr.extradata)) + lib.avcodec_free_context(cython.address(self.ptr)) + if self.parser: + lib.av_parser_close(self.parser) + + def __repr__(self): + _type = self.type or "" + name = self.name or "" + return f"" + + def parse(self, raw_input=None): + """Split up a byte stream into list of :class:`.Packet`. + + This is only effectively splitting up a byte stream, and does no + actual interpretation of the data. + + It will return all packets that are fully contained within the given + input, and will buffer partial packets until they are complete. + + Any timing information the parser is able to infer (``pts``, ``dts``, + ``duration``, ``pos`` and the keyframe flag) is assigned onto the + returned packets. Fields the parser cannot determine are left unset. + + :param ByteSource raw_input: A chunk of a byte-stream to process. + Anything that can be turned into a :class:`.ByteSource` is fine. + ``None`` or empty inputs will flush the parser's buffers. + + :return: ``list`` of :class:`.Packet` newly available. + + """ + + if not self.parser: + self.parser = lib.av_parser_init(self.codec.ptr.id) + if not self.parser: + raise ValueError(f"No parser for {self.codec.name}") + + source: ByteSource = bytesource(raw_input, allow_none=True) + + in_data: cython.p_uchar = source.ptr if source is not None else cython.NULL + in_size: cython.int = source.length if source is not None else 0 + + out_data: cython.p_uchar + out_size: cython.int + consumed: cython.int + packet: Packet = None + packets: list = [] + + while True: + with cython.nogil: + consumed = lib.av_parser_parse2( + self.parser, + self.ptr, + cython.address(out_data), + cython.address(out_size), + in_data, + in_size, + lib.AV_NOPTS_VALUE, + lib.AV_NOPTS_VALUE, + 0, + ) + err_check(consumed) + + if out_size: + # We copy the data immediately, as we have yet to figure out + # the expected lifetime of the buffer we get back. All of the + # examples decode it immediately. + # + # We've also tried: + # packet = Packet() + # packet.data = out_data + # packet.size = out_size + # packet.source = source + # + # ... but this results in corruption. + + packet = Packet(out_size) + memcpy(packet.ptr.data, out_data, out_size) + + # Propagate the timing information the parser inferred for + # this frame onto the packet (mirrors FFmpeg's parse_packet). + packet.ptr.pts = self.parser.pts + packet.ptr.dts = self.parser.dts + packet.ptr.pos = self.parser.pos + if self.parser.duration: + packet.ptr.duration = self.parser.duration + if self.parser.key_frame == 1: + packet.ptr.flags |= lib.AV_PKT_FLAG_KEY + + packets.append(packet) + + if not in_size: + # This was a flush. Only one packet should ever be returned. + break + + in_data += consumed + in_size -= consumed + + if not in_size: + break + + return packets + + @property + def is_hwaccel(self): + """ + Returns ``True`` if this codec context is hardware accelerated, ``False`` otherwise. + """ + return self.hwaccel_ctx is not None + + def _send_frame_and_recv(self, frame: Frame | None): + packet: Packet + res: cython.int + with cython.nogil: + res = lib.avcodec_send_frame( + self.ptr, frame.ptr if frame is not None else cython.NULL + ) + err_check(res, "avcodec_send_frame()") + + packet = self._recv_packet() + while packet: + yield packet + packet = self._recv_packet() + + @cython.cfunc + def _prepare_frames_for_encode(self, frame: Frame | None) -> list: + return [frame] + + @cython.cfunc + def _alloc_next_frame(self) -> Frame: + raise NotImplementedError("Base CodecContext cannot decode.") + + @cython.cfunc + def _recv_frame(self): + if not self._next_frame: + self._next_frame = self._alloc_next_frame() + + frame: Frame = self._next_frame + res: cython.int + + with cython.nogil: + res = lib.avcodec_receive_frame(self.ptr, frame.ptr) + + if res == -EAGAIN or res == lib.AVERROR_EOF: + return + + err_check(res, "avcodec_receive_frame()") + frame = self._transfer_hwframe(frame) + + if not res: + self._next_frame = None + return frame + + @cython.cfunc + def _transfer_hwframe(self, frame: Frame): + return frame + + @cython.cfunc + def _recv_packet(self): + packet: Packet = Packet() + res: cython.int + + with cython.nogil: + res = lib.avcodec_receive_packet(self.ptr, packet.ptr) + + if res == -EAGAIN or res == lib.AVERROR_EOF: + return + + err_check(res, "avcodec_receive_packet()") + if not res: + return packet + + @cython.cfunc + def _prepare_and_time_rebase_frames_for_encode(self, frame: Frame): + if self.ptr.codec_type not in [lib.AVMEDIA_TYPE_VIDEO, lib.AVMEDIA_TYPE_AUDIO]: + raise NotImplementedError("Encoding is only supported for audio and video.") + + self.open(strict=False) + + frames = self._prepare_frames_for_encode(frame) + + # Assert the frames are in our time base. + # TODO: Don't mutate time. + for frame in frames: + if frame is not None: + frame._rebase_time(self.ptr.time_base) + + return frames + + @cython.ccall + def encode(self, frame: Frame | None = None): + """Encode a list of :class:`.Packet` from the given :class:`.Frame`.""" + res = [] + for frame in self._prepare_and_time_rebase_frames_for_encode(frame): + for packet in self._send_frame_and_recv(frame): + self._setup_encoded_packet(packet) + res.append(packet) + return res + + def encode_lazy(self, frame: Frame | None = None): + for frame in self._prepare_and_time_rebase_frames_for_encode(frame): + for packet in self._send_frame_and_recv(frame): + self._setup_encoded_packet(packet) + yield packet + + @cython.cfunc + def _setup_encoded_packet(self, packet: Packet): + # We coerced the frame's time_base into the CodecContext's during encoding, + # and FFmpeg copied the frame's pts/dts to the packet, so keep track of + # this time_base in case the frame needs to be muxed to a container with + # a different time_base. + # + # NOTE: if the CodecContext's time_base is altered during encoding, all bets + # are off! + packet.ptr.time_base = self.ptr.time_base + + @cython.ccall + def decode(self, packet: Packet | None = None): + """Decode a list of :class:`.Frame` from the given :class:`.Packet`. + + If the packet is None, the buffers will be flushed. This is useful if + you do not want the library to automatically re-order frames for you + (if they are encoded with a codec that has B-frames). + + .. warning:: + + This method is **not thread-safe**. Calling :meth:`decode` concurrently + from multiple threads on the same :class:`CodecContext` will corrupt + internal FFmpeg state and likely cause a crash (segfault). FFmpeg 8.1 + enforces this more strictly than earlier releases. If you need to decode + from multiple threads, give each thread its own :class:`CodecContext`. + + """ + if not self.codec.ptr: + raise ValueError("cannot decode unknown codec") + + self.open(strict=False) + + res: cython.int + with cython.nogil: + res = lib.avcodec_send_packet( + self.ptr, packet.ptr if packet is not None else cython.NULL + ) + err_check(res, "avcodec_send_packet()") + + out: list = [] + frame = self._recv_frame() + while frame: + self._setup_decoded_frame(frame, packet) + out.append(frame) + frame = self._recv_frame() + return out + + @cython.ccall + def flush_buffers(self): + """Reset the internal codec state and discard all internal buffers. + + Should be called before you start decoding from a new position e.g. + when seeking or when switching to a different stream. + + """ + if self.is_open: + with cython.nogil: + lib.avcodec_flush_buffers(self.ptr) + + @cython.cfunc + def _setup_decoded_frame(self, frame: Frame, packet: Packet | None): + # Propagate our manual times. + # While decoding, frame times are in stream time_base, which PyAV + # is carrying around. + # TODO: Somehow get this from the stream so we can not pass the + # packet here (because flushing packets are bogus). + if packet is not None: + frame._time_base = packet.ptr.time_base + + @property + def name(self): + return self.codec.name + + @property + def type(self): + return self.codec.type + + @property + def profiles(self): + """ + List the available profiles for this stream. + + :type: list[str] + """ + ret: list = [] + if not self.ptr.codec or not self.codec.desc or not self.codec.desc.profiles: + return ret + + # Profiles are always listed in the codec descriptor, but not necessarily in + # the codec itself. So use the descriptor here. + desc = self.codec.desc + i: cython.int = 0 + while desc.profiles[i].profile != lib.AV_PROFILE_UNKNOWN: + ret.append(desc.profiles[i].name) + i += 1 + + return ret + + @property + def profile(self): + if not self.ptr.codec or not self.codec.desc or not self.codec.desc.profiles: + return + + # Profiles are always listed in the codec descriptor, but not necessarily in + # the codec itself. So use the descriptor here. + desc = self.codec.desc + i: cython.int = 0 + while desc.profiles[i].profile != lib.AV_PROFILE_UNKNOWN: + if desc.profiles[i].profile == self.ptr.profile: + return desc.profiles[i].name + i += 1 + + @profile.setter + def profile(self, value): + if not self.codec or not self.codec.desc or not self.codec.desc.profiles: + return + + # Profiles are always listed in the codec descriptor, but not necessarily in + # the codec itself. So use the descriptor here. + desc = self.codec.desc + i: cython.int = 0 + while desc.profiles[i].profile != lib.AV_PROFILE_UNKNOWN: + if desc.profiles[i].name == value: + self.ptr.profile = desc.profiles[i].profile + return + i += 1 + + @property + def time_base(self): + if self.is_decoder: + raise RuntimeError("Cannot access 'time_base' as a decoder") + return avrational_to_fraction(cython.address(self.ptr.time_base)) + + @time_base.setter + def time_base(self, value): + if self.is_decoder: + raise RuntimeError("Cannot access 'time_base' as a decoder") + to_avrational(value, cython.address(self.ptr.time_base)) + + @property + def codec_tag(self): + return self.ptr.codec_tag.to_bytes(4, byteorder="little", signed=False).decode( + encoding="ascii" + ) + + @codec_tag.setter + def codec_tag(self, value): + if isinstance(value, str) and len(value) == 4: + self.ptr.codec_tag = int.from_bytes( + value.encode(encoding="ascii"), byteorder="little", signed=False + ) + else: + raise ValueError("Codec tag should be a 4 character string.") + + @property + @cython.cdivision(True) + def global_quality(self): + """Global quality for codecs which cannot change it per frame. + + Stored internally in lambda units; this property converts to/from + QP units using ``FF_QP2LAMBDA``. + + Wraps :ffmpeg:`AVCodecContext.global_quality`. + + """ + return self.ptr.global_quality // lib.FF_QP2LAMBDA + + @global_quality.setter + def global_quality(self, value: cython.int): + self.ptr.global_quality = value * lib.FF_QP2LAMBDA + + @property + def bit_rate(self): + return self.ptr.bit_rate if self.ptr.bit_rate > 0 else None + + @bit_rate.setter + def bit_rate(self, value: cython.int): + self.ptr.bit_rate = value + + @property + def max_bit_rate(self): + if self.ptr.rc_max_rate > 0: + return self.ptr.rc_max_rate + else: + return None + + @property + def bit_rate_tolerance(self): + self.ptr.bit_rate_tolerance + + @bit_rate_tolerance.setter + def bit_rate_tolerance(self, value: cython.int): + self.ptr.bit_rate_tolerance = value + + @property + def thread_count(self): + """How many threads to use; 0 means auto. + + Wraps :ffmpeg:`AVCodecContext.thread_count`. + + """ + return self.ptr.thread_count + + @thread_count.setter + def thread_count(self, value: cython.int): + if self.is_open: + raise RuntimeError("Cannot change thread_count after codec is open.") + self.ptr.thread_count = value + + @property + def thread_type(self): + """One of :class:`.ThreadType`. + + Wraps :ffmpeg:`AVCodecContext.thread_type`. + + """ + return ThreadType(self.ptr.thread_type) + + @thread_type.setter + def thread_type(self, value): + if self.is_open: + raise RuntimeError("Cannot change thread_type after codec is open.") + if type(value) is int: + self.ptr.thread_type = value + elif type(value) is str: + self.ptr.thread_type = ThreadType[value].value + else: + self.ptr.thread_type = value.value + + @property + def skip_frame(self): + """Returns one of the following str literals: + + "NONE" Discard nothing + "DEFAULT" Discard useless packets like 0 size packets in AVI + "NONREF" Discard all non reference + "BIDIR" Discard all bidirectional frames + "NONINTRA" Discard all non intra frames + "NONKEY Discard all frames except keyframes + "ALL" Discard all + + Wraps :ffmpeg:`AVCodecContext.skip_frame`. + """ + value = self.ptr.skip_frame + if value == lib.AVDISCARD_NONE: + return "NONE" + if value == lib.AVDISCARD_DEFAULT: + return "DEFAULT" + if value == lib.AVDISCARD_NONREF: + return "NONREF" + if value == lib.AVDISCARD_BIDIR: + return "BIDIR" + if value == lib.AVDISCARD_NONINTRA: + return "NONINTRA" + if value == lib.AVDISCARD_NONKEY: + return "NONKEY" + if value == lib.AVDISCARD_ALL: + return "ALL" + return f"{value}" + + @skip_frame.setter + def skip_frame(self, value): + if value == "NONE": + self.ptr.skip_frame = lib.AVDISCARD_NONE + elif value == "DEFAULT": + self.ptr.skip_frame = lib.AVDISCARD_DEFAULT + elif value == "NONREF": + self.ptr.skip_frame = lib.AVDISCARD_NONREF + elif value == "BIDIR": + self.ptr.skip_frame = lib.AVDISCARD_BIDIR + elif value == "NONINTRA": + self.ptr.skip_frame = lib.AVDISCARD_NONINTRA + elif value == "NONKEY": + self.ptr.skip_frame = lib.AVDISCARD_NONKEY + elif value == "ALL": + self.ptr.skip_frame = lib.AVDISCARD_ALL + else: + raise ValueError("Invalid skip_frame type") + + @property + def delay(self): + """Codec delay. + + Wraps :ffmpeg:`AVCodecContext.delay`. + + """ + return self.ptr.delay diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/context.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/context.pyi new file mode 100644 index 0000000000000000000000000000000000000000..808bb292b23041abe2fd5dd316f2a38f14d86bdc --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/context.pyi @@ -0,0 +1,118 @@ +from enum import Flag, IntEnum +from fractions import Fraction +from typing import ClassVar, Literal, cast, overload + +from av.audio import _AudioCodecName +from av.audio.codeccontext import AudioCodecContext +from av.packet import Packet +from av.video import _VideoCodecName +from av.video.codeccontext import VideoCodecContext + +from .codec import Codec +from .hwaccel import HWAccel + +class ThreadType(Flag): + NONE = cast(ClassVar[ThreadType], ...) + FRAME = cast(ClassVar[ThreadType], ...) + SLICE = cast(ClassVar[ThreadType], ...) + AUTO = cast(ClassVar[ThreadType], ...) + def __get__(self, i: object | None, owner: type | None = None) -> ThreadType: ... + def __set__(self, instance: object, value: int | str | ThreadType) -> None: ... + +class Flags(IntEnum): + unaligned = cast(int, ...) + qscale = cast(int, ...) + four_mv = cast(int, ...) + output_corrupt = cast(int, ...) + qpel = cast(int, ...) + recon_frame = cast(int, ...) + copy_opaque = cast(int, ...) + frame_duration = cast(int, ...) + pass1 = cast(int, ...) + pass2 = cast(int, ...) + loop_filter = cast(int, ...) + gray = cast(int, ...) + psnr = cast(int, ...) + interlaced_dct = cast(int, ...) + low_delay = cast(int, ...) + global_header = cast(int, ...) + bitexact = cast(int, ...) + ac_pred = cast(int, ...) + interlaced_me = cast(int, ...) + closed_gop = cast(int, ...) + +class Flags2(IntEnum): + fast = cast(int, ...) + no_output = cast(int, ...) + local_header = cast(int, ...) + chunks = cast(int, ...) + ignore_crop = cast(int, ...) + show_all = cast(int, ...) + export_mvs = cast(int, ...) + skip_manual = cast(int, ...) + ro_flush_noop = cast(int, ...) + +class CodecContext: + name: str + type: Literal["video", "audio", "data", "subtitle", "attachment"] + options: dict[str, str] + profile: str | None + @property + def profiles(self) -> list[str]: ... + extradata: bytes | None + time_base: Fraction + codec_tag: str + global_quality: int + bit_rate: int | None + bit_rate_tolerance: int + thread_count: int + thread_type: ThreadType + skip_frame: Literal[ + "NONE", "DEFAULT", "NONREF", "BIDIR", "NONINTRA", "NONKEY", "ALL" + ] + flags: int + qscale: bool + copy_opaque: bool + flags2: int + @property + def is_open(self) -> bool: ... + @property + def is_encoder(self) -> bool: ... + @property + def is_decoder(self) -> bool: ... + @property + def codec(self) -> Codec: ... + @property + def max_bit_rate(self) -> int | None: ... + @property + def delay(self) -> bool: ... + @property + def extradata_size(self) -> int: ... + @property + def is_hwaccel(self) -> bool: ... + def open(self, strict: bool = True) -> None: ... + @overload + @staticmethod + def create( + codec: _AudioCodecName, + mode: Literal["r", "w"] | None = None, + hwaccel: HWAccel | None = None, + ) -> AudioCodecContext: ... + @overload + @staticmethod + def create( + codec: _VideoCodecName, + mode: Literal["r", "w"] | None = None, + hwaccel: HWAccel | None = None, + ) -> VideoCodecContext: ... + @overload + @staticmethod + def create( + codec: str | Codec, + mode: Literal["r", "w"] | None = None, + hwaccel: HWAccel | None = None, + ) -> CodecContext: ... + def parse( + self, raw_input: bytes | bytearray | memoryview | None = None + ) -> list[Packet]: ... + def flush_buffers(self) -> None: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/hwaccel.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/hwaccel.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..f05a7b47b09ee82448bdc028d014676bc06cea75 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/hwaccel.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2da7190024514dc86518dbca88a7780f11ec4ca9b5a3acee39af546f91c0648e +size 137600 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/hwaccel.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/hwaccel.pxd new file mode 100644 index 0000000000000000000000000000000000000000..fd9bb1720977171a15f8112d42cc102dd31a0c6b --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/hwaccel.pxd @@ -0,0 +1,23 @@ +cimport libav as lib + +from av.codec.codec cimport Codec + + +cdef class HWConfig: + cdef object __weakref__ + cdef const lib.AVCodecHWConfig *ptr + cdef void _init(self, const lib.AVCodecHWConfig *ptr) + +cdef HWConfig wrap_hwconfig(const lib.AVCodecHWConfig *ptr) + +cdef class HWAccel: + cdef int _device_type + cdef str _device + cdef readonly Codec codec + cdef readonly HWConfig config + cdef lib.AVBufferRef *ptr + cdef readonly int device_id + cdef readonly bint is_hw_owned + cdef public bint allow_software_fallback + cdef public dict options + cdef public int flags diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/hwaccel.py b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/hwaccel.py new file mode 100644 index 0000000000000000000000000000000000000000..69f742b29def72d07f5a05862f51fd8005c9e140 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/hwaccel.py @@ -0,0 +1,188 @@ +import weakref +from enum import IntEnum + +import cython +import cython.cimports.libav as lib +from cython.cimports.av.codec.codec import Codec +from cython.cimports.av.dictionary import Dictionary +from cython.cimports.av.error import err_check +from cython.cimports.av.video.format import get_video_format + + +class HWDeviceType(IntEnum): + none = lib.AV_HWDEVICE_TYPE_NONE + vdpau = lib.AV_HWDEVICE_TYPE_VDPAU + cuda = lib.AV_HWDEVICE_TYPE_CUDA + vaapi = lib.AV_HWDEVICE_TYPE_VAAPI + dxva2 = lib.AV_HWDEVICE_TYPE_DXVA2 + qsv = lib.AV_HWDEVICE_TYPE_QSV + videotoolbox = lib.AV_HWDEVICE_TYPE_VIDEOTOOLBOX + d3d11va = lib.AV_HWDEVICE_TYPE_D3D11VA + drm = lib.AV_HWDEVICE_TYPE_DRM + opencl = lib.AV_HWDEVICE_TYPE_OPENCL + mediacodec = lib.AV_HWDEVICE_TYPE_MEDIACODEC + vulkan = lib.AV_HWDEVICE_TYPE_VULKAN + d3d12va = lib.AV_HWDEVICE_TYPE_D3D12VA + amf = 13 # FFmpeg >=8 + ohcodec = 14 + # TODO: When ffmpeg major is changed, check this enum. + + +class HWConfigMethod(IntEnum): + none = 0 + hw_device_ctx = ( + lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX + ) # This is the only one we support. + hw_frame_ctx = lib.AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX + internal = lib.AV_CODEC_HW_CONFIG_METHOD_INTERNAL + ad_hoc = lib.AV_CODEC_HW_CONFIG_METHOD_AD_HOC + + +_cinit_sentinel = cython.declare(object, object()) +_singletons = cython.declare(object, weakref.WeakValueDictionary()) + + +@cython.cfunc +def wrap_hwconfig(ptr: cython.pointer[cython.const[lib.AVCodecHWConfig]]) -> HWConfig: + try: + return _singletons[cython.cast(cython.Py_ssize_t, ptr)] + except KeyError: + pass + config: HWConfig = HWConfig(_cinit_sentinel) + config._init(ptr) + _singletons[cython.cast(cython.Py_ssize_t, ptr)] = config + return config + + +@cython.final +@cython.cclass +class HWConfig: + def __init__(self, sentinel): + if sentinel is not _cinit_sentinel: + raise RuntimeError("Cannot instantiate CodecContext") + + @cython.cfunc + def _init( + self, ptr: cython.pointer[cython.const[lib.AVCodecHWConfig]] + ) -> cython.void: + self.ptr = ptr + + def __repr__(self): + return ( + f"" + ) + + @property + def device_type(self): + return HWDeviceType(self.ptr.device_type) + + @property + def format(self): + return get_video_format(self.ptr.pix_fmt, 0, 0) + + @property + def methods(self): + return HWConfigMethod(self.ptr.methods) + + @property + def is_supported(self): + return bool(self.ptr.methods & lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) + + +@cython.ccall +def hwdevices_available(): + result: list = [] + x: lib.AVHWDeviceType = lib.AV_HWDEVICE_TYPE_NONE + while True: + x = lib.av_hwdevice_iterate_types(x) + if x == lib.AV_HWDEVICE_TYPE_NONE: + break + result.append(lib.av_hwdevice_get_type_name(HWDeviceType(x))) + return result + + +@cython.final +@cython.cclass +class HWAccel: + def __init__( + self, + device_type, + device=None, + allow_software_fallback=True, + options=None, + flags=None, + is_hw_owned=False, + ): + if isinstance(device_type, HWDeviceType): + self._device_type = device_type + elif isinstance(device_type, str): + self._device_type = int(lib.av_hwdevice_find_type_by_name(device_type)) + elif isinstance(device_type, int): + self._device_type = device_type + else: + raise ValueError("Unknown type for device_type") + + self.is_hw_owned = is_hw_owned + self.device_id = 0 + if self._device_type == HWDeviceType.cuda and device: + self.device_id = int(device) + + self._device = None if device is None else f"{device}" + self.allow_software_fallback = allow_software_fallback + + self.options = {} if not options else dict(options) + if self._device_type == HWDeviceType.cuda and self.is_hw_owned: + self.options.setdefault("primary_ctx", "1") + self.flags = 0 if not flags else flags + self.ptr = cython.NULL + self.config = None + + def _initialize_hw_context(self, codec: Codec): + config: HWConfig + for config in codec.hardware_configs: + if not (config.ptr.methods & lib.AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX): + continue + if self._device_type and config.device_type != self._device_type: + continue + break + else: # nobreak + raise NotImplementedError(f"No supported hardware config for {codec}") + + self.config = config + c_device: cython.p_char = cython.NULL + if self._device: + device_bytes = self._device.encode() + c_device = device_bytes + c_options: Dictionary = Dictionary(self.options) + + err_check( + lib.av_hwdevice_ctx_create( + cython.address(self.ptr), + config.ptr.device_type, + c_device, + c_options.ptr, + self.flags, + ) + ) + + def create(self, codec: Codec) -> HWAccel: + """Create a new hardware accelerator context with the given codec""" + if self.ptr: + raise RuntimeError("Hardware context already initialized") + + ret = HWAccel( + device_type=self._device_type, + device=self._device, + allow_software_fallback=self.allow_software_fallback, + options=self.options, + is_hw_owned=self.is_hw_owned, + ) + ret._initialize_hw_context(codec) + return ret + + def __dealloc__(self): + if self.ptr: + lib.av_buffer_unref(cython.address(self.ptr)) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/codec/hwaccel.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/hwaccel.pyi new file mode 100644 index 0000000000000000000000000000000000000000..247771a3bb6ca550fbfe0a11bf68998b9ba2f0fe --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/codec/hwaccel.pyi @@ -0,0 +1,57 @@ +from enum import IntEnum +from typing import cast + +from av.codec.codec import Codec +from av.video.format import VideoFormat + +class HWDeviceType(IntEnum): + none = cast(int, ...) + vdpau = cast(int, ...) + cuda = cast(int, ...) + vaapi = cast(int, ...) + dxva2 = cast(int, ...) + qsv = cast(int, ...) + videotoolbox = cast(int, ...) + d3d11va = cast(int, ...) + drm = cast(int, ...) + opencl = cast(int, ...) + mediacodec = cast(int, ...) + vulkan = cast(int, ...) + d3d12va = cast(int, ...) + +class HWConfigMethod(IntEnum): + none = cast(int, ...) + hw_device_ctx = cast(int, ...) + hw_frame_ctx = cast(int, ...) + internal = cast(int, ...) + ad_hoc = cast(int, ...) + +class HWConfig: + @property + def device_type(self) -> HWDeviceType: ... + @property + def format(self) -> VideoFormat: ... + @property + def methods(self) -> HWConfigMethod: ... + @property + def is_supported(self) -> bool: ... + +class HWAccel: + options: dict[str, object] + + @property + def is_hw_owned(self) -> bool: ... + @property + def device_id(self) -> int: ... + def __init__( + self, + device_type: str | HWDeviceType, + device: str | int | None = None, + allow_software_fallback: bool = False, + options: dict[str, object] | None = None, + flags: int | None = None, + is_hw_owned: bool = False, + ) -> None: ... + def create(self, codec: Codec) -> HWAccel: ... + +def hwdevices_available() -> list[str]: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/__init__.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/container/__init__.pxd new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/__init__.py b/.venvs/kimodo/lib/python3.10/site-packages/av/container/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c5dde46ea6f5cbdc72d990623fb27ee6f27af69c --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/__init__.py @@ -0,0 +1,3 @@ +from .core import Container, Flags, open +from .input import InputContainer as InputContainer +from .output import OutputContainer as OutputContainer diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/__init__.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/container/__init__.pyi new file mode 100644 index 0000000000000000000000000000000000000000..7160777cc06b6ebf854ad7988da98d9740b7a36b --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/__init__.pyi @@ -0,0 +1,3 @@ +from .core import * +from .input import * +from .output import * diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/core.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/container/core.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..88332f94d80a7382b2d1c58bc4c4442614f4df72 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/core.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec9ad6e06dd4525f1fc790290ec2ec0205ecda1d0c50e47a74c692ff7c73dd82 +size 195888 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/core.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/container/core.pxd new file mode 100644 index 0000000000000000000000000000000000000000..7cba26407d99c0399a4994293c935b1bf6f6002b --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/core.pxd @@ -0,0 +1,49 @@ +cimport libav as lib +from libc.stdint cimport uint8_t + +from av.codec.hwaccel cimport HWAccel +from av.container.pyio cimport PyIOFile +from av.container.streams cimport StreamContainer +from av.dictionary cimport Dictionary +from av.format cimport ContainerFormat +from av.stream cimport Stream + +# Interrupt callback information, times are in seconds. +ctypedef struct timeout_info: + double start_time + double timeout + + +cdef class Container: + cdef lib.AVFormatContext *ptr + cdef readonly object name + cdef readonly str metadata_encoding + cdef readonly str metadata_errors + + cdef readonly PyIOFile file + cdef int buffer_size + cdef readonly object io_open + cdef readonly object open_files + + cdef readonly ContainerFormat format + + cdef readonly dict options + cdef readonly dict container_options + cdef readonly list stream_options + + cdef HWAccel hwaccel + + cdef readonly StreamContainer streams + cdef readonly dict metadata + + # Private API. + cdef uint8_t _myflag # enum: writeable, input_was_opened, started, done + cdef _assert_open(self) + cdef int err_check(self, int value) except -1 + + # Timeouts + cdef readonly object open_timeout + cdef readonly object read_timeout + cdef timeout_info interrupt_callback_info + cdef set_timeout(self, object) + cdef start_timeout(self) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/core.py b/.venvs/kimodo/lib/python3.10/site-packages/av/container/core.py new file mode 100644 index 0000000000000000000000000000000000000000..8aeaa7a3e7ee8701755a1154e00dbe9658993a1b --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/core.py @@ -0,0 +1,604 @@ +import os +import time +from enum import Flag, IntEnum +from pathlib import Path + +import cython +import cython.cimports.libav as lib +from cython.cimports.av.container.core import timeout_info +from cython.cimports.av.container.input import InputContainer +from cython.cimports.av.container.output import OutputContainer +from cython.cimports.av.container.pyio import pyio_close_custom_gil, pyio_close_gil +from cython.cimports.av.error import err_check, stash_exception +from cython.cimports.av.format import build_container_format +from cython.cimports.av.utils import ( + avdict_to_dict, + avrational_to_fraction, + dict_to_avdict, + to_avrational, +) +from cython.cimports.libc.stdint import int64_t +from cython.operator import dereference + +from av.logging import Capture as LogCapture + +_cinit_sentinel = cython.declare(object, object()) + +AVChapterPtrPtr = cython.typedef(cython.pointer[cython.pointer[lib.AVChapter]]) + + +@cython.cfunc +@cython.nogil +@cython.exceptval(check=False) +def interrupt_cb(p: cython.p_void) -> cython.int: + info: timeout_info = dereference(cython.cast(cython.pointer[timeout_info], p)) + if info.timeout < 0: # timeout < 0 means no timeout + return 0 + + current_time: cython.double + with cython.gil: + current_time = time.monotonic() + if current_time < info.start_time: + # Raise this when we get back to Python. + stash_exception( + ( + RuntimeError, + RuntimeError("Clock has been changed to before timeout start"), + None, + ) + ) + return 1 + + if current_time > info.start_time + info.timeout: + return 1 + return 0 + + +@cython.cfunc +@cython.nogil +@cython.exceptval(check=False) +def pyav_io_open( + s: cython.pointer[lib.AVFormatContext], + pb: cython.pointer[cython.pointer[lib.AVIOContext]], + url: cython.p_const_char, + flags: cython.int, + options: cython.pointer[cython.pointer[lib.AVDictionary]], +) -> cython.int: + with cython.gil: + return pyav_io_open_gil(s, pb, url, flags, options) + + +@cython.cfunc +@cython.exceptval(check=False) +def pyav_io_open_gil( + s: cython.pointer[lib.AVFormatContext], + pb: cython.pointer[cython.pointer[lib.AVIOContext]], + url: cython.p_const_char, + flags: cython.int, + options: cython.pointer[cython.pointer[lib.AVDictionary]], +) -> cython.int: + container: Container + file: object + pyio_file: PyIOFile + try: + container = cython.cast(Container, dereference(s).opaque) + + if options is not cython.NULL: + options_dict = avdict_to_dict( + dereference( + cython.cast( + cython.pointer[cython.pointer[lib.AVDictionary]], options + ) + ), + encoding=container.metadata_encoding, + errors=container.metadata_errors, + ) + else: + options_dict = {} + + file = container.io_open( + cython.cast(str, url) if url is not cython.NULL else "", flags, options_dict + ) + + pyio_file = PyIOFile( + file, container.buffer_size, (flags & lib.AVIO_FLAG_WRITE) != 0 + ) + + # Add it to the container to avoid it being deallocated + container.open_files[cython.cast(int64_t, pyio_file.iocontext.opaque)] = ( + pyio_file + ) + pb[0] = pyio_file.iocontext + return 0 + except Exception: + return stash_exception() + + +@cython.cfunc +@cython.nogil +@cython.exceptval(check=False) +def pyav_io_close( + s: cython.pointer[lib.AVFormatContext], pb: cython.pointer[lib.AVIOContext] +) -> cython.int: + with cython.gil: + return pyav_io_close_gil(s, pb) + + +@cython.cfunc +@cython.exceptval(check=False) +def pyav_io_close_gil( + s: cython.pointer[lib.AVFormatContext], pb: cython.pointer[lib.AVIOContext] +) -> cython.int: + container: Container + result: cython.int = 0 + try: + container = cython.cast(Container, dereference(s).opaque) + if ( + container.open_files is not None + and cython.cast(int64_t, pb.opaque) in container.open_files + ): + result = pyio_close_custom_gil(pb) + + # Remove it from the container so that it can be deallocated + del container.open_files[cython.cast(int64_t, pb.opaque)] + else: + result = pyio_close_gil(pb) + + except Exception: + stash_exception() + result = lib.AVERROR_UNKNOWN # Or another appropriate error code + + return result + + +@cython.cfunc +@cython.nogil +@cython.exceptval(check=False) +def _free_chapters(ctx: cython.pointer[lib.AVFormatContext]) -> cython.void: + i: cython.Py_ssize_t + if ctx.chapters != cython.NULL: + for i in range(ctx.nb_chapters): + if ctx.chapters[i] != cython.NULL: + if ctx.chapters[i].metadata != cython.NULL: + lib.av_dict_free(cython.address(ctx.chapters[i].metadata)) + lib.av_freep( + cython.cast(cython.pp_void, cython.address(ctx.chapters[i])) + ) + lib.av_freep(cython.cast(cython.pp_void, cython.address(ctx.chapters))) + ctx.nb_chapters = 0 + + +# fmt: off +class Flags(Flag): + gen_pts: "Generate missing pts even if it requires parsing future frames." = lib.AVFMT_FLAG_GENPTS + ign_idx: "Ignore index." = lib.AVFMT_FLAG_IGNIDX + non_block: "Do not block when reading packets from input." = lib.AVFMT_FLAG_NONBLOCK + ign_dts: "Ignore DTS on frames that contain both DTS & PTS." = lib.AVFMT_FLAG_IGNDTS + no_fillin: "Do not infer any values from other values, just return what is stored in the container." = lib.AVFMT_FLAG_NOFILLIN + no_parse: "Do not use AVParsers, you also must set AVFMT_FLAG_NOFILLIN as the fill in code works on frames and no parsing -> no frames. Also seeking to frames can not work if parsing to find frame boundaries has been disabled." = lib.AVFMT_FLAG_NOPARSE + no_buffer: "Do not buffer frames when possible." = lib.AVFMT_FLAG_NOBUFFER + custom_io: "The caller has supplied a custom AVIOContext, don't avio_close() it." = lib.AVFMT_FLAG_CUSTOM_IO + discard_corrupt: "Discard frames marked corrupted." = lib.AVFMT_FLAG_DISCARD_CORRUPT + flush_packets: "Flush the AVIOContext every packet." = lib.AVFMT_FLAG_FLUSH_PACKETS + bitexact: "When muxing, try to avoid writing any random/volatile data to the output. This includes any random IDs, real-time timestamps/dates, muxer version, etc. This flag is mainly intended for testing." = lib.AVFMT_FLAG_BITEXACT + sort_dts: "Try to interleave outputted packets by dts (using this flag can slow demuxing down)." = lib.AVFMT_FLAG_SORT_DTS + fast_seek: "Enable fast, but inaccurate seeks for some formats." = lib.AVFMT_FLAG_FAST_SEEK + auto_bsf: "Add bitstream filters as requested by the muxer." = lib.AVFMT_FLAG_AUTO_BSF + +class AudioCodec(IntEnum): + """Enumeration for audio codec IDs.""" + none = lib.AV_CODEC_ID_NONE # No codec. + pcm_alaw = lib.AV_CODEC_ID_PCM_ALAW # PCM A-law. + pcm_bluray = lib.AV_CODEC_ID_PCM_BLURAY # PCM Blu-ray. + pcm_dvd = lib.AV_CODEC_ID_PCM_DVD # PCM DVD. + pcm_f16le = lib.AV_CODEC_ID_PCM_F16LE # PCM F16 little-endian. + pcm_f24le = lib.AV_CODEC_ID_PCM_F24LE # PCM F24 little-endian. + pcm_f32be = lib.AV_CODEC_ID_PCM_F32BE # PCM F32 big-endian. + pcm_f32le = lib.AV_CODEC_ID_PCM_F32LE # PCM F32 little-endian. + pcm_f64be = lib.AV_CODEC_ID_PCM_F64BE # PCM F64 big-endian. + pcm_f64le = lib.AV_CODEC_ID_PCM_F64LE # PCM F64 little-endian. + pcm_lxf = lib.AV_CODEC_ID_PCM_LXF # PCM LXF. + pcm_mulaw = lib.AV_CODEC_ID_PCM_MULAW # PCM μ-law. + pcm_s16be = lib.AV_CODEC_ID_PCM_S16BE # PCM signed 16-bit big-endian. + pcm_s16be_planar = lib.AV_CODEC_ID_PCM_S16BE_PLANAR # PCM signed 16-bit big-endian planar. + pcm_s16le = lib.AV_CODEC_ID_PCM_S16LE # PCM signed 16-bit little-endian. + pcm_s16le_planar = lib.AV_CODEC_ID_PCM_S16LE_PLANAR # PCM signed 16-bit little-endian planar. + pcm_s24be = lib.AV_CODEC_ID_PCM_S24BE # PCM signed 24-bit big-endian. + pcm_s24daud = lib.AV_CODEC_ID_PCM_S24DAUD # PCM signed 24-bit D-Cinema audio. + pcm_s24le = lib.AV_CODEC_ID_PCM_S24LE # PCM signed 24-bit little-endian. + pcm_s24le_planar = lib.AV_CODEC_ID_PCM_S24LE_PLANAR # PCM signed 24-bit little-endian planar. + pcm_s32be = lib.AV_CODEC_ID_PCM_S32BE # PCM signed 32-bit big-endian. + pcm_s32le = lib.AV_CODEC_ID_PCM_S32LE # PCM signed 32-bit little-endian. + pcm_s32le_planar = lib.AV_CODEC_ID_PCM_S32LE_PLANAR # PCM signed 32-bit little-endian planar. + pcm_s64be = lib.AV_CODEC_ID_PCM_S64BE # PCM signed 64-bit big-endian. + pcm_s64le = lib.AV_CODEC_ID_PCM_S64LE # PCM signed 64-bit little-endian. + pcm_s8 = lib.AV_CODEC_ID_PCM_S8 # PCM signed 8-bit. + pcm_s8_planar = lib.AV_CODEC_ID_PCM_S8_PLANAR # PCM signed 8-bit planar. + pcm_u16be = lib.AV_CODEC_ID_PCM_U16BE # PCM unsigned 16-bit big-endian. + pcm_u16le = lib.AV_CODEC_ID_PCM_U16LE # PCM unsigned 16-bit little-endian. + pcm_u24be = lib.AV_CODEC_ID_PCM_U24BE # PCM unsigned 24-bit big-endian. + pcm_u24le = lib.AV_CODEC_ID_PCM_U24LE # PCM unsigned 24-bit little-endian. + pcm_u32be = lib.AV_CODEC_ID_PCM_U32BE # PCM unsigned 32-bit big-endian. + pcm_u32le = lib.AV_CODEC_ID_PCM_U32LE # PCM unsigned 32-bit little-endian. + pcm_u8 = lib.AV_CODEC_ID_PCM_U8 # PCM unsigned 8-bit. + pcm_vidc = lib.AV_CODEC_ID_PCM_VIDC # PCM VIDC. +# fmt: on + + +@cython.cclass +class Container: + def __cinit__( + self, + sentinel, + file_, + format_name, + options, + container_options, + stream_options, + hwaccel, + metadata_encoding, + metadata_errors, + buffer_size, + open_timeout, + read_timeout, + io_open, + ): + if sentinel is not _cinit_sentinel: + raise RuntimeError("cannot construct base Container") + + writeable: cython.bint = isinstance(self, OutputContainer) + if not writeable and not isinstance(self, InputContainer): + raise RuntimeError("Container cannot be directly extended.") + + if isinstance(file_, str): + self.name = file_ + else: + self.name = str(getattr(file_, "name", "")) + + self.options = dict(options or ()) + self.container_options = dict(container_options or ()) + self.stream_options = [dict(x) for x in stream_options or ()] + + self.hwaccel = hwaccel + + self.metadata_encoding = metadata_encoding + self.metadata_errors = metadata_errors + + self.open_timeout = open_timeout + self.read_timeout = read_timeout + + self.buffer_size = buffer_size + self.io_open = io_open + + acodec = None # no audio codec specified + if format_name is not None: + if ":" in format_name: + format_name, acodec = format_name.split(":") + self.format = ContainerFormat(format_name) + + res: cython.int + name_obj: bytes = os.fsencode(self.name) + name: cython.p_char = name_obj + ofmt: cython.pointer[cython.const[lib.AVOutputFormat]] + + if writeable: + self._myflag |= 1 # enum.writeable = True + ofmt = ( + self.format.optr + if self.format + else lib.av_guess_format(cython.NULL, name, cython.NULL) + ) + if ofmt == cython.NULL: + raise ValueError("Could not determine output format") + + with cython.nogil: + # This does not actually open the file. + res = lib.avformat_alloc_output_context2( + cython.address(self.ptr), + ofmt, + cython.NULL, + name, + ) + self.err_check(res) + else: + # We need the context before we open the input AND setup Python IO. + self.ptr = lib.avformat_alloc_context() + + # Setup interrupt callback + if self.open_timeout is not None or self.read_timeout is not None: + self.ptr.interrupt_callback.callback = interrupt_cb + self.ptr.interrupt_callback.opaque = cython.address( + self.interrupt_callback_info + ) + + if acodec is not None: + self.ptr.audio_codec_id = getattr(AudioCodec, acodec) + + self.ptr.flags |= lib.AVFMT_FLAG_GENPTS + self.ptr.opaque = cython.cast(cython.p_void, self) + + # Setup Python IO. + self.open_files = {} + if not isinstance(file_, basestring): + self.file = PyIOFile(file_, buffer_size, writeable) + self.ptr.pb = self.file.iocontext + + if io_open is not None: + self.ptr.io_open = pyav_io_open + self.ptr.io_close2 = pyav_io_close + self.ptr.flags |= lib.AVFMT_FLAG_CUSTOM_IO + + ifmt: cython.pointer[cython.const[lib.AVInputFormat]] + c_options: Dictionary + if not writeable: + ifmt = self.format.iptr if self.format else cython.NULL + c_options = Dictionary(self.options, self.container_options) + + self.set_timeout(self.open_timeout) + self.start_timeout() + with cython.nogil: + res = lib.avformat_open_input( + cython.address(self.ptr), name, ifmt, cython.address(c_options.ptr) + ) + self.set_timeout(None) + self.err_check(res) + self._myflag |= 2 # enum.input_was_opened = True + + if format_name is None: + self.format = build_container_format(self.ptr.iformat, self.ptr.oformat) + + def __dealloc__(self): + with cython.nogil: + lib.avformat_free_context(self.ptr) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + def close(self): + raise NotImplementedError + + def __repr__(self): + return f"" + + @cython.cfunc + @cython.exceptval(-1, check=False) + def err_check(self, value: cython.int) -> cython.int: + return err_check(value, filename=self.name) + + def dumps_format(self): + self._assert_open() + with LogCapture() as logs: + lib.av_dump_format(self.ptr, 0, "", isinstance(self, OutputContainer)) + return "".join(log[2] for log in logs) + + @cython.cfunc + def set_timeout(self, timeout): + if timeout is None: + self.interrupt_callback_info.timeout = -1.0 + else: + self.interrupt_callback_info.timeout = timeout + + @cython.cfunc + def start_timeout(self): + self.interrupt_callback_info.start_time = time.monotonic() + + @cython.cfunc + def _assert_open(self): + if self.ptr == cython.NULL: + raise AssertionError("Container is not open") + + @property + def video_codec_id(self): + self._assert_open() + return self.ptr.video_codec_id + + @video_codec_id.setter + def video_codec_id(self, value: lib.AVCodecID): + self._assert_open() + self.ptr.video_codec_id = value + + @property + def flags(self): + self._assert_open() + return self.ptr.flags + + @flags.setter + def flags(self, value: cython.int): + self._assert_open() + self.ptr.flags = value + + @property + def input_was_opened(self): + return self._myflag & 2 + + def chapters(self): + self._assert_open() + result: list = [] + i: cython.Py_ssize_t + for i in range(self.ptr.nb_chapters): + ch = self.ptr.chapters[i] + result.append( + { + "id": ch.id, + "start": ch.start, + "end": ch.end, + "time_base": avrational_to_fraction(cython.address(ch.time_base)), + "metadata": avdict_to_dict( + ch.metadata, self.metadata_encoding, self.metadata_errors + ), + } + ) + return result + + def set_chapters(self, chapters): + self._assert_open() + + count: cython.Py_ssize_t = len(chapters) + i: cython.Py_ssize_t + ch_array: AVChapterPtrPtr + ch: cython.pointer[lib.AVChapter] + entry: dict + + with cython.nogil: + _free_chapters(self.ptr) + + if count == 0: + self.ptr.nb_chapters = 0 + self.ptr.chapters = cython.NULL + return + + ch_array = cython.cast( + AVChapterPtrPtr, + lib.av_malloc(count * cython.sizeof(cython.pointer[lib.AVChapter])), + ) + if ch_array == cython.NULL: + raise MemoryError("av_malloc failed for chapters") + + for i in range(count): + entry = chapters[i] + ch = cython.cast( + cython.pointer[lib.AVChapter], + lib.av_malloc(cython.sizeof(lib.AVChapter)), + ) + if ch == cython.NULL: + raise MemoryError("av_malloc failed for chapter") + ch.id = entry["id"] + ch.start = cython.cast(int64_t, entry["start"]) + ch.end = cython.cast(int64_t, entry["end"]) + to_avrational(entry["time_base"], cython.address(ch.time_base)) + ch.metadata = cython.NULL + if "metadata" in entry: + dict_to_avdict( + cython.address(ch.metadata), + entry["metadata"], + self.metadata_encoding, + self.metadata_errors, + ) + ch_array[i] = ch + + self.ptr.nb_chapters = count + self.ptr.chapters = ch_array + + +def open( + file, + mode=None, + format=None, + options=None, + container_options=None, + stream_options=None, + metadata_encoding="utf-8", + metadata_errors="strict", + buffer_size=32768, + timeout=None, + io_open=None, + hwaccel=None, +): + """open(file, mode='r', **kwargs) + + Main entrypoint to opening files/streams. + + :param str file: The file to open, which can be either a string or a file-like object. + :param str mode: ``"r"`` for reading and ``"w"`` for writing. + :param str format: Specific format to use. Defaults to autodect. + :param dict options: Options to pass to the container and all streams. + :param dict container_options: Options to pass to the container. + :param list stream_options: Options to pass to each stream. + :param str metadata_encoding: Encoding to use when reading or writing file metadata. + Defaults to ``"utf-8"``. + :param str metadata_errors: Specifies how to handle encoding errors; behaves like + ``str.encode`` parameter. Defaults to ``"strict"``. + :param int buffer_size: Size of buffer for Python input/output operations in bytes. + Honored only when ``file`` is a file-like object. Defaults to 32768 (32k). + :param timeout: How many seconds to wait for data before giving up, as a float, or a + ``(open timeout, read timeout)`` tuple. + :param callable io_open: Custom I/O callable for opening files/streams. + This option is intended for formats that need to open additional + file-like objects to ``file`` using custom I/O. + The callable signature is ``io_open(url: str, flags: int, options: dict)``, where + ``url`` is the url to open, ``flags`` is a combination of AVIO_FLAG_* and + ``options`` is a dictionary of additional options. The callable should return a + file-like object. + :param HWAccel hwaccel: Optional settings for hardware-accelerated decoding. + :rtype: Container + + For devices (via ``libavdevice``), pass the name of the device to ``format``, + e.g.:: + + >>> # Open webcam on MacOS. + >>> av.open('0', format='avfoundation') # doctest: +SKIP + + For DASH and custom I/O using ``io_open``, add a protocol prefix to the ``file`` to + prevent the DASH encoder defaulting to the file protocol and using temporary files. + The custom I/O callable can be used to remove the protocol prefix to reveal the actual + name for creating the file-like object. E.g.:: + + >>> av.open("customprotocol://manifest.mpd", "w", io_open=custom_io) # doctest: +SKIP + + .. seealso:: :ref:`garbage_collection` + + More information on using input and output devices is available on the + `FFmpeg website `_. + """ + + if not (mode is None or (isinstance(mode, str) and mode == "r" or mode == "w")): + raise ValueError(f"mode must be 'r', 'w', or None, got: {mode}") + + if isinstance(file, str): + pass + elif isinstance(file, Path): + file = f"{file}" + elif mode is None: + mode = getattr(file, "mode", None) + + if mode is None: + mode = "r" + + if isinstance(timeout, tuple): + if not len(timeout) == 2: + raise ValueError("timeout must be `float` or `tuple[float, float]`") + + open_timeout, read_timeout = timeout + else: + open_timeout = timeout + read_timeout = timeout + + if mode.startswith("r"): + return InputContainer( + _cinit_sentinel, + file, + format, + options, + container_options, + stream_options, + hwaccel, + metadata_encoding, + metadata_errors, + buffer_size, + open_timeout, + read_timeout, + io_open, + ) + + if stream_options: + raise ValueError( + "Provide stream options via Container.add_stream(..., options={})." + ) + return OutputContainer( + _cinit_sentinel, + file, + format, + options, + container_options, + stream_options, + None, + metadata_encoding, + metadata_errors, + buffer_size, + open_timeout, + read_timeout, + io_open, + ) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/core.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/container/core.pyi new file mode 100644 index 0000000000000000000000000000000000000000..81e4936e4bcc94769093de22a92bf563a6ad48ee --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/core.pyi @@ -0,0 +1,167 @@ +from enum import Flag, IntEnum +from fractions import Fraction +from pathlib import Path +from types import TracebackType +from typing import Any, Callable, ClassVar, Literal, Type, TypedDict, cast, overload + +from av.codec.hwaccel import HWAccel +from av.format import ContainerFormat + +from .input import InputContainer +from .output import OutputContainer +from .streams import StreamContainer + +Real = int | float | Fraction + +class Flags(Flag): + gen_pts = cast(ClassVar[Flags], ...) + ign_idx = cast(ClassVar[Flags], ...) + non_block = cast(ClassVar[Flags], ...) + ign_dts = cast(ClassVar[Flags], ...) + no_fillin = cast(ClassVar[Flags], ...) + no_parse = cast(ClassVar[Flags], ...) + no_buffer = cast(ClassVar[Flags], ...) + custom_io = cast(ClassVar[Flags], ...) + discard_corrupt = cast(ClassVar[Flags], ...) + flush_packets = cast(ClassVar[Flags], ...) + bitexact = cast(ClassVar[Flags], ...) + sort_dts = cast(ClassVar[Flags], ...) + fast_seek = cast(ClassVar[Flags], ...) + shortest = cast(ClassVar[Flags], ...) + auto_bsf = cast(ClassVar[Flags], ...) + +class AudioCodec(IntEnum): + none = cast(int, ...) + pcm_alaw = cast(int, ...) + pcm_bluray = cast(int, ...) + pcm_dvd = cast(int, ...) + pcm_f16le = cast(int, ...) + pcm_f24le = cast(int, ...) + pcm_f32be = cast(int, ...) + pcm_f32le = cast(int, ...) + pcm_f64be = cast(int, ...) + pcm_f64le = cast(int, ...) + pcm_lxf = cast(int, ...) + pcm_mulaw = cast(int, ...) + pcm_s16be = cast(int, ...) + pcm_s16be_planar = cast(int, ...) + pcm_s16le = cast(int, ...) + pcm_s16le_planar = cast(int, ...) + pcm_s24be = cast(int, ...) + pcm_s24daud = cast(int, ...) + pcm_s24le = cast(int, ...) + pcm_s24le_planar = cast(int, ...) + pcm_s32be = cast(int, ...) + pcm_s32le = cast(int, ...) + pcm_s32le_planar = cast(int, ...) + pcm_s64be = cast(int, ...) + pcm_s64le = cast(int, ...) + pcm_s8 = cast(int, ...) + pcm_s8_planar = cast(int, ...) + pcm_u16be = cast(int, ...) + pcm_u16le = cast(int, ...) + pcm_u24be = cast(int, ...) + pcm_u24le = cast(int, ...) + pcm_u32be = cast(int, ...) + pcm_u32le = cast(int, ...) + pcm_u8 = cast(int, ...) + pcm_vidc = cast(int, ...) + +class Chapter(TypedDict): + id: int + start: int + end: int + time_base: Fraction | None + metadata: dict[str, str] + +class Container: + name: str + metadata_encoding: str + metadata_errors: str + file: Any + buffer_size: int + io_open: Any + open_files: Any + format: ContainerFormat + options: dict[str, str] + container_options: dict[str, str] + stream_options: list[dict[str, str]] + streams: StreamContainer + metadata: dict[str, str] + open_timeout: Real | None + read_timeout: Real | None + flags: int + video_codec_id: int + def __enter__(self) -> Container: ... + def __exit__( + self, + exc_type: Type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: ... + @property + def input_was_opened(self) -> bool: ... + def close(self) -> None: ... + def chapters(self) -> list[Chapter]: ... + def set_chapters(self, chapters: list[Chapter]) -> None: ... + +@overload +def open( + file: Any, + mode: Literal["r"], + format: str | None = None, + options: dict[str, str] | None = None, + container_options: dict[str, str] | None = None, + stream_options: list[str] | None = None, + metadata_encoding: str = "utf-8", + metadata_errors: str = "strict", + buffer_size: int = 32768, + timeout: Real | None | tuple[Real | None, Real | None] = None, + io_open: Callable[..., Any] | None = None, + hwaccel: HWAccel | None = None, +) -> InputContainer: ... +@overload +def open( + file: str | Path, + mode: Literal["r"] | None = None, + format: str | None = None, + options: dict[str, str] | None = None, + container_options: dict[str, str] | None = None, + stream_options: list[str] | None = None, + metadata_encoding: str = "utf-8", + metadata_errors: str = "strict", + buffer_size: int = 32768, + timeout: Real | None | tuple[Real | None, Real | None] = None, + io_open: Callable[..., Any] | None = None, + hwaccel: HWAccel | None = None, +) -> InputContainer: ... +@overload +def open( + file: Any, + mode: Literal["w"], + format: str | None = None, + options: dict[str, str] | None = None, + container_options: dict[str, str] | None = None, + stream_options: list[str] | None = None, + metadata_encoding: str = "utf-8", + metadata_errors: str = "strict", + buffer_size: int = 32768, + timeout: Real | None | tuple[Real | None, Real | None] = None, + io_open: Callable[..., Any] | None = None, + hwaccel: HWAccel | None = None, +) -> OutputContainer: ... +@overload +def open( + file: Any, + mode: Literal["r", "w"] | None = None, + format: str | None = None, + options: dict[str, str] | None = None, + container_options: dict[str, str] | None = None, + stream_options: list[str] | None = None, + metadata_encoding: str = "utf-8", + metadata_errors: str = "strict", + buffer_size: int = 32768, + timeout: Real | None | tuple[Real | None, Real | None] = None, + io_open: Callable[..., Any] | None = None, + hwaccel: HWAccel | None = None, +) -> InputContainer | OutputContainer: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/input.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/container/input.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..94ea199d5234747448e63fe4b741ca0e9c7453d9 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/input.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ada9314227b2b79078257a35cfea939db9b3e76b4be7be1fcc6b131d1b11ab6d +size 157504 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/input.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/container/input.pxd new file mode 100644 index 0000000000000000000000000000000000000000..2e65025b9f0df813ecaca624b6785954a7d1a47a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/input.pxd @@ -0,0 +1,8 @@ +cimport libav as lib + +from av.container.core cimport Container +from av.stream cimport Stream + + +cdef class InputContainer(Container): + cdef flush_buffers(self) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/input.py b/.venvs/kimodo/lib/python3.10/site-packages/av/container/input.py new file mode 100644 index 0000000000000000000000000000000000000000..f5fc52547ba9e0fb6e8905455d8b47f093f8171f --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/input.py @@ -0,0 +1,318 @@ +import cython +from cython.cimports.av.codec.context import CodecContext, wrap_codec_context +from cython.cimports.av.container.streams import StreamContainer +from cython.cimports.av.dictionary import Dictionary +from cython.cimports.av.error import err_check +from cython.cimports.av.packet import Packet +from cython.cimports.av.stream import Stream, wrap_stream +from cython.cimports.av.utils import avdict_to_dict +from cython.cimports.libc.stdint import int64_t, uint8_t +from cython.cimports.libc.stdlib import free, malloc + + +@cython.cfunc +def close_input(self: InputContainer): + self.streams = StreamContainer() + with cython.nogil: + if self._myflag & 2: + # This causes `self.ptr` to be set to NULL. + lib.avformat_close_input(cython.address(self.ptr)) + self._myflag &= ~2 # enum.input_was_opened = False + + +@cython.final +@cython.cclass +class InputContainer(Container): + def __cinit__(self, *args, **kwargs): + py_codec_context: CodecContext + i: cython.uint + stream: cython.pointer[lib.AVStream] + codec: cython.pointer[cython.const[lib.AVCodec]] + codec_context: cython.pointer[lib.AVCodecContext] + + # If we have either the global `options`, or a `stream_options`, prepare + # a mashup of those options for each stream. + c_options: cython.pointer[cython.pointer[lib.AVDictionary]] = cython.NULL + base_dict: Dictionary + stream_dict: Dictionary + nb_streams_before: cython.uint = self.ptr.nb_streams + if self.stream_options and nb_streams_before == 0: + raise ValueError( + "stream_options were provided, but this format does not expose " + "its streams before avformat_find_stream_info (e.g. MPEG). " + "Per-stream options cannot be applied." + ) + # Only allocate c_options when streams are already known. + if (self.options or self.stream_options) and nb_streams_before > 0: + base_dict = Dictionary(self.options) + c_options = cython.cast( + cython.pointer[cython.pointer[lib.AVDictionary]], + malloc(nb_streams_before * cython.sizeof(cython.p_void)), + ) + for i in range(nb_streams_before): + c_options[i] = cython.NULL + if i < len(self.stream_options) and self.stream_options: + stream_dict = base_dict.copy() + stream_dict.update(self.stream_options[i]) + lib.av_dict_copy(cython.address(c_options[i]), stream_dict.ptr, 0) + else: + lib.av_dict_copy(cython.address(c_options[i]), base_dict.ptr, 0) + + self.set_timeout(self.open_timeout) + self.start_timeout() + with cython.nogil: + ret = lib.avformat_find_stream_info(self.ptr, c_options) + self.set_timeout(None) + self.err_check(ret) + + if c_options: + for i in range(nb_streams_before): + lib.av_dict_free(cython.address(c_options[i])) + free(c_options) + + at_least_one_accelerated_context = False + + self.streams = StreamContainer() + for i in range(self.ptr.nb_streams): + stream = self.ptr.streams[i] + codec = lib.avcodec_find_decoder(stream.codecpar.codec_id) + if codec: + codec_context = lib.avcodec_alloc_context3(codec) + err_check( + lib.avcodec_parameters_to_context(codec_context, stream.codecpar) + ) + codec_context.pkt_timebase = stream.time_base + py_codec_context = wrap_codec_context( + codec_context, codec, self.hwaccel + ) + if py_codec_context.is_hwaccel: + at_least_one_accelerated_context = True + else: + # no decoder is available + py_codec_context = None + self.streams.add_stream(wrap_stream(self, stream, py_codec_context)) + + if ( + self.hwaccel + and not self.hwaccel.allow_software_fallback + and not at_least_one_accelerated_context + ): + raise RuntimeError( + "Hardware accelerated decode requested but no stream is compatible" + ) + + self.metadata = avdict_to_dict( + self.ptr.metadata, self.metadata_encoding, self.metadata_errors + ) + + def __dealloc__(self): + close_input(self) + + @property + def start_time(self): + self._assert_open() + if self.ptr.start_time != lib.AV_NOPTS_VALUE: + return self.ptr.start_time + + @property + def duration(self): + self._assert_open() + if self.ptr.duration != lib.AV_NOPTS_VALUE: + return self.ptr.duration + + @property + def bit_rate(self): + self._assert_open() + return self.ptr.bit_rate + + @property + def size(self): + self._assert_open() + return lib.avio_size(self.ptr.pb) + + def close(self): + close_input(self) + + def demux(self, *args, **kwargs): + """demux(streams=None, video=None, audio=None, subtitles=None, data=None) + + Yields a series of :class:`.Packet` from the given set of :class:`.Stream`:: + + for packet in container.demux(): + # Do something with `packet`, often: + for frame in packet.decode(): + # Do something with `frame`. + + .. seealso:: :meth:`.StreamContainer.get` for the interpretation of + the arguments. + + .. note:: The last packets are dummy packets that when decoded will flush the buffers. + + """ + self._assert_open() + + streams: list[Stream] = self.streams.get(*args, **kwargs) + if self.ptr.nb_streams == 0: + return + include_stream: cython.pointer[uint8_t] = cython.cast( + cython.pointer[uint8_t], + malloc(self.ptr.nb_streams * cython.sizeof(uint8_t)), + ) + if include_stream == cython.NULL: + raise MemoryError() + + i: cython.uint + packet: Packet + read_packet: cython.pointer[lib.AVPacket] + ret: cython.int + + self.set_timeout(self.read_timeout) + try: + for i in range(self.ptr.nb_streams): + include_stream[i] = 0 + for stream in streams: + i = stream.index + if i >= self.ptr.nb_streams: + raise ValueError(f"stream index {i} out of range") + include_stream[i] = 1 + + # Pre-allocate a AVPacket that is reused as the read buffer. + with cython.nogil: + read_packet = lib.av_packet_alloc() + if read_packet == cython.NULL: + raise MemoryError("Could not allocate packet") + + while True: + # Reset the read buffer + with cython.nogil: + lib.av_packet_unref(read_packet) + try: + self.start_timeout() + with cython.nogil: + ret = lib.av_read_frame(self.ptr, read_packet) + self.err_check(ret) + except EOFError: + break + + if include_stream[read_packet.stream_index]: + # If AVFMTCTX_NOHEADER is set in ctx_flags, then new streams + # may also appear in av_read_frame(). + # http://ffmpeg.org/doxygen/trunk/structAVFormatContext.html + # TODO: find better way to handle this + if read_packet.stream_index < len(self.streams): + # Move the encoded data out of the read buffer into a + # fresh Packet for the caller. + packet = Packet() + with cython.nogil: + lib.av_packet_move_ref(packet.ptr, read_packet) + packet._stream = self.streams[packet.ptr.stream_index] + # Keep track of this so that remuxing is easier. + packet.ptr.time_base = packet._stream.ptr.time_base + yield packet + + # Flush! + for i in range(self.ptr.nb_streams): + if include_stream[i]: + packet = Packet() + packet._stream = self.streams[i] + packet.ptr.time_base = packet._stream.ptr.time_base + yield packet + + finally: + self.set_timeout(None) + free(include_stream) + if read_packet != cython.NULL: + lib.av_packet_free(cython.address(read_packet)) + + def decode(self, *args, **kwargs): + """decode(streams=None, video=None, audio=None, subtitles=None, data=None) + + Yields a series of :class:`.Frame` from the given set of streams:: + + for frame in container.decode(): + # Do something with `frame`. + + .. seealso:: :meth:`.StreamContainer.get` for the interpretation of + the arguments. + + """ + self._assert_open() + for packet in self.demux(*args, **kwargs): + for frame in packet.decode(): + yield frame + + def seek( + self, + offset, + *, + backward: cython.bint = True, + any_frame: cython.bint = False, + stream: Stream | None = None, + unsupported_frame_offset: cython.bint = False, + unsupported_byte_offset: cython.bint = False, + ): + """seek(offset, *, backward=True, any_frame=False, stream=None) + + Seek to a (key)frame nearest to the given timestamp. + + :param int offset: Time to seek to, expressed in``stream.time_base`` if ``stream`` + is given, otherwise in :data:`av.time_base`. + :param bool backward: If there is not a (key)frame at the given offset, + look backwards for it. + :param bool any_frame: Seek to any frame, not just a keyframe. + :param Stream stream: The stream who's ``time_base`` the ``offset`` is in. + + :param bool unsupported_frame_offset: ``offset`` is a frame + index instead of a time; not supported by any known format. + :param bool unsupported_byte_offset: ``offset`` is a byte + location in the file; not supported by any known format. + + After seeking, packets that you demux should correspond (roughly) to + the position you requested. + + In most cases, the defaults of ``backwards = True`` and ``any_frame = False`` + are the best course of action, followed by you demuxing/decoding to + the position that you want. This is because to properly decode video frames + you need to start from the previous keyframe. + + .. seealso:: :ffmpeg:`avformat_seek_file` for discussion of the flags. + + """ + self._assert_open() + + if not isinstance(offset, int): + raise TypeError("Container.seek only accepts integer offset.", type(offset)) + + c_offset: int64_t = offset + flags: cython.int = 0 + ret: cython.int + + if backward: + flags |= lib.AVSEEK_FLAG_BACKWARD + if any_frame: + flags |= lib.AVSEEK_FLAG_ANY + + # If someone really wants (and to experiment), expose these. + if unsupported_frame_offset: + flags |= lib.AVSEEK_FLAG_FRAME + if unsupported_byte_offset: + flags |= lib.AVSEEK_FLAG_BYTE + + stream_index: cython.int = stream.index if stream else -1 + with cython.nogil: + ret = lib.av_seek_frame(self.ptr, stream_index, c_offset, flags) + err_check(ret) + + self.flush_buffers() + + @cython.cfunc + def flush_buffers(self): + self._assert_open() + + stream: Stream + codec_context: CodecContext + + for stream in self.streams: + codec_context = stream.codec_context + if codec_context: + codec_context.flush_buffers() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/input.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/container/input.pyi new file mode 100644 index 0000000000000000000000000000000000000000..a5f829fefe23e2ebca80a7673a11b5b0efe08b0e --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/input.pyi @@ -0,0 +1,89 @@ +from typing import Any, Iterator, overload + +from av.audio.frame import AudioFrame +from av.audio.stream import AudioStream +from av.packet import Packet +from av.stream import AttachmentStream, DataStream, Stream +from av.subtitles.stream import SubtitleStream +from av.subtitles.subtitle import SubtitleSet +from av.video.frame import VideoFrame +from av.video.stream import VideoStream + +from .core import Container + +class InputContainer(Container): + start_time: int + duration: int | None + bit_rate: int + size: int + + def __enter__(self) -> InputContainer: ... + @overload + def demux(self, video_stream: VideoStream) -> Iterator[Packet[VideoStream]]: ... + @overload + def demux( + self, video_streams: tuple[VideoStream, ...] + ) -> Iterator[Packet[VideoStream]]: ... + @overload + def demux(self, *, video: Any) -> Iterator[Packet[VideoStream]]: ... + @overload + def demux(self, audio_stream: AudioStream) -> Iterator[Packet[AudioStream]]: ... + @overload + def demux( + self, audio_streams: tuple[AudioStream, ...] + ) -> Iterator[Packet[AudioStream]]: ... + @overload + def demux(self, *, audio: Any) -> Iterator[Packet[AudioStream]]: ... + @overload + def demux( + self, subtitle_stream: SubtitleStream + ) -> Iterator[Packet[SubtitleStream]]: ... + @overload + def demux( + self, subtitle_streams: tuple[SubtitleStream, ...] + ) -> Iterator[Packet[SubtitleStream]]: ... + @overload + def demux(self, data_stream: DataStream) -> Iterator[Packet[DataStream]]: ... + @overload + def demux( + self, data_streams: tuple[DataStream, ...] + ) -> Iterator[Packet[DataStream]]: ... + @overload + def demux(self, *, data: Any) -> Iterator[Packet[DataStream]]: ... + @overload + def demux( + self, attachment_stream: AttachmentStream + ) -> Iterator[Packet[AttachmentStream]]: ... + @overload + def demux( + self, attachment_streams: tuple[AttachmentStream, ...] + ) -> Iterator[Packet[AttachmentStream]]: ... + @overload + def demux(self, *args: Any, **kwargs: Any) -> Iterator[Packet[Stream]]: ... + @overload + def decode(self, video: int) -> Iterator[VideoFrame]: ... + @overload + def decode(self, audio: int) -> Iterator[AudioFrame]: ... + @overload + def decode(self, subtitles: int) -> Iterator[SubtitleSet]: ... + @overload + def decode(self, *args: VideoStream) -> Iterator[VideoFrame]: ... + @overload + def decode(self, *args: AudioStream) -> Iterator[AudioFrame]: ... + @overload + def decode(self, *args: SubtitleStream) -> Iterator[SubtitleSet]: ... + @overload + def decode( + self, *args: Any, **kwargs: Any + ) -> Iterator[VideoFrame | AudioFrame | SubtitleSet]: ... + def seek( + self, + offset: int, + *, + backward: bool = True, + any_frame: bool = False, + stream: Stream | VideoStream | AudioStream | None = None, + unsupported_frame_offset: bool = False, + unsupported_byte_offset: bool = False, + ) -> None: ... + def flush_buffers(self) -> None: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/output.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/container/output.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..24b42a86fc6471217dd75e26bb3c5c98084da598 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/output.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c0585d71d5310922c3a00198d6090077754e8c1c4ac518bcb3854d99d12d862f +size 156176 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/output.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/container/output.pxd new file mode 100644 index 0000000000000000000000000000000000000000..71edc3bf2eb4baf4b492f88a264f9f14b4edd9ab --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/output.pxd @@ -0,0 +1,9 @@ +cimport libav as lib + +from av.container.core cimport Container +from av.stream cimport Stream + + +cdef class OutputContainer(Container): + cdef lib.AVPacket *packet_ptr + cpdef start_encoding(self) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/output.py b/.venvs/kimodo/lib/python3.10/site-packages/av/container/output.py new file mode 100644 index 0000000000000000000000000000000000000000..239e550f27102ab9cdeff0c0d31b47dbac244f58 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/output.py @@ -0,0 +1,579 @@ +import os + +import cython +from cython.cimports import libav as lib +from cython.cimports.av.codec.codec import Codec +from cython.cimports.av.codec.context import CodecContext, wrap_codec_context +from cython.cimports.av.container.streams import StreamContainer +from cython.cimports.av.dictionary import Dictionary +from cython.cimports.av.error import err_check +from cython.cimports.av.packet import Packet +from cython.cimports.av.stream import Stream, wrap_stream +from cython.cimports.av.utils import dict_to_avdict, to_avrational + + +@cython.cfunc +def close_output(self: OutputContainer): + self.streams = StreamContainer() + if self._myflag & 12 == 4: # enum.started and not enum.done + # If the underlying Python IO file was already closed (e.g. during GC + # finalization where cycle ordering is undefined), skip the trailer. + if self.file is not None and getattr(self.file.file, "closed", False): + self._myflag |= 8 # enum.done = True + return + # We must only ever call av_write_trailer *once*, otherwise we get a + # segmentation fault. Therefore no matter whether it succeeds or not + # we must absolutely set enum.done. + try: + self.err_check(lib.av_write_trailer(self.ptr)) + finally: + if self.file is None and not (self.ptr.oformat.flags & lib.AVFMT_NOFILE): + lib.avio_closep(cython.address(self.ptr.pb)) + self._myflag |= 8 # enum.done = True + + +@cython.final +@cython.cclass +class OutputContainer(Container): + def __cinit__(self, *args, **kwargs): + self.streams = StreamContainer() + self.metadata = {} + with cython.nogil: + self.packet_ptr = lib.av_packet_alloc() + + def __del__(self): + close_output(self) + + def __dealloc__(self): + with cython.nogil: + lib.av_packet_free(cython.address(self.packet_ptr)) + + def add_stream(self, codec_name, rate=None, options: dict | None = None, **kwargs): + """add_stream(codec_name, rate=None) + + Creates a new stream from a codec name and returns it. + Supports video, audio, and subtitle streams. + + :param codec_name: The name of a codec. + :type codec_name: str + :param dict options: Stream options. + :param \\**kwargs: Set attributes for the stream. + :rtype: The new :class:`~av.stream.Stream`. + + """ + + codec_obj: Codec = Codec(codec_name, "w") + codec: cython.pointer[cython.const[lib.AVCodec]] = codec_obj.ptr + + # Assert that this format supports the requested codec. + if not lib.avformat_query_codec( + self.ptr.oformat, codec.id, lib.FF_COMPLIANCE_NORMAL + ): + raise ValueError( + f"{self.format.name!r} format does not support {codec_obj.name!r} codec" + ) + + # Create new stream in the AVFormatContext, set AVCodecContext values. + stream: cython.pointer[lib.AVStream] = lib.avformat_new_stream(self.ptr, codec) + ctx: cython.pointer[lib.AVCodecContext] = lib.avcodec_alloc_context3(codec) + + # Now lets set some more sane video defaults + if codec.type == lib.AVMEDIA_TYPE_VIDEO: + ctx.pix_fmt = lib.AV_PIX_FMT_YUV420P + ctx.width = kwargs.pop("width", 640) + ctx.height = kwargs.pop("height", 480) + ctx.bit_rate = kwargs.pop("bit_rate", 0) + ctx.bit_rate_tolerance = kwargs.pop("bit_rate_tolerance", 128000) + try: + to_avrational(kwargs.pop("time_base"), cython.address(ctx.time_base)) + except KeyError: + pass + to_avrational(rate or 24, cython.address(ctx.framerate)) + + stream.avg_frame_rate = ctx.framerate + stream.time_base = ctx.time_base + + # Some sane audio defaults + elif codec.type == lib.AVMEDIA_TYPE_AUDIO: + out: cython.pointer[cython.const[cython.void]] = cython.NULL + lib.avcodec_get_supported_config( + cython.NULL, + codec, + lib.AV_CODEC_CONFIG_SAMPLE_FORMAT, + 0, + cython.address(out), + cython.NULL, + ) + if out: + ctx.sample_fmt = cython.cast(cython.pointer[lib.AVSampleFormat], out)[0] + ctx.bit_rate = kwargs.pop("bit_rate", 0) + ctx.bit_rate_tolerance = kwargs.pop("bit_rate_tolerance", 32000) + try: + to_avrational(kwargs.pop("time_base"), cython.address(ctx.time_base)) + except KeyError: + pass + + if rate is None: + ctx.sample_rate = 48000 + elif type(rate) is int: + ctx.sample_rate = rate + else: + raise TypeError("audio stream `rate` must be: int | None") + stream.time_base = ctx.time_base + lib.av_channel_layout_default(cython.address(ctx.ch_layout), 2) + + # Some formats want stream headers to be separate + if self.ptr.oformat.flags & lib.AVFMT_GLOBALHEADER: + ctx.flags |= lib.AV_CODEC_FLAG_GLOBAL_HEADER + + # Initialise stream codec parameters to populate the codec type. + # + # Subsequent changes to the codec context will be applied just before + # encoding starts in `start_encoding()`. + err_check(lib.avcodec_parameters_from_context(stream.codecpar, ctx)) + + # Construct the user-land stream + py_codec_context: CodecContext = wrap_codec_context(ctx, codec, None) + py_stream: Stream = wrap_stream(self, stream, py_codec_context) + self.streams.add_stream(py_stream) + + if options: + py_stream.options.update(options) + + for k, v in kwargs.items(): + setattr(py_stream, k, v) + + return py_stream + + def add_mux_stream(self, codec_name: str, rate=None, **kwargs) -> Stream: + """add_mux_stream(codec_name, rate=None) + + Creates a new stream for muxing pre-encoded data without creating a + :class:`.CodecContext`. Use this when you want to mux packets that were + already encoded externally and no encoding/decoding is needed. + + :param codec_name: The name of a codec. + :type codec_name: str + :param \\**kwargs: Set attributes for the stream (e.g. ``width``, ``height``, + ``time_base``). + :rtype: The new :class:`~av.stream.Stream`. + + """ + # Find the codec to get its id and type (try encoder first, then decoder). + codec_name_bytes: bytes = codec_name.encode() + codec: cython.pointer[cython.const[lib.AVCodec]] = ( + lib.avcodec_find_encoder_by_name(codec_name_bytes) + ) + codec_descriptor: cython.pointer[cython.const[lib.AVCodecDescriptor]] = ( + cython.NULL + ) + if codec == cython.NULL: + codec = lib.avcodec_find_decoder_by_name(codec_name_bytes) + if codec == cython.NULL: + codec_descriptor = lib.avcodec_descriptor_get_by_name(codec_name_bytes) + if codec_descriptor == cython.NULL: + raise ValueError(f"Unknown codec: {codec_name!r}") + + codec_id: lib.AVCodecID + codec_type: lib.AVMediaType + if codec != cython.NULL: + codec_id = codec.id + codec_type = codec.type + else: + codec_id = codec_descriptor.id + codec_type = codec_descriptor.type + + # Assert that this format supports the requested codec. + if not lib.avformat_query_codec( + self.ptr.oformat, codec_id, lib.FF_COMPLIANCE_NORMAL + ): + raise ValueError( + f"{self.format.name!r} format does not support {codec_name!r} codec" + ) + + # Create stream with no codec context. + stream: cython.pointer[lib.AVStream] = lib.avformat_new_stream( + self.ptr, cython.NULL + ) + if stream == cython.NULL: + raise MemoryError("Could not allocate stream") + + stream.codecpar.codec_id = codec_id + stream.codecpar.codec_type = codec_type + + if codec_type == lib.AVMEDIA_TYPE_VIDEO: + stream.codecpar.width = kwargs.pop("width", 0) + stream.codecpar.height = kwargs.pop("height", 0) + if rate is not None: + to_avrational(rate, cython.address(stream.avg_frame_rate)) + elif codec_type == lib.AVMEDIA_TYPE_AUDIO: + if rate is not None: + if type(rate) is int: + stream.codecpar.sample_rate = rate + else: + raise TypeError("audio stream `rate` must be: int | None") + + # Construct the user-land stream (no codec context). + py_stream: Stream = wrap_stream(self, stream, None) + self.streams.add_stream(py_stream) + + for k, v in kwargs.items(): + setattr(py_stream, k, v) + + return py_stream + + def add_stream_from_template( + self, template: Stream, opaque: bool | None = None, **kwargs + ): + """ + Creates a new stream from a template. Supports video, audio, subtitle, data and attachment streams. + + :param template: Copy codec from another :class:`~av.stream.Stream` instance. + :param opaque: If True, copy opaque data from the template's codec context. + :param \\**kwargs: Set attributes for the stream. + :rtype: The new :class:`~av.stream.Stream`. + """ + if opaque is None: + opaque = template.type != "video" + + if template.codec_context is None: + return self._add_stream_without_codec_from_template(template, **kwargs) + + codec_obj: Codec + if opaque: # Copy ctx from template. + codec_obj = template.codec_context.codec + else: # Construct new codec object. + codec_obj = Codec(template.codec_context.codec.name, "w") + + codec: cython.pointer[cython.const[lib.AVCodec]] = codec_obj.ptr + + # Assert that this format supports the requested codec. + if not lib.avformat_query_codec( + self.ptr.oformat, codec.id, lib.FF_COMPLIANCE_NORMAL + ): + raise ValueError( + f"{self.format.name!r} format does not support {codec_obj.name!r} codec" + ) + + # Create new stream in the AVFormatContext, set AVCodecContext values. + stream: cython.pointer[lib.AVStream] = lib.avformat_new_stream(self.ptr, codec) + ctx: cython.pointer[lib.AVCodecContext] = lib.avcodec_alloc_context3(codec) + + err_check(lib.avcodec_parameters_to_context(ctx, template.ptr.codecpar)) + # Reset the codec tag assuming we are remuxing. + ctx.codec_tag = 0 + + # Copy the template's stream time_base + stream.time_base = template.ptr.time_base + ctx.time_base = template.ptr.time_base + + # Some formats want stream headers to be separate + if self.ptr.oformat.flags & lib.AVFMT_GLOBALHEADER: + ctx.flags |= lib.AV_CODEC_FLAG_GLOBAL_HEADER + + # Copy flags If we're creating a new codec object. This fixes some muxing issues. + # Overwriting `ctx.flags |= lib.AV_CODEC_FLAG_GLOBAL_HEADER` is intentional. + if not opaque: + ctx.flags = template.codec_context.flags + + # Initialize stream codec parameters to populate the codec type. Subsequent changes to + # the codec context will be applied just before encoding starts in `start_encoding()`. + err_check(lib.avcodec_parameters_from_context(stream.codecpar, ctx)) + + # Construct the user-land stream + py_codec_context: CodecContext = wrap_codec_context(ctx, codec, None) + py_codec_context._ctxflags |= 1 # _template_initialized = True + py_stream: Stream = wrap_stream(self, stream, py_codec_context) + self.streams.add_stream(py_stream) + + for k, v in kwargs.items(): + setattr(py_stream, k, v) + + return py_stream + + def _add_stream_without_codec_from_template( + self, template: Stream, **kwargs + ) -> Stream: + codec_type: cython.int = template.ptr.codecpar.codec_type + if codec_type not in {lib.AVMEDIA_TYPE_ATTACHMENT, lib.AVMEDIA_TYPE_DATA}: + raise ValueError( + f"template stream of type {template.type} has no codec context" + ) + + stream: cython.pointer[lib.AVStream] = lib.avformat_new_stream( + self.ptr, cython.NULL + ) + if stream == cython.NULL: + raise MemoryError("Could not allocate stream") + + err_check(lib.avcodec_parameters_copy(stream.codecpar, template.ptr.codecpar)) + + # Mirror basic properties that are not derived from a codec context. + stream.time_base = template.ptr.time_base + stream.start_time = template.ptr.start_time + stream.duration = template.ptr.duration + stream.disposition = template.ptr.disposition + + py_stream: Stream = wrap_stream(self, stream, None) + self.streams.add_stream(py_stream) + + py_stream.metadata = dict(template.metadata) + + for k, v in kwargs.items(): + setattr(py_stream, k, v) + + return py_stream + + def add_attachment(self, name: str, mimetype: str, data: bytes): + """ + Create an attachment stream and embed its payload into the container header. + + - Only supported by formats that support attachments (e.g. Matroska). + - No per-packet muxing is required; attachments are written at header time. + """ + # Create stream with no codec (attachments are codec-less). + stream: cython.pointer[lib.AVStream] = lib.avformat_new_stream( + self.ptr, cython.NULL + ) + if stream == cython.NULL: + raise MemoryError("Could not allocate stream") + + stream.codecpar.codec_type = lib.AVMEDIA_TYPE_ATTACHMENT + stream.codecpar.codec_id = lib.AV_CODEC_ID_NONE + + # Allocate and copy payload into codecpar.extradata. + payload_size: cython.size_t = len(data) + if payload_size: + buf = cython.cast(cython.p_uchar, lib.av_malloc(payload_size + 1)) + if buf == cython.NULL: + raise MemoryError("Could not allocate attachment data") + # Copy bytes. + for i in range(payload_size): + buf[i] = data[i] + buf[payload_size] = 0 + stream.codecpar.extradata = cython.cast(cython.p_uchar, buf) + stream.codecpar.extradata_size = payload_size + + # Wrap as user-land stream. + meta_ptr = cython.address(stream.metadata) + err_check(lib.av_dict_set(meta_ptr, b"filename", name.encode(), 0)) + mime_bytes = mimetype.encode() + err_check(lib.av_dict_set(meta_ptr, b"mimetype", mime_bytes, 0)) + + py_stream: Stream = wrap_stream(self, stream, None) + self.streams.add_stream(py_stream) + return py_stream + + def add_data_stream(self, codec_name=None, options: dict | None = None): + """add_data_stream(codec_name=None) + + Creates a new data stream and returns it. + + :param codec_name: Optional name of the data codec (e.g. 'klv') + :type codec_name: str | None + :param dict options: Stream options. + :rtype: The new :class:`~av.data.stream.DataStream`. + """ + codec: cython.pointer[cython.const[lib.AVCodec]] = cython.NULL + codec_descriptor: cython.pointer[cython.const[lib.AVCodecDescriptor]] = ( + cython.NULL + ) + + if codec_name is not None: + codec_name_bytes: bytes = codec_name.encode() + codec = lib.avcodec_find_encoder_by_name(codec_name_bytes) + if codec == cython.NULL: + codec = lib.avcodec_find_decoder_by_name(codec_name_bytes) + if codec == cython.NULL: + codec_descriptor = lib.avcodec_descriptor_get_by_name(codec_name_bytes) + if codec_descriptor == cython.NULL: + raise ValueError(f"Unknown data codec: {codec_name}") + + # Verify format supports this codec + codec_id = codec.id if codec != cython.NULL else codec_descriptor.id + if not lib.avformat_query_codec( + self.ptr.oformat, codec_id, lib.FF_COMPLIANCE_NORMAL + ): + raise ValueError( + f"{self.format.name!r} format does not support {codec_name!r} codec" + ) + + # Create new stream in the AVFormatContext + stream: cython.pointer[lib.AVStream] = lib.avformat_new_stream(self.ptr, codec) + if stream == cython.NULL: + raise MemoryError("Could not allocate stream") + + # Set up codec context and parameters + ctx: cython.pointer[lib.AVCodecContext] = cython.NULL + if codec != cython.NULL: + ctx = lib.avcodec_alloc_context3(codec) + if ctx == cython.NULL: + raise MemoryError("Could not allocate codec context") + + # Some formats want stream headers to be separate + if self.ptr.oformat.flags & lib.AVFMT_GLOBALHEADER: + ctx.flags |= lib.AV_CODEC_FLAG_GLOBAL_HEADER + + # Initialize stream codec parameters + err_check(lib.avcodec_parameters_from_context(stream.codecpar, ctx)) + else: + # No codec available - set basic parameters for data stream + stream.codecpar.codec_type = lib.AVMEDIA_TYPE_DATA + if codec_descriptor != cython.NULL: + stream.codecpar.codec_id = codec_descriptor.id + + # Construct the user-land stream + py_codec_context: CodecContext | None = None + if ctx != cython.NULL: + py_codec_context = wrap_codec_context(ctx, codec, None) + + py_stream: Stream = wrap_stream(self, stream, py_codec_context) + self.streams.add_stream(py_stream) + + if options: + py_stream.options.update(options) + + return py_stream + + @cython.ccall + def start_encoding(self): + """Write the file header! Called automatically.""" + if self._myflag & 4: # started + return + + # TODO: This does NOT handle options coming from 3 sources. + # This is only a rough approximation of what would be cool to do. + used_options: set = set() + stream: Stream + + # Finalize and open all streams. + for stream in self.streams: + ctx = stream.codec_context + if ctx is not None and not ctx.is_open: + for k, v in self.options.items(): + ctx.options.setdefault(k, v) + + if not (ctx._ctxflags & 1): # template_initialized + ctx.open() + + # Track option consumption. + for k in self.options: + if k not in ctx.options: + used_options.add(k) + + stream._finalize_for_output() + + # Open the output file, if needed. + name_obj: bytes = os.fsencode(self.name if self.file is None else "") + name: cython.p_char = name_obj + if self.ptr.pb == cython.NULL and not self.ptr.oformat.flags & lib.AVFMT_NOFILE: + err_check( + lib.avio_open(cython.address(self.ptr.pb), name, lib.AVIO_FLAG_WRITE) + ) + + # Copy the metadata dict. + dict_to_avdict( + cython.address(self.ptr.metadata), + self.metadata, + encoding=self.metadata_encoding, + errors=self.metadata_errors, + ) + + all_options: Dictionary = Dictionary(self.options, self.container_options) + options: Dictionary = all_options.copy() + self.err_check(lib.avformat_write_header(self.ptr, cython.address(options.ptr))) + + # Track option usage... + for k in all_options: + if k not in options: + used_options.add(k) + + # ... and warn if any weren't used. + unused_options = { + k: v for k, v in self.options.items() if k not in used_options + } + if unused_options: + import logging + + log = logging.getLogger(__name__) + log.warning("Some options were not used: %s" % unused_options) + + self._myflag |= 4 + + @property + def supported_codecs(self): + """ + Returns a set of all codecs this format supports. + """ + result: set = set() + codec: cython.pointer[cython.const[lib.AVCodec]] = cython.NULL + opaque: cython.p_void = cython.NULL + + while True: + codec = lib.av_codec_iterate(cython.address(opaque)) + if codec == cython.NULL: + break + + if ( + lib.avformat_query_codec( + self.ptr.oformat, codec.id, lib.FF_COMPLIANCE_NORMAL + ) + == 1 + ): + result.add(codec.name) + + return result + + @property + def default_video_codec(self): + """ + Returns the default video codec this container recommends. + """ + return lib.avcodec_get_name(self.format.optr.video_codec) + + @property + def default_audio_codec(self): + """ + Returns the default audio codec this container recommends. + """ + return lib.avcodec_get_name(self.format.optr.audio_codec) + + @property + def default_subtitle_codec(self): + """ + Returns the default subtitle codec this container recommends. + """ + return lib.avcodec_get_name(self.format.optr.subtitle_codec) + + def close(self): + close_output(self) + + def mux(self, packets): + # We accept either a Packet, or a sequence of packets. This should smooth out + # the transition to the new encode API which returns a sequence of packets. + if isinstance(packets, Packet): + self.mux_one(packets) + else: + for packet in packets: + self.mux_one(packet) + + def mux_one(self, packet: Packet): + self.start_encoding() + + # Assert the packet is in stream time. + if ( + packet.ptr.stream_index < 0 + or cython.cast(cython.uint, packet.ptr.stream_index) >= self.ptr.nb_streams + ): + raise ValueError("Bad Packet stream_index.") + + stream: cython.pointer[lib.AVStream] = self.ptr.streams[packet.ptr.stream_index] + packet._rebase_time(stream.time_base) + + # Make another reference to the packet, as `av_interleaved_write_frame()` + # takes ownership of the reference. + self.err_check(lib.av_packet_ref(self.packet_ptr, packet.ptr)) + + with cython.nogil: + ret: cython.int = lib.av_interleaved_write_frame(self.ptr, self.packet_ptr) + self.err_check(ret) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/output.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/container/output.pyi new file mode 100644 index 0000000000000000000000000000000000000000..f7781be29e9d700552fc0c69cd5dc8ab7284f4b8 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/output.pyi @@ -0,0 +1,67 @@ +from fractions import Fraction +from typing import Sequence, TypeVar, overload + +from av.audio import _AudioCodecName +from av.audio.stream import AudioStream +from av.packet import Packet +from av.stream import AttachmentStream, DataStream, Stream +from av.subtitles.stream import SubtitleStream +from av.video import _VideoCodecName +from av.video.stream import VideoStream + +from .core import Container + +_StreamT = TypeVar("_StreamT", bound=Stream) + +class OutputContainer(Container): + def __enter__(self) -> OutputContainer: ... + @overload + def add_stream( + self, + codec_name: _AudioCodecName, + rate: int | None = None, + options: dict[str, str] | None = None, + **kwargs, + ) -> AudioStream: ... + @overload + def add_stream( + self, + codec_name: _VideoCodecName, + rate: Fraction | int | None = None, + options: dict[str, str] | None = None, + **kwargs, + ) -> VideoStream: ... + @overload + def add_stream( + self, + codec_name: str, + rate: Fraction | int | None = None, + options: dict[str, str] | None = None, + **kwargs, + ) -> VideoStream | AudioStream | SubtitleStream: ... + def add_mux_stream( + self, + codec_name: str, + rate: Fraction | int | None = None, + **kwargs, + ) -> Stream: ... + def add_stream_from_template( + self, template: _StreamT, opaque: bool | None = None, **kwargs + ) -> _StreamT: ... + def add_attachment( + self, name: str, mimetype: str, data: bytes + ) -> AttachmentStream: ... + def add_data_stream( + self, codec_name: str | None = None, options: dict[str, str] | None = None + ) -> DataStream: ... + def start_encoding(self) -> None: ... + def mux(self, packets: Packet | Sequence[Packet]) -> None: ... + def mux_one(self, packet: Packet) -> None: ... + @property + def default_video_codec(self) -> str: ... + @property + def default_audio_codec(self) -> str: ... + @property + def default_subtitle_codec(self) -> str: ... + @property + def supported_codecs(self) -> set[str]: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/pyio.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/container/pyio.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..c9e9f7284136eecdaae985b110cf66da0194f33b --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/pyio.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee88cae7542f611d2e292c377edc94cf0cd7ed23e6b5b4ab5b62d6aa4ed12f07 +size 113696 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/pyio.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/container/pyio.pxd new file mode 100644 index 0000000000000000000000000000000000000000..80edc8a6b7a95f6b3568fd24d93a0a4036f135b2 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/pyio.pxd @@ -0,0 +1,24 @@ +cimport libav as lib +from libc.stdint cimport int64_t, uint8_t + + +cdef int pyio_read(void *opaque, uint8_t *buf, int buf_size) noexcept nogil +cdef int pyio_write(void *opaque, const uint8_t *buf, int buf_size) noexcept nogil +cdef int64_t pyio_seek(void *opaque, int64_t offset, int whence) noexcept nogil +cdef int pyio_close_gil(lib.AVIOContext *pb) +cdef int pyio_close_custom_gil(lib.AVIOContext *pb) + +cdef class PyIOFile: + # File-like source. + cdef readonly object file + cdef object fread + cdef object fwrite + cdef object fseek + cdef object ftell + cdef object fclose + + # Custom IO for above. + cdef lib.AVIOContext *iocontext + cdef unsigned char *buffer + cdef long pos + cdef bint pos_is_valid diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/pyio.py b/.venvs/kimodo/lib/python3.10/site-packages/av/container/pyio.py new file mode 100644 index 0000000000000000000000000000000000000000..d5c420ea4801c9d2984fdddd652433093ec3e536 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/pyio.py @@ -0,0 +1,204 @@ +# type: ignore +import cython +from cython import NULL +from cython.cimports import libav as lib +from cython.cimports.av.error import stash_exception +from cython.cimports.libc.stdint import int64_t, uint8_t +from cython.cimports.libc.string import memcpy + +Buf = cython.typedef(cython.pointer[uint8_t]) +BufC = cython.typedef(cython.pointer[cython.const[uint8_t]]) + +seek_func_t = cython.typedef( + "int64_t (*seek_func_t)(void *opaque, int64_t offset, int whence) noexcept nogil" +) + + +@cython.final +@cython.cclass +class PyIOFile: + def __cinit__(self, file, buffer_size, writeable=None): + self.file = file + + seek_func: seek_func_t = NULL + + readable = getattr(self.file, "readable", None) + writable = getattr(self.file, "writable", None) + seekable = getattr(self.file, "seekable", None) + self.fread = getattr(self.file, "read", None) + self.fwrite = getattr(self.file, "write", None) + self.fseek = getattr(self.file, "seek", None) + self.ftell = getattr(self.file, "tell", None) + self.fclose = getattr(self.file, "close", None) + + # To be seekable, the file object must have `seek` and `tell` methods. + # If it also has a `seekable` method, it must return True. + if ( + self.fseek is not None + and self.ftell is not None + and (seekable is None or seekable()) + ): + seek_func: seek_func_t = pyio_seek + + if writeable is None: + writeable = self.fwrite is not None + + if writeable: + if self.fwrite is None or (writable is not None and not writable()): + raise ValueError( + "File object has no write() method, or writable() returned False." + ) + else: + if self.fread is None or (readable is not None and not readable()): + raise ValueError( + "File object has no read() method, or readable() returned False." + ) + + self.pos = 0 + self.pos_is_valid = True + + # This is effectively the maximum size of reads. + self.buffer = cython.cast(cython.p_uchar, lib.av_malloc(buffer_size)) + + self.iocontext = lib.avio_alloc_context( + self.buffer, + buffer_size, + writeable, + cython.cast(cython.p_void, self), # User data. + pyio_read, + pyio_write, + seek_func, + ) + + if seek_func: + self.iocontext.seekable = lib.AVIO_SEEKABLE_NORMAL + self.iocontext.max_packet_size = buffer_size + + def __dealloc__(self): + with cython.nogil: + # FFmpeg will not release custom input, so it's up to us to free it. + # Do not touch our original buffer as it may have been freed and replaced. + if self.iocontext: + lib.av_freep(cython.address(self.iocontext.buffer)) + lib.av_freep(cython.address(self.iocontext)) + + # We likely errored badly if we got here, and so we are still responsible. + else: + lib.av_freep(cython.address(self.buffer)) + + +@cython.cfunc +@cython.nogil +@cython.exceptval(check=False) +def pyio_read(opaque: cython.p_void, buf: Buf, buf_size: cython.int) -> cython.int: + with cython.gil: + return pyio_read_gil(opaque, buf, buf_size) + + +@cython.cfunc +@cython.exceptval(check=False) +def pyio_read_gil(opaque: cython.p_void, buf: Buf, buf_size: cython.int) -> cython.int: + self: PyIOFile + res: bytes + try: + self = cython.cast(PyIOFile, opaque) + res = self.fread(buf_size) + memcpy( + buf, cython.cast(cython.p_void, cython.cast(cython.p_char, res)), len(res) + ) + self.pos += len(res) + if not res: + return lib.AVERROR_EOF + return len(res) + except Exception: + return stash_exception() + + +@cython.cfunc +@cython.nogil +@cython.exceptval(check=False) +def pyio_write(opaque: cython.p_void, buf: BufC, buf_size: cython.int) -> cython.int: + with cython.gil: + return pyio_write_gil(opaque, buf, buf_size) + + +@cython.cfunc +@cython.exceptval(check=False) +def pyio_write_gil( + opaque: cython.p_void, buf: BufC, buf_size: cython.int +) -> cython.int: + self: PyIOFile + bytes_to_write: bytes + bytes_written: cython.int + try: + self = cython.cast(PyIOFile, opaque) + bytes_to_write = buf[:buf_size] + ret_value = self.fwrite(bytes_to_write) + bytes_written = ret_value if isinstance(ret_value, int) else buf_size + self.pos += bytes_written + return bytes_written + except Exception: + return stash_exception() + + +@cython.cfunc +@cython.nogil +@cython.exceptval(check=False) +def pyio_seek(opaque: cython.p_void, offset: int64_t, whence: cython.int) -> int64_t: + # Seek takes the standard flags, but also a ad-hoc one which means that the library + # wants to know how large the file is. We are generally allowed to ignore this. + if whence == lib.AVSEEK_SIZE: + return -1 + with cython.gil: + return pyio_seek_gil(opaque, offset, whence) + + +@cython.cfunc +def pyio_seek_gil( + opaque: cython.p_void, offset: int64_t, whence: cython.int +) -> int64_t: + self: PyIOFile + try: + self = cython.cast(PyIOFile, opaque) + res = self.fseek(offset, whence) + + # Track the position for the user. + if whence == 0: + self.pos = offset + elif whence == 1: + self.pos += offset + else: + self.pos_is_valid = False + if res is None: + if self.pos_is_valid: + res = self.pos + else: + res = self.ftell() + return res + except Exception: + return stash_exception() + + +@cython.cfunc +def pyio_close_gil(pb: cython.pointer[lib.AVIOContext]) -> cython.int: + try: + return lib.avio_close(pb) + except Exception: + return stash_exception() + + +@cython.cfunc +def pyio_close_custom_gil(pb: cython.pointer[lib.AVIOContext]) -> cython.int: + self: PyIOFile + try: + self = cython.cast(PyIOFile, pb.opaque) + + # Flush bytes in the AVIOContext buffers to the custom I/O + lib.avio_flush(pb) + + if self.fclose is not None: + self.fclose() + + return 0 + except Exception: + stash_exception() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/streams.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/container/streams.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..9e30b200aef918f48fb07396b4a360892da547a5 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/streams.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a43f431b3b87f6c28b60c79cc774234dc230f2b3b951afe9dcf3fe7c2e114171 +size 159696 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/streams.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/container/streams.pxd new file mode 100644 index 0000000000000000000000000000000000000000..d62174992b96e00a330cca7ee9d276e717cf21bd --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/streams.pxd @@ -0,0 +1,8 @@ +cimport libav as lib + +from av.stream cimport Stream + + +cdef class StreamContainer: + cdef list _streams + cdef void add_stream(self, Stream stream) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/streams.py b/.venvs/kimodo/lib/python3.10/site-packages/av/container/streams.py new file mode 100644 index 0000000000000000000000000000000000000000..e588694b4eeea2a29631b9d110c2e518551ab7ff --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/streams.py @@ -0,0 +1,178 @@ +from typing import Iterator + +import cython +import cython.cimports.libav as lib +from cython.cimports.av.stream import Stream + + +@cython.cfunc +def _get_media_type_enum(type: str) -> lib.AVMediaType: + if type == "video": + return lib.AVMEDIA_TYPE_VIDEO + elif type == "audio": + return lib.AVMEDIA_TYPE_AUDIO + elif type == "subtitle": + return lib.AVMEDIA_TYPE_SUBTITLE + elif type == "attachment": + return lib.AVMEDIA_TYPE_ATTACHMENT + elif type == "data": + return lib.AVMEDIA_TYPE_DATA + else: + raise ValueError(f"Invalid stream type: {type}") + + +@cython.cfunc +@cython.exceptval(check=False) +def _get_best_stream_index( + fmtctx: cython.pointer[lib.AVFormatContext], + enumtype: lib.AVMediaType, + related: Stream | None, +) -> cython.int: + stream_index: cython.int + + if related is None: + stream_index = lib.av_find_best_stream(fmtctx, enumtype, -1, -1, cython.NULL, 0) + else: + stream_index = lib.av_find_best_stream( + fmtctx, enumtype, -1, related.ptr.index, cython.NULL, 0 + ) + + return stream_index + + +@cython.final +@cython.cclass +class StreamContainer: + """ + + A tuple-like container of :class:`Stream`. + + :: + + # There are a few ways to pulling out streams. + first = container.streams[0] + video = container.streams.video[0] + audio = container.streams.get(audio=(0, 1)) + + + """ + + def __cinit__(self): + self._streams = [] + + @cython.cfunc + def add_stream(self, stream: Stream) -> cython.void: + assert stream.ptr.index == len(self._streams) + self._streams.append(stream) + + # Basic tuple interface. + def __len__(self): + return len(self._streams) + + def __iter__(self): + return iter(self._streams) + + def __getitem__(self, index): + if isinstance(index, int): + return self.get(index)[0] + else: + return self.get(index) + + @property + def video(self): + return tuple(s for s in self._streams if s.type == "video") + + @property + def audio(self): + return tuple(s for s in self._streams if s.type == "audio") + + @property + def subtitles(self): + return tuple(s for s in self._streams if s.type == "subtitle") + + @property + def data(self): + return tuple(s for s in self._streams if s.type == "data") + + @property + def attachments(self): + return tuple(s for s in self._streams if s.type == "attachment") + + def _get_streams(self, x) -> Iterator[Stream]: + if x is None: + pass + elif isinstance(x, Stream): + yield x + elif isinstance(x, int): + yield self._streams[x] + elif isinstance(x, (tuple, list)): + for item in x: + yield from self._get_streams(item) + elif isinstance(x, dict): + for type_, indices in x.items(): + # For compatibility with the pseudo signature + streams = self._streams if type_ == "streams" else getattr(self, type_) + if not isinstance(indices, (tuple, list)): + indices = [indices] + for i in indices: + yield streams[i] + else: + raise TypeError("Argument must be Stream or int.", type(x)) + + def get(self, *args, **kwargs): + """get(streams=None, video=None, audio=None, subtitles=None, data=None) + + Get a selection of :class:`.Stream` as a ``list``. + + Positional arguments may be ``int`` (which is an index into the streams), + or ``list`` or ``tuple`` of those:: + + # Get the first channel. + streams.get(0) + + # Get the first two audio channels. + streams.get(audio=(0, 1)) + + Keyword arguments (or dicts as positional arguments) as interpreted + as ``(stream_type, index_value_or_set)`` pairs:: + + # Get the first video channel. + streams.get(video=0) + # or + streams.get({'video': 0}) + + :class:`.Stream` objects are passed through untouched. + + If nothing is selected, then all streams are returned. + + """ + selection = list(self._get_streams(args)) + if kwargs: + selection.extend(self._get_streams(kwargs)) + + return selection or self._streams[:] + + def best(self, type: str, /, related: Stream | None = None): + """best(type: Literal["video", "audio", "subtitle", "attachment", "data"], /, related: Stream | None) + Finds the "best" stream in the file. Wraps :ffmpeg:`av_find_best_stream`. Example:: + + stream = container.streams.best("video") + + :param type: The type of stream to find + :param related: A related stream to use as a reference (optional) + :return: The best stream of the specified type + :rtype: Stream | None + """ + if len(self._streams) == 0: + return None + + first_stream: Stream = cython.cast(Stream, self._streams[0]) + container: cython.pointer[lib.AVFormatContext] = first_stream.container.ptr + stream_index: cython.int = _get_best_stream_index( + container, _get_media_type_enum(type), related + ) + + if stream_index < 0: + return None + + return self._streams[stream_index] diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/container/streams.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/container/streams.pyi new file mode 100644 index 0000000000000000000000000000000000000000..9e0df35a4e496b15249134a1b469b05167b38a82 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/container/streams.pyi @@ -0,0 +1,34 @@ +from typing import Iterator, Literal, overload + +from av.audio.stream import AudioStream +from av.stream import AttachmentStream, DataStream, Stream +from av.subtitles.stream import SubtitleStream +from av.video.stream import VideoStream + +class StreamContainer: + video: tuple[VideoStream, ...] + audio: tuple[AudioStream, ...] + subtitles: tuple[SubtitleStream, ...] + attachments: tuple[AttachmentStream, ...] + data: tuple[DataStream, ...] + + def __init__(self) -> None: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[Stream]: ... + @overload + def __getitem__(self, index: int) -> Stream: ... + @overload + def __getitem__(self, index: slice) -> list[Stream]: ... + @overload + def __getitem__(self, index: int | slice) -> Stream | list[Stream]: ... + def get( + self, + *args: int | Stream | dict[str, int | tuple[int, ...]], + **kwargs: int | tuple[int, ...], + ) -> list[Stream]: ... + def best( + self, + type: Literal["video", "audio", "subtitle", "data", "attachment"], + /, + related: Stream | None = None, + ) -> Stream | None: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/datasets.py b/.venvs/kimodo/lib/python3.10/site-packages/av/datasets.py new file mode 100644 index 0000000000000000000000000000000000000000..5954a9c98370d8424c9becce2cd6010b03700688 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/datasets.py @@ -0,0 +1,123 @@ +import errno +import logging +import os +import sys +from typing import Iterator +from urllib.request import urlopen + +log = logging.getLogger(__name__) + + +def iter_data_dirs(check_writable: bool = False) -> Iterator[str]: + try: + yield os.environ["PYAV_TESTDATA_DIR"] + except KeyError: + pass + + if os.name == "nt": + yield os.path.join(sys.prefix, "pyav", "datasets") + return + + bases = [ + "/usr/local/share", + "/usr/local/lib", + "/usr/share", + "/usr/lib", + ] + + # Prefer the local virtualenv. + if hasattr(sys, "real_prefix"): + bases.insert(0, sys.prefix) + + for base in bases: + dir_ = os.path.join(base, "pyav", "datasets") + if check_writable: + if os.path.exists(dir_): + if not os.access(dir_, os.W_OK): + continue + else: + if not os.access(base, os.W_OK): + continue + yield dir_ + + yield os.path.join(os.path.expanduser("~"), ".pyav", "datasets") + + +def cached_download(url: str, name: str) -> str: + """Download the data at a URL, and cache it under the given name. + + The file is stored under `pyav/test` with the given name in the directory + :envvar:`PYAV_TESTDATA_DIR`, or the first that is writeable of: + + - the current virtualenv + - ``/usr/local/share`` + - ``/usr/local/lib`` + - ``/usr/share`` + - ``/usr/lib`` + - the user's home + + """ + + clean_name = os.path.normpath(name) + if clean_name != name: + raise ValueError(f"{name} is not normalized.") + + for dir_ in iter_data_dirs(): + path = os.path.join(dir_, name) + if os.path.exists(path): + return path + + dir_ = next(iter_data_dirs(True)) + path = os.path.join(dir_, name) + + log.info(f"Downloading {url} to {path}") + + response = urlopen(url) + if response.getcode() != 200: + raise ValueError(f"HTTP {response.getcode()}") + + dir_ = os.path.dirname(path) + try: + os.makedirs(dir_) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + tmp_path = path + ".tmp" + with open(tmp_path, "wb") as fh: + while True: + chunk = response.read(8196) + if chunk: + fh.write(chunk) + else: + break + + os.rename(tmp_path, path) + + return path + + +def fate(name: str) -> str: + """Download and return a path to a sample from the FFmpeg test suite. + + Data is handled by :func:`cached_download`. + + See the `FFmpeg Automated Test Environment `_ + + """ + return cached_download( + "http://fate.ffmpeg.org/fate-suite/" + name, + os.path.join("fate-suite", name.replace("/", os.path.sep)), + ) + + +def curated(name: str) -> str: + """Download and return a path to a sample that is curated by the PyAV developers. + + Data is handled by :func:`cached_download`. + + """ + return cached_download( + "https://pyav.org/datasets/" + name, + os.path.join("pyav-curated", name.replace("/", os.path.sep)), + ) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/device.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/device.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..e2853d7b428928223fb2e0d26e5f860c9c9ba3a1 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/device.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae47cfc92f9a3f38a63a882ebf65b1552feda28b2a8703ccc6eed638494ff28d +size 137344 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/device.py b/.venvs/kimodo/lib/python3.10/site-packages/av/device.py new file mode 100644 index 0000000000000000000000000000000000000000..b5d5d110c6d250b272d649fcddd6b96d3a5f0318 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/device.py @@ -0,0 +1,187 @@ +import re + +import cython +import cython.cimports.libav as lib +from cython.cimports.av.error import err_check + + +class DeviceInfo: + """Information about an input or output device. + + :param str name: The device identifier, for use as the first argument to :func:`av.open`. + :param str description: Human-readable description of the device. + :param bool is_default: Whether this is the default device. + :param list media_types: Media types this device provides, e.g. ``["video"]``, ``["audio"]``, + or ``["video", "audio"]``. + + """ + + name: str + description: str + is_default: bool + media_types: list[str] + + def __init__( + self, + name: str, + description: str, + is_default: bool, + media_types: list[str], + ) -> None: + self.name = name + self.description = description + self.is_default = is_default + self.media_types = media_types + + def __repr__(self) -> str: + default = " (default)" if self.is_default else "" + return f"" + + +@cython.cfunc +def _build_device_list(device_list: cython.pointer[lib.AVDeviceInfoList]) -> list: + devices: list = [] + i: cython.int + j: cython.int + device_info: cython.pointer[lib.AVDeviceInfo] + mt: lib.AVMediaType + s: cython.p_const_char + + for i in range(device_list.nb_devices): + device_info = device_list.devices[i] + + media_types: list = [] + for j in range(device_info.nb_media_types): + mt = device_info.media_types[j] + s = lib.av_get_media_type_string(mt) + if s: + media_types.append(s) + + devices.append( + DeviceInfo( + name=cython.cast(bytes, device_info.device_name).decode() + if device_info.device_name + else "", + description=cython.cast(bytes, device_info.device_description).decode() + if device_info.device_description + else "", + is_default=(i == device_list.default_device), + media_types=media_types, + ) + ) + + return devices + + +def _enumerate_via_log_fallback(format_name: str) -> list[DeviceInfo]: + """Fallback for formats (e.g. avfoundation) that log devices instead of + implementing get_device_list. Opens the format with list_devices=1 and + parses the INFO log output.""" + from av import logging as avlogging + + fmt: cython.pointer[cython.const[lib.AVInputFormat]] = lib.av_find_input_format( + format_name + ) + + opts: cython.pointer[lib.AVDictionary] = cython.NULL + lib.av_dict_set(cython.address(opts), b"list_devices", b"1", 0) + + ctx: cython.pointer[lib.AVFormatContext] = cython.NULL + + # Temporarily enable INFO logging so Capture receives device list messages. + old_level = avlogging.get_level() + avlogging.set_level(avlogging.INFO) + devices: list[DeviceInfo] = [] + try: + with avlogging.Capture() as logs: + lib.avformat_open_input(cython.address(ctx), b"", fmt, cython.address(opts)) + if ctx: + lib.avformat_close_input(cython.address(ctx)) + + current_media_type = "video" + for _level, _name, message in logs: + message = message.strip() + if "video devices" in message.lower(): + current_media_type = "video" + elif "audio devices" in message.lower(): + current_media_type = "audio" + else: + m = re.match(r"\[(\d+)\] (.+)", message) + if m: + devices.append( + DeviceInfo( + name=m.group(1), + description=m.group(2), + is_default=False, + media_types=[current_media_type], + ) + ) + finally: + avlogging.set_level(old_level) + lib.av_dict_free(cython.address(opts)) + + return devices + + +def enumerate_input_devices(format_name: str) -> list[DeviceInfo]: + """List the available input devices for a given format. + + :param str format_name: The format name, e.g. ``"avfoundation"``, ``"dshow"``, ``"v4l2"``. + :rtype: list[DeviceInfo] + :raises ValueError: If *format_name* is not a known input format. + :raises av.FFmpegError: If the device does not support enumeration. + + Example:: + + for device in av.enumerate_input_devices("avfoundation"): + print(device.name, device.description) + + """ + fmt: cython.pointer[cython.const[lib.AVInputFormat]] = lib.av_find_input_format( + format_name + ) + if not fmt: + raise ValueError(f"no such input format: {format_name!r}") + + device_list: cython.pointer[lib.AVDeviceInfoList] = cython.NULL + try: + err_check( + lib.avdevice_list_input_sources( + fmt, cython.NULL, cython.NULL, cython.address(device_list) + ) + ) + return _build_device_list(device_list) + except NotImplementedError: + # Format doesn't implement get_device_list (e.g. avfoundation). + # Fall back to opening with list_devices=1 and parsing the log output. + return _enumerate_via_log_fallback(format_name) + finally: + lib.avdevice_free_list_devices(cython.address(device_list)) + + +def enumerate_output_devices(format_name: str) -> list[DeviceInfo]: + """List the available output devices for a given format. + + :param str format_name: The format name, e.g. ``"audiotoolbox"``. + :rtype: list[DeviceInfo] + :raises ValueError: If *format_name* is not a known output format. + :raises av.FFmpegError: If the device does not support enumeration. + + """ + fmt: cython.pointer[cython.const[lib.AVOutputFormat]] = lib.av_guess_format( + format_name, cython.NULL, cython.NULL + ) + if not fmt: + raise ValueError(f"no such output format: {format_name!r}") + + device_list: cython.pointer[lib.AVDeviceInfoList] = cython.NULL + err_check( + lib.avdevice_list_output_sinks( + fmt, cython.NULL, cython.NULL, cython.address(device_list) + ) + ) + + try: + return _build_device_list(device_list) + finally: + lib.avdevice_free_list_devices(cython.address(device_list)) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/device.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/device.pyi new file mode 100644 index 0000000000000000000000000000000000000000..33c55699445b2f6f6bb239125bf584db674e45d3 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/device.pyi @@ -0,0 +1,19 @@ +__all__ = ("DeviceInfo", "enumerate_input_devices", "enumerate_output_devices") + +class DeviceInfo: + name: str + description: str + is_default: bool + media_types: list[str] + + def __init__( + self, + name: str, + description: str, + is_default: bool, + media_types: list[str], + ) -> None: ... + def __repr__(self) -> str: ... + +def enumerate_input_devices(format_name: str) -> list[DeviceInfo]: ... +def enumerate_output_devices(format_name: str) -> list[DeviceInfo]: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/dictionary.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/dictionary.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..553fd8a762cad7e0a542c1e4b52c7b4291a4e894 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/dictionary.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81f861bd79f43d4a02302cd00d0418333ac09db7d40018c9829076c7ed8e5c66 +size 138416 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/dictionary.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/dictionary.pxd new file mode 100644 index 0000000000000000000000000000000000000000..d1eb3c9d19b7750bc32cbbe93a67d9dc6fdab37f --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/dictionary.pxd @@ -0,0 +1,8 @@ +cimport libav as lib + + +cdef class Dictionary: + cdef lib.AVDictionary *ptr + cpdef Dictionary copy(self) + +cdef Dictionary wrap_dictionary(lib.AVDictionary *input_) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/dictionary.py b/.venvs/kimodo/lib/python3.10/site-packages/av/dictionary.py new file mode 100644 index 0000000000000000000000000000000000000000..c9bd76acbaafc7f78b1b372a5b4a29d73cf390e9 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/dictionary.py @@ -0,0 +1,78 @@ +import cython +from cython.cimports.av.error import err_check + + +@cython.final +@cython.cclass +class Dictionary: + def __cinit__(self, *args, **kwargs): + for arg in args: + self.update(arg) + if kwargs: + self.update(kwargs) + + def __dealloc__(self): + if self.ptr != cython.NULL: + lib.av_dict_free(cython.address(self.ptr)) + + def __getitem__(self, key: cython.str): + element: cython.pointer[lib.AVDictionaryEntry] = lib.av_dict_get( + self.ptr, key, cython.NULL, 0 + ) + if element == cython.NULL: + raise KeyError(key) + return element.value + + def __setitem__(self, key: cython.str, value: cython.str): + err_check(lib.av_dict_set(cython.address(self.ptr), key, value, 0)) + + def __delitem__(self, key: cython.str): + err_check(lib.av_dict_set(cython.address(self.ptr), key, cython.NULL, 0)) + + def __len__(self): + return err_check(lib.av_dict_count(self.ptr)) + + def __iter__(self): + element: cython.pointer[lib.AVDictionaryEntry] = cython.NULL + while True: + element = lib.av_dict_get(self.ptr, "", element, lib.AV_DICT_IGNORE_SUFFIX) + if element == cython.NULL: + break + yield element.key + + def __repr__(self): + return f"av.Dictionary({dict(self)!r})" + + def keys(self): + return list(self) + + def copy(self): + other: Dictionary = Dictionary() + lib.av_dict_copy(cython.address(other.ptr), self.ptr, 0) + return other + + def pop(self, key: str): + value = self[key] + del self[key] + return value + + def update(self, other=(), /, **kwds): + if isinstance(other, Dictionary): + lib.av_dict_copy( + cython.address(self.ptr), cython.cast(Dictionary, other).ptr, 0 + ) + elif hasattr(other, "keys"): + for key in other.keys(): + self[key] = other[key] + else: + for key, value in other: + self[key] = value + for key, value in kwds.items(): + self[key] = value + + +@cython.cfunc +def wrap_dictionary(input_: cython.pointer[lib.AVDictionary]) -> Dictionary: + output: Dictionary = Dictionary() + output.ptr = input_ + return output diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/dictionary.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/dictionary.pyi new file mode 100644 index 0000000000000000000000000000000000000000..7994c13067ca93ebeaccbb6f8644fa22ad5614fe --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/dictionary.pyi @@ -0,0 +1,18 @@ +from typing import Iterable, Iterator, Mapping + +class Dictionary: + def __getitem__(self, key: str) -> str: ... + def __setitem__(self, key: str, value: str) -> None: ... + def __delitem__(self, key: str) -> None: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[str]: ... + def __repr__(self) -> str: ... + def keys(self) -> Iterable[str]: ... + def copy(self) -> Dictionary: ... + def pop(self, key: str) -> str: ... + def update( + self, + other: Mapping[str, str] | Iterable[tuple[str, str]] = (), + /, + **kwds: str, + ) -> None: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/error.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/error.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..b1863f50a6e9f9a1ecb92618eb65d9c8fc655182 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/error.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ddfa24762d04fd110f0865c16d38daf16c3b69954083e6c3670b2bc7c40ea45c +size 227344 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/error.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/error.pxd new file mode 100644 index 0000000000000000000000000000000000000000..17c37f609f16bcc5dbee4b1666387c6c4170c6ed --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/error.pxd @@ -0,0 +1,2 @@ +cdef int stash_exception(exc_info=*) +cpdef int err_check(int res, filename=*) except -1 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/error.py b/.venvs/kimodo/lib/python3.10/site-packages/av/error.py new file mode 100644 index 0000000000000000000000000000000000000000..32fb4d171169418bd9382d7077102b99e86ce747 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/error.py @@ -0,0 +1,414 @@ +import errno +import os +import sys +import traceback +from threading import local + +import cython +from cython.cimports import libav as lib +from cython.cimports.av.logging import get_last_error +from cython.cimports.libc.stdio import fprintf, stderr +from cython.cimports.libc.stdlib import free, malloc + +# Will get extended with all the exceptions. +__all__ = [ + "ErrorType", + "FFmpegError", + "LookupError", + "HTTPError", + "HTTPClientError", + "UndefinedError", +] +sentinel = cython.declare(object, object()) + + +@cython.ccall +def code_to_tag(code: cython.int) -> bytes: + """Convert an integer error code into 4-byte tag. + + >>> code_to_tag(1953719668) + b'test' + + """ + return bytes( + ( + code & 0xFF, + (code >> 8) & 0xFF, + (code >> 16) & 0xFF, + (code >> 24) & 0xFF, + ) + ) + + +@cython.ccall +def tag_to_code(tag: bytes) -> cython.int: + """Convert a 4-byte error tag into an integer code. + + >>> tag_to_code(b'test') + 1953719668 + + """ + if len(tag) != 4: + raise ValueError("Error tags are 4 bytes.") + return (tag[0]) + (tag[1] << 8) + (tag[2] << 16) + (tag[3] << 24) + + +class FFmpegError(Exception): + """Exception class for errors from within FFmpeg. + + .. attribute:: errno + + FFmpeg's integer error code. + + .. attribute:: strerror + + FFmpeg's error message. + + .. attribute:: filename + + The filename that was being operated on (if available). + + .. attribute:: log + + The tuple from :func:`av.logging.get_last_log`, or ``None``. + + """ + + def __init__(self, code, message, filename=None, log=None): + args = [code, message] + if filename or log: + args.append(filename) + if log: + args.append(log) + super().__init__(*args) + + self.errno = code + self.strerror = message + self.args = tuple(args) + + @property + def filename(self): + try: + return self.args[2] + except IndexError: + pass + + @property + def log(self): + try: + return self.args[3] + except IndexError: + pass + + def __str__(self): + msg = "" + if self.errno is not None: + msg = f"{msg}[Errno {self.errno}] " + if self.strerror is not None: + msg = f"{msg}{self.strerror}" + if self.filename: + msg = f"{msg}: {self.filename!r}" + if self.log: + msg = ( + f"{msg}; last error log: [{self.log[1].strip()}] {self.log[2].strip()}" + ) + + return msg + + +# Our custom error, used in callbacks. +c_PYAV_STASHED_ERROR = cython.declare(cython.int, tag_to_code(b"PyAV")) +PYAV_STASHED_ERROR_message = cython.declare(str, "Error in PyAV callback") + + +# Bases for the FFmpeg-based exceptions. +class LookupError(FFmpegError, LookupError): + pass + + +class HTTPError(FFmpegError): + pass + + +class HTTPClientError(FFmpegError): + pass + + +# Tuples of (enum_name, enum_value, exc_name, exc_base). +# tuple[str, int, str | None, Exception | none] +# fmt: off +_ffmpeg_specs = ( + ("BSF_NOT_FOUND", -lib.AVERROR_BSF_NOT_FOUND, "BSFNotFoundError", LookupError), + ("BUG", -lib.AVERROR_BUG, None, RuntimeError), + ("BUFFER_TOO_SMALL", -lib.AVERROR_BUFFER_TOO_SMALL, None, ValueError), + ("DECODER_NOT_FOUND", -lib.AVERROR_DECODER_NOT_FOUND, None, LookupError), + ("DEMUXER_NOT_FOUND", -lib.AVERROR_DEMUXER_NOT_FOUND, None, LookupError), + ("ENCODER_NOT_FOUND", -lib.AVERROR_ENCODER_NOT_FOUND, None, LookupError), + ("EOF", -lib.AVERROR_EOF, "EOFError", EOFError), + ("EXIT", -lib.AVERROR_EXIT, None, None), + ("EXTERNAL", -lib.AVERROR_EXTERNAL, None, None), + ("FILTER_NOT_FOUND", -lib.AVERROR_FILTER_NOT_FOUND, None, LookupError), + ("INVALIDDATA", -lib.AVERROR_INVALIDDATA, "InvalidDataError", ValueError), + ("MUXER_NOT_FOUND", -lib.AVERROR_MUXER_NOT_FOUND, None, LookupError), + ("OPTION_NOT_FOUND", -lib.AVERROR_OPTION_NOT_FOUND, None, LookupError), + ("PATCHWELCOME", -lib.AVERROR_PATCHWELCOME, "PatchWelcomeError", None), + ("PROTOCOL_NOT_FOUND", -lib.AVERROR_PROTOCOL_NOT_FOUND, None, LookupError), + ("UNKNOWN", -lib.AVERROR_UNKNOWN, None, None), + ("EXPERIMENTAL", -lib.AVERROR_EXPERIMENTAL, None, None), + ("INPUT_CHANGED", -lib.AVERROR_INPUT_CHANGED, None, None), + ("OUTPUT_CHANGED", -lib.AVERROR_OUTPUT_CHANGED, None, None), + ("HTTP_BAD_REQUEST", -lib.AVERROR_HTTP_BAD_REQUEST, "HTTPBadRequestError", HTTPClientError), + ("HTTP_UNAUTHORIZED", -lib.AVERROR_HTTP_UNAUTHORIZED, "HTTPUnauthorizedError", HTTPClientError), + ("HTTP_FORBIDDEN", -lib.AVERROR_HTTP_FORBIDDEN, "HTTPForbiddenError", HTTPClientError), + ("HTTP_NOT_FOUND", -lib.AVERROR_HTTP_NOT_FOUND, "HTTPNotFoundError", HTTPClientError), + ("HTTP_TOO_MANY_REQUESTS", -lib.AVERROR_HTTP_TOO_MANY_REQUESTS, "HTTPTooManyRequestsError", HTTPClientError), + ("HTTP_OTHER_4XX", -lib.AVERROR_HTTP_OTHER_4XX, "HTTPOtherClientError", HTTPClientError), + ("HTTP_SERVER_ERROR", -lib.AVERROR_HTTP_SERVER_ERROR, "HTTPServerError", HTTPError), + ("PYAV_CALLBACK", c_PYAV_STASHED_ERROR, "PyAVCallbackError", RuntimeError), +) +# fmt: on + + +class EnumType(type): + def __new__(mcl, name, bases, attrs, *args): + # Just adapting the method signature. + return super().__new__(mcl, name, bases, attrs) + + def __init__(self, name, bases, attrs, items): + self._by_name = {} + self._by_value = {} + self._all = [] + + for spec in items: + self._create(*spec) + + def _create(self, name, value, doc=None, by_value_only=False): + # We only have one instance per value. + try: + item = self._by_value[value] + except KeyError: + item = self(sentinel, name, value, doc) + self._by_value[value] = item + + return item + + def __len__(self): + return len(self._all) + + def __iter__(self): + return iter(self._all) + + +@cython.cclass +class EnumItem: + """An enumeration of FFmpeg's error types. + + .. attribute:: tag + + The FFmpeg byte tag for the error. + + .. attribute:: strerror + + The error message that would be returned. + """ + + name = cython.declare(str, visibility="readonly") + value = cython.declare(cython.int, visibility="readonly") + + def __cinit__(self, sentinel_, name: str, value: cython.int, doc=None): + if sentinel_ is not sentinel: + raise RuntimeError(f"Cannot instantiate {self.__class__.__name__}.") + + self.name = name + self.value = value + self.__doc__ = doc + + def __repr__(self): + return f"<{self.__class__.__module__}.{self.__class__.__name__}:{self.name}(0x{self.value:x})>" + + def __str__(self): + return self.name + + def __int__(self): + return self.value + + @property + def tag(self): + return code_to_tag(self.value) + + +ErrorType = EnumType( + "ErrorType", (EnumItem,), {"__module__": __name__}, [x[:2] for x in _ffmpeg_specs] +) + + +for enum in ErrorType: + # Mimick the errno module. + globals()[enum.name] = enum + if enum.value == c_PYAV_STASHED_ERROR: + enum.strerror = PYAV_STASHED_ERROR_message + else: + enum.strerror = lib.av_err2str(-enum.value) + +classes: dict = {} + + +def _extend_builtin(name, codes): + base = getattr(__builtins__, name, OSError) + cls = type(name, (FFmpegError, base), {"__module__": __name__}) + + # Register in builder. + for code in codes: + classes[code] = cls + + # Register in module. + globals()[name] = cls + __all__.append(name) + + return cls + + +_extend_builtin("PermissionError", (errno.EACCES, errno.EPERM)) +_extend_builtin( + "BlockingIOError", + (errno.EAGAIN, errno.EALREADY, errno.EINPROGRESS, errno.EWOULDBLOCK), +) +_extend_builtin("ChildProcessError", (errno.ECHILD,)) +_extend_builtin("ConnectionAbortedError", (errno.ECONNABORTED,)) +_extend_builtin("ConnectionRefusedError", (errno.ECONNREFUSED,)) +_extend_builtin("ConnectionResetError", (errno.ECONNRESET,)) +_extend_builtin("FileExistsError", (errno.EEXIST,)) +_extend_builtin("InterruptedError", (errno.EINTR,)) +_extend_builtin("IsADirectoryError", (errno.EISDIR,)) +_extend_builtin("FileNotFoundError", (errno.ENOENT,)) +_extend_builtin("NotADirectoryError", (errno.ENOTDIR,)) +_extend_builtin("BrokenPipeError", (errno.EPIPE, errno.ESHUTDOWN)) +_extend_builtin("ProcessLookupError", (errno.ESRCH,)) +_extend_builtin("TimeoutError", (errno.ETIMEDOUT,)) +_extend_builtin("MemoryError", (errno.ENOMEM,)) +_extend_builtin("NotImplementedError", (errno.ENOSYS,)) +_extend_builtin("OverflowError", (errno.ERANGE,)) +_extend_builtin("OSError", [code for code in errno.errorcode if code not in classes]) + + +class ArgumentError(FFmpegError, ValueError): + def __str__(self): + msg = "" + if self.strerror is not None: + msg = f"{msg}{self.strerror}" + if self.filename: + msg = f"{msg}: {self.filename!r}" + if self.errno is not None: + msg = f"{msg} returned {self.errno}" + if self.log: + msg = ( + f"{msg}; last error log: [{self.log[1].strip()}] {self.log[2].strip()}" + ) + + return msg + + +class UndefinedError(FFmpegError): + """Fallback exception type in case FFmpeg returns an error we don't know about.""" + + pass + + +classes[errno.EINVAL] = ArgumentError +globals()["ArgumentError"] = ArgumentError +__all__.append("ArgumentError") + + +# Classes for the FFmpeg errors. +for enum_name, code, name, base in _ffmpeg_specs: + name = name or enum_name.title().replace("_", "") + "Error" + + if base is None: + bases = (FFmpegError,) + elif issubclass(base, FFmpegError): + bases = (base,) + else: + bases = (FFmpegError, base) + + cls = type(name, bases, {"__module__": __name__}) + + # Register in builder. + classes[code] = cls + + # Register in module. + globals()[name] = cls + __all__.append(name) + +del _ffmpeg_specs + + +# Storage for stashing. +_local = cython.declare(object, local()) +_err_count = cython.declare(cython.int, 0) +_last_log_count = cython.declare(cython.int, 0) + + +@cython.cfunc +def stash_exception(exc_info=None) -> cython.int: + global _err_count + + existing = getattr(_local, "exc_info", None) + if existing is not None: + fprintf(stderr, "PyAV library exception being dropped:\n") + traceback.print_exception(*existing) + _err_count -= 1 # Balance out the +=1 that is coming. + + exc_info = exc_info or sys.exc_info() + _local.exc_info = exc_info + if exc_info: + _err_count += 1 + + return -c_PYAV_STASHED_ERROR + + +@cython.ccall +@cython.exceptval(-1, check=False) +def err_check(res: cython.int, filename=None) -> cython.int: + """Raise appropriate exceptions from library return code.""" + + global _err_count + global _last_log_count + + # Check for stashed exceptions. + if _err_count: + exc_info = getattr(_local, "exc_info", None) + if exc_info is not None: + _err_count -= 1 + _local.exc_info = None + raise exc_info[1].with_traceback(exc_info[2]) + + if res >= 0: + return res + + # Grab details from the last log. + log_count, last_log = get_last_error() + if log_count > _last_log_count: + _last_log_count = log_count + log = last_log + else: + log = None + + code: cython.int = -res + error_buffer: cython.p_char = cython.cast( + cython.p_char, malloc(lib.AV_ERROR_MAX_STRING_SIZE * cython.sizeof(char)) + ) + if error_buffer == cython.NULL: + raise MemoryError() + + try: + if code == c_PYAV_STASHED_ERROR: + message = PYAV_STASHED_ERROR_message + else: + lib.av_strerror(res, error_buffer, lib.AV_ERROR_MAX_STRING_SIZE) + # Fallback to OS error string if no message + message = error_buffer or os.strerror(code) + + cls = classes.get(code, UndefinedError) + raise cls(code, message, filename, log) + finally: + free(error_buffer) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/error.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/error.pyi new file mode 100644 index 0000000000000000000000000000000000000000..1191d962f6cf8fb4594a39ebf8568c0e03ca8c8e --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/error.pyi @@ -0,0 +1,72 @@ +import builtins + +classes: dict[int, Exception] + +def code_to_tag(code: int) -> bytes: ... +def tag_to_code(tag: bytes) -> int: ... +def err_check(res: int, filename: str | None = None) -> int: ... + +class FFmpegError(Exception): + errno: int | None + strerror: str | None + filename: str + log: tuple[int, tuple[int, str, str] | None] + + def __init__( + self, + code: int, + message: str, + filename: str | None = None, + log: tuple[int, tuple[int, str, str] | None] | None = None, + ) -> None: ... + +class LookupError(FFmpegError): ... +class HTTPError(FFmpegError): ... +class HTTPClientError(FFmpegError): ... +class UndefinedError(FFmpegError): ... +class InvalidDataError(FFmpegError, builtins.ValueError): ... +class BugError(FFmpegError, builtins.RuntimeError): ... +class BufferTooSmallError(FFmpegError, builtins.ValueError): ... +class BSFNotFoundError(LookupError): ... +class DecoderNotFoundError(LookupError): ... +class DemuxerNotFoundError(LookupError): ... +class EncoderNotFoundError(LookupError): ... +class ExitError(FFmpegError): ... +class ExternalError(FFmpegError): ... +class FilterNotFoundError(LookupError): ... +class MuxerNotFoundError(LookupError): ... +class OptionNotFoundError(LookupError): ... +class PatchWelcomeError(FFmpegError): ... +class ProtocolNotFoundError(LookupError): ... +class UnknownError(FFmpegError): ... +class ExperimentalError(FFmpegError): ... +class InputChangedError(FFmpegError): ... +class OutputChangedError(FFmpegError): ... +class HTTPBadRequestError(HTTPClientError): ... +class HTTPUnauthorizedError(HTTPClientError): ... +class HTTPForbiddenError(HTTPClientError): ... +class HTTPNotFoundError(HTTPClientError): ... +class HTTPTooManyRequestsError(HTTPClientError): ... +class HTTPOtherClientError(HTTPClientError): ... +class HTTPServerError(HTTPError): ... +class PyAVCallbackError(FFmpegError, builtins.RuntimeError): ... +class BrokenPipeError(FFmpegError, builtins.BrokenPipeError): ... +class ChildProcessError(FFmpegError, builtins.ChildProcessError): ... +class ConnectionAbortedError(FFmpegError, builtins.ConnectionAbortedError): ... +class ConnectionRefusedError(FFmpegError, builtins.ConnectionRefusedError): ... +class ConnectionResetError(FFmpegError, builtins.ConnectionResetError): ... +class BlockingIOError(FFmpegError, builtins.BlockingIOError): ... +class EOFError(FFmpegError, builtins.EOFError): ... +class FileExistsError(FFmpegError, builtins.FileExistsError): ... +class FileNotFoundError(FFmpegError, builtins.FileNotFoundError): ... +class InterruptedError(FFmpegError, builtins.InterruptedError): ... +class IsADirectoryError(FFmpegError, builtins.IsADirectoryError): ... +class MemoryError(FFmpegError, builtins.MemoryError): ... +class NotADirectoryError(FFmpegError, builtins.NotADirectoryError): ... +class NotImplementedError(FFmpegError, builtins.NotImplementedError): ... +class OverflowError(FFmpegError, builtins.OverflowError): ... +class OSError(FFmpegError, builtins.OSError): ... +class PermissionError(FFmpegError, builtins.PermissionError): ... +class ProcessLookupError(FFmpegError, builtins.ProcessLookupError): ... +class TimeoutError(FFmpegError, builtins.TimeoutError): ... +class ArgumentError(FFmpegError): ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/__init__.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/__init__.pxd new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/__init__.py b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cffc49e7ddfc7bc3924a1cf8145c3b31e0f7031a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/__init__.py @@ -0,0 +1,3 @@ +from .filter import Filter, filters_available +from .graph import Graph as Graph +from .loudnorm import stats as stats diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/__init__.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/__init__.pyi new file mode 100644 index 0000000000000000000000000000000000000000..5be1326c99efab97108a8dba6887cda669b6996a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/__init__.pyi @@ -0,0 +1,4 @@ +from .context import * +from .filter import * +from .graph import * +from .loudnorm import * diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/context.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/context.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..dd025d0df44d07c17f83d1d423fa40645c14f242 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/context.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1320b67ef0e72c8d52a3aa4dea6e8cd2cf19c91a16bdd1647f652ad7fc730705 +size 217984 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/context.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/context.pxd new file mode 100644 index 0000000000000000000000000000000000000000..ae9f27c99f85e2de9ee526095b391464049f9efc --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/context.pxd @@ -0,0 +1,19 @@ +cimport libav as lib + +from av.filter.filter cimport Filter +from av.filter.graph cimport Graph + + +cdef class FilterContext: + + cdef lib.AVFilterContext *ptr + cdef readonly object _graph + cdef readonly Filter filter + + cdef object _inputs + cdef object _outputs + + cdef bint inited + + +cdef FilterContext wrap_filter_context(Graph graph, Filter filter, lib.AVFilterContext *ptr) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/context.py b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/context.py new file mode 100644 index 0000000000000000000000000000000000000000..4873b3ff5ef1b305dafe060942599ec05cb939a8 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/context.py @@ -0,0 +1,191 @@ +import weakref + +import cython +import cython.cimports.libav as lib +from cython.cimports.av.audio.frame import alloc_audio_frame +from cython.cimports.av.dictionary import Dictionary +from cython.cimports.av.error import err_check +from cython.cimports.av.filter.link import alloc_filter_pads +from cython.cimports.av.frame import Frame +from cython.cimports.av.utils import avrational_to_fraction +from cython.cimports.av.video.frame import alloc_video_frame + +_cinit_sentinel = cython.declare(object, object()) + + +@cython.cfunc +def wrap_filter_context( + graph: Graph, filter: Filter, ptr: cython.pointer[lib.AVFilterContext] +) -> FilterContext: + self: FilterContext = FilterContext(_cinit_sentinel) + self._graph = weakref.ref(graph) + self.filter = filter + self.ptr = ptr + return self + + +@cython.final +@cython.cclass +class FilterContext: + def __cinit__(self, sentinel): + if sentinel is not _cinit_sentinel: + raise RuntimeError("cannot construct FilterContext") + + def __repr__(self): + if self.ptr != cython.NULL: + name = repr(self.ptr.name) if self.ptr.name != cython.NULL else "" + else: + name = "None" + + parent = ( + self.filter.ptr.name + if self.filter and self.filter.ptr != cython.NULL + else None + ) + return f"" + + @property + def name(self): + if self.ptr.name != cython.NULL: + return self.ptr.name + + @property + def inputs(self): + if self._inputs is None: + self._inputs = alloc_filter_pads( + self.filter, self.ptr.input_pads, True, self + ) + return self._inputs + + @property + def outputs(self): + if self._outputs is None: + self._outputs = alloc_filter_pads( + self.filter, self.ptr.output_pads, False, self + ) + return self._outputs + + def init(self, args=None, **kwargs): + if self.inited: + raise ValueError("already inited") + if args and kwargs: + raise ValueError("cannot init from args and kwargs") + + dict_: Dictionary = None + c_args: cython.p_char = cython.NULL + if args or not kwargs: + if args: + c_args = args + err_check(lib.avfilter_init_str(self.ptr, c_args)) + else: + dict_ = Dictionary(kwargs) + err_check(lib.avfilter_init_dict(self.ptr, cython.address(dict_.ptr))) + + self.inited = True + if dict_: + raise ValueError(f"unused config: {', '.join(sorted(dict_))}") + + def link_to( + self, + input_: FilterContext, + output_idx: cython.int = 0, + input_idx: cython.int = 0, + ): + err_check(lib.avfilter_link(self.ptr, output_idx, input_.ptr, input_idx)) + + @property + def graph(self): + if graph := self._graph(): + return graph + else: + raise RuntimeError("graph is unallocated") + + def push(self, frame: Frame | None): + res: cython.int + + if frame is None: + with cython.nogil: + res = lib.av_buffersrc_write_frame(self.ptr, cython.NULL) + err_check(res) + return + elif self.filter.name in ("abuffer", "buffer"): + with cython.nogil: + res = lib.av_buffersrc_write_frame(self.ptr, frame.ptr) + err_check(res) + return + + # Delegate to the input. + if len(self.inputs) != 1: + raise ValueError( + f"cannot delegate push without single input; found {len(self.inputs)}" + ) + if not self.inputs[0].link: + raise ValueError("cannot delegate push without linked input") + self.inputs[0].linked.context.push(frame) + + def pull(self): + frame: Frame + res: cython.int + if self.filter.name == "buffersink": + frame = alloc_video_frame() + elif self.filter.name == "abuffersink": + frame = alloc_audio_frame() + else: + # Delegate to the output. + if len(self.outputs) != 1: + raise ValueError( + f"cannot delegate pull without single output; found {len(self.outputs)}" + ) + if not self.outputs[0].link: + raise ValueError("cannot delegate pull without linked output") + return self.outputs[0].linked.context.pull() + + self.graph.configure() + + with cython.nogil: + res = lib.av_buffersink_get_frame(self.ptr, frame.ptr) + err_check(res) + + frame._init_user_attributes() + frame.time_base = avrational_to_fraction( + cython.address(self.ptr.inputs[0].time_base) + ) + return frame + + def process_command( + self, cmd, arg=None, res_len: cython.int = 1024, flags: cython.int = 0 + ): + if not cmd: + raise ValueError("Invalid cmd") + + c_arg: cython.p_char = cython.NULL + c_cmd: cython.p_char = cmd + if arg is not None: + c_arg = arg + + c_res: cython.p_char = cython.NULL + ret: cython.int + res_buf: bytearray = None + view: cython.uchar[:] + b: bytes + nul: cython.int + + if res_len > 0: + res_buf = bytearray(res_len) + view = res_buf + c_res = cython.cast(cython.p_char, cython.address(view[0])) + + with cython.nogil: + ret = lib.avfilter_process_command( + self.ptr, c_cmd, c_arg, c_res, res_len, flags + ) + err_check(ret) + + if res_buf is not None: + b = bytes(res_buf) + nul = b.find(b"\x00") + if nul >= 0: + b = b[:nul] + if b: + return b.decode("utf-8", "strict") + return None diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/context.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/context.pyi new file mode 100644 index 0000000000000000000000000000000000000000..2350019f3aae5f3c7e554e56f3c0a5903528d56c --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/context.pyi @@ -0,0 +1,17 @@ +from av.filter import Graph +from av.frame import Frame + +class FilterContext: + name: str | None + + def init(self, args: str | None = None, **kwargs: str | None) -> None: ... + def link_to( + self, input_: FilterContext, output_idx: int = 0, input_idx: int = 0 + ) -> None: ... + @property + def graph(self) -> Graph: ... + def push(self, frame: Frame | None) -> None: ... + def pull(self) -> Frame: ... + def process_command( + self, cmd: str, arg: str | None = None, res_len: int = 1024, flags: int = 0 + ) -> str | None: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/filter.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/filter.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..56824d7563e6acb4fe1c7b15f6b3cb8a000b46c4 Binary files /dev/null and b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/filter.cpython-310-darwin.so differ diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/filter.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/filter.pxd new file mode 100644 index 0000000000000000000000000000000000000000..d74065a199f4d3df11a364a7537bb60a962a0c6c --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/filter.pxd @@ -0,0 +1,10 @@ +cimport libav as lib + + +cdef class Filter: + cdef const lib.AVFilter *ptr + cdef object _inputs + cdef object _outputs + + +cdef Filter wrap_filter(const lib.AVFilter *ptr) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/filter.py b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/filter.py new file mode 100644 index 0000000000000000000000000000000000000000..acbbc111a35791572f3cf10769335f16c744593a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/filter.py @@ -0,0 +1,67 @@ +import cython +from cython.cimports import libav as lib +from cython.cimports.av.filter.link import alloc_filter_pads + +_cinit_sentinel = cython.declare(object, object()) + + +@cython.cfunc +def wrap_filter(ptr: cython.pointer[cython.const[lib.AVFilter]]) -> Filter: + filter_: Filter = Filter(_cinit_sentinel) + filter_.ptr = ptr + return filter_ + + +@cython.final +@cython.cclass +class Filter: + def __cinit__(self, name): + if name is _cinit_sentinel: + return + if not isinstance(name, str): + raise TypeError("takes a filter name as a string") + + self.ptr = lib.avfilter_get_by_name(name) + if not self.ptr: + raise ValueError(f"no filter {name}") + + @property + def name(self): + return self.ptr.name + + @property + def description(self): + return self.ptr.description + + @property + def flags(self): + return self.ptr.flags + + @property + def inputs(self): + if self._inputs is None: + self._inputs = alloc_filter_pads(self, self.ptr.inputs, True) + return self._inputs + + @property + def outputs(self): + if self._outputs is None: + self._outputs = alloc_filter_pads(self, self.ptr.outputs, False) + return self._outputs + + +@cython.cfunc +def get_filter_names() -> set: + names: set = set() + ptr: cython.pointer[cython.const[lib.AVFilter]] + opaque: cython.p_void = cython.NULL + while True: + ptr = lib.av_filter_iterate(cython.address(opaque)) + if ptr: + names.add(ptr.name) + else: + break + return names + + +filters_available = get_filter_names() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/filter.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/filter.pyi new file mode 100644 index 0000000000000000000000000000000000000000..090d4f1d0ee9e2fd913bcd596bda82f09627cf73 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/filter.pyi @@ -0,0 +1,8 @@ +class Filter: + name: str + description: str + flags: int + + def __init__(self, name: str) -> None: ... + +filters_available: set[str] diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/graph.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/graph.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..8ce3c8049e987179311c81bdeb6d6b9161a24a16 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/graph.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9a4b3cc58ce30de324190af1b505f160e60d130e08fc86d2e9d2d3d660afadb +size 153712 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/graph.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/graph.pxd new file mode 100644 index 0000000000000000000000000000000000000000..e85f67ce4541bf562e34721d87ed67b6678d6d5a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/graph.pxd @@ -0,0 +1,22 @@ +cimport libav as lib + +from av.filter.context cimport FilterContext + + +cdef class Graph: + cdef object __weakref__ + + cdef lib.AVFilterGraph *ptr + + cdef readonly bint configured + cpdef configure(self, bint auto_buffer=*, bint force=*) + + cdef dict _name_counts + cdef str _get_unique_name(self, str name) + cdef list[FilterContext] _get_context_by_type(self, str type) + + cdef _register_context(self, FilterContext) + cdef _auto_register(self) + cdef int _nb_filters_seen + cdef dict[long, FilterContext] _context_by_ptr + cdef dict[str, list[FilterContext]] _context_by_type diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/graph.py b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/graph.py new file mode 100644 index 0000000000000000000000000000000000000000..c1b61eb20635c5d5546310cf6bf014c4ccc1849c --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/graph.py @@ -0,0 +1,275 @@ +import warnings +from fractions import Fraction + +import cython +from cython.cimports.av.audio.format import AudioFormat +from cython.cimports.av.audio.frame import AudioFrame +from cython.cimports.av.audio.layout import AudioLayout +from cython.cimports.av.error import err_check +from cython.cimports.av.filter.context import FilterContext, wrap_filter_context +from cython.cimports.av.filter.filter import Filter, wrap_filter +from cython.cimports.av.video.format import VideoFormat +from cython.cimports.av.video.frame import VideoFrame + + +@cython.final +@cython.cclass +class Graph: + def __cinit__(self): + self.ptr = lib.avfilter_graph_alloc() + self.configured = False + self._name_counts = {} + self._nb_filters_seen = 0 + self._context_by_ptr = {} + self._context_by_type = {} + + def __dealloc__(self): + if self.ptr: + # This frees the graph, filter contexts, links, etc.. + lib.avfilter_graph_free(cython.address(self.ptr)) + + @property + def threads(self): + """Maximum number of threads used by filters in this graph. + + Set to 0 for automatic thread count. Must be set before adding any + filters to the graph. + + Wraps :ffmpeg:`AVFilterGraph.nb_threads`. + """ + return self.ptr.nb_threads + + @threads.setter + def threads(self, value: cython.int): + if self.ptr.nb_filters: + raise RuntimeError("Cannot change threads after filters have been added.") + self.ptr.nb_threads = value + + @cython.cfunc + def _get_unique_name(self, name: str) -> str: + count = self._name_counts.get(name, 0) + self._name_counts[name] = count + 1 + if count: + return "%s_%s" % (name, count) + else: + return name + + @cython.cfunc + @cython.inline + def _get_context_by_type(self, type_: str) -> list[FilterContext]: + return self._context_by_type.get(type_, []) + + @cython.ccall + def configure(self, auto_buffer: cython.bint = True, force: cython.bint = False): + if self.configured and not force: + return + + err_check(lib.avfilter_graph_config(self.ptr, cython.NULL)) + self.configured = True + + # We get auto-inserted stuff here. + self._auto_register() + + def link_nodes(self, *nodes): + """ + Links nodes together for simple filter graphs. + """ + for c, n in zip(nodes, nodes[1:]): + c.link_to(n) + return self + + def add(self, filter, args=None, **kwargs): + cy_filter: Filter + if isinstance(filter, str): + cy_filter = Filter(filter) + elif isinstance(filter, Filter): + cy_filter = filter + else: + raise TypeError("filter must be a string or Filter") + + name: str = self._get_unique_name(kwargs.pop("name", None) or cy_filter.name) + ptr: cython.pointer[lib.AVFilterContext] = lib.avfilter_graph_alloc_filter( + self.ptr, cy_filter.ptr, name + ) + if not ptr: + raise RuntimeError("Could not allocate AVFilterContext") + + # Manually construct this context (so we can return it). + ctx: FilterContext = wrap_filter_context(self, cy_filter, ptr) + ctx.init(args, **kwargs) + self._register_context(ctx) + + # There might have been automatic contexts added (e.g. resamplers, + # fifos, and scalers). It is more likely to see them after the graph + # is configured, but we want to be safe. + self._auto_register() + + return ctx + + @cython.cfunc + def _register_context(self, ctx: FilterContext): + self._context_by_ptr[cython.cast(cython.long, ctx.ptr)] = ctx + self._context_by_type.setdefault(ctx.filter.ptr.name, []).append(ctx) + + @cython.cfunc + def _auto_register(self): + i: cython.int + c_ctx: cython.pointer[lib.AVFilterContext] + filter_: Filter + py_ctx: FilterContext + # We assume that filters are never removed from the graph. At this + # point we don't expose that in the API, so we should be okay... + for i in range(self._nb_filters_seen, self.ptr.nb_filters): + c_ctx = self.ptr.filters[i] + if cython.cast(cython.long, c_ctx) in self._context_by_ptr: + continue + filter_ = wrap_filter(c_ctx.filter) + py_ctx = wrap_filter_context(self, filter_, c_ctx) + self._register_context(py_ctx) + self._nb_filters_seen = self.ptr.nb_filters + + def add_buffer( + self, + template=None, + width=None, + height=None, + format=None, + name=None, + time_base=None, + ): + if template is not None: + if width is None: + width = template.width + if height is None: + height = template.height + if format is None: + format = template.format + if time_base is None: + time_base = template.time_base + + if width is None: + raise ValueError("missing width") + if height is None: + raise ValueError("missing height") + if format is None: + raise ValueError("missing format") + if time_base is None: + warnings.warn( + "missing time_base. Guessing 1/1000 time base. " + "This is deprecated and may be removed in future releases.", + DeprecationWarning, + ) + time_base = Fraction(1, 1000) + + return self.add( + "buffer", + name=name, + video_size=f"{width}x{height}", + pix_fmt=str(int(VideoFormat(format))), + time_base=str(time_base), + pixel_aspect="1/1", + ) + + def add_abuffer( + self, + template=None, + sample_rate=None, + format=None, + layout=None, + channels=None, + name=None, + time_base=None, + ): + """ + Convenience method for adding `abuffer `_. + """ + + if template is not None: + if sample_rate is None: + sample_rate = template.sample_rate + if format is None: + format = template.format + if layout is None: + layout = template.layout.name + if channels is None: + channels = template.channels + if time_base is None: + time_base = template.time_base + + if sample_rate is None: + raise ValueError("missing sample_rate") + if format is None: + raise ValueError("missing format") + if layout is None and channels is None: + raise ValueError("missing layout or channels") + if time_base is None: + time_base = Fraction(1, sample_rate) + + kwargs = { + "sample_rate": f"{sample_rate}", + "sample_fmt": AudioFormat(format).name, + "time_base": f"{time_base}", + } + if layout: + kwargs["channel_layout"] = AudioLayout(layout).name + if channels: + kwargs["channels"] = f"{channels}" + + return self.add("abuffer", name=name, **kwargs) + + def set_audio_frame_size(self, frame_size): + """ + Set the audio frame size for the graphs `abuffersink`. + See `av_buffersink_set_frame_size `_. + """ + if not self.configured: + raise ValueError("graph not configured") + sinks = self._get_context_by_type("abuffersink") + if not sinks: + raise ValueError("missing abuffersink filter") + for sink in sinks: + lib.av_buffersink_set_frame_size( + cython.cast(FilterContext, sink).ptr, frame_size + ) + + def push(self, frame): + if frame is None: + contexts = self._get_context_by_type("buffer") + self._get_context_by_type( + "abuffer" + ) + elif isinstance(frame, VideoFrame): + contexts = self._get_context_by_type("buffer") + elif isinstance(frame, AudioFrame): + contexts = self._get_context_by_type("abuffer") + else: + raise ValueError( + f"can only AudioFrame, VideoFrame or None; got {type(frame)}" + ) + + for ctx in contexts: + ctx.push(frame) + + def vpush(self, frame: VideoFrame | None): + """Like `push`, but only for VideoFrames.""" + for ctx in self._get_context_by_type("buffer"): + ctx.push(frame) + + # TODO: Test complex filter graphs, add `at: int = 0` arg to pull() and vpull(). + def pull(self): + vsinks = self._get_context_by_type("buffersink") + asinks = self._get_context_by_type("abuffersink") + + nsinks = len(vsinks) + len(asinks) + if nsinks != 1: + raise ValueError(f"can only auto-pull with single sink; found {nsinks}") + + return (vsinks or asinks)[0].pull() + + def vpull(self): + """Like `pull`, but only for VideoFrames.""" + vsinks = self._get_context_by_type("buffersink") + nsinks = len(vsinks) + if nsinks != 1: + raise ValueError(f"can only auto-pull with single sink; found {nsinks}") + + return vsinks[0].pull() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/graph.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/graph.pyi new file mode 100644 index 0000000000000000000000000000000000000000..758813ea276f4198dd70a09538754dd0ffa2b650 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/graph.pyi @@ -0,0 +1,48 @@ +from fractions import Fraction +from typing import Any + +from av.audio.format import AudioFormat +from av.audio.frame import AudioFrame +from av.audio.layout import AudioLayout +from av.audio.stream import AudioStream +from av.video.format import VideoFormat +from av.video.frame import VideoFrame +from av.video.stream import VideoStream + +from .context import FilterContext +from .filter import Filter + +class Graph: + configured: bool + threads: int + + def __init__(self) -> None: ... + def configure(self, auto_buffer: bool = True, force: bool = False) -> None: ... + def link_nodes(self, *nodes: FilterContext) -> Graph: ... + def add( + self, filter: str | Filter, args: Any = None, **kwargs: str + ) -> FilterContext: ... + def add_buffer( + self, + template: VideoStream | None = None, + width: int | None = None, + height: int | None = None, + format: VideoFormat | None = None, + name: str | None = None, + time_base: Fraction | None = None, + ) -> FilterContext: ... + def add_abuffer( + self, + template: AudioStream | None = None, + sample_rate: int | None = None, + format: AudioFormat | str | None = None, + layout: AudioLayout | str | None = None, + channels: int | None = None, + name: str | None = None, + time_base: Fraction | None = None, + ) -> FilterContext: ... + def set_audio_frame_size(self, frame_size: int) -> None: ... + def push(self, frame: None | AudioFrame | VideoFrame) -> None: ... + def pull(self) -> VideoFrame | AudioFrame: ... + def vpush(self, frame: VideoFrame | None) -> None: ... + def vpull(self) -> VideoFrame: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/link.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/link.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..69aaa72461a105acf96a99b63afea87c0bf3f28e --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/link.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5121554319a4970b58c23f994ccccc751fa99f217496cd0ed20f26c80e0e51c +size 117872 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/link.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/link.pxd new file mode 100644 index 0000000000000000000000000000000000000000..7f6aad2aa2a3b5c1a5edcfb323075733d521c846 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/link.pxd @@ -0,0 +1,32 @@ +cimport libav as lib + +from av.filter.context cimport FilterContext +from av.filter.filter cimport Filter +from av.filter.graph cimport Graph +from av.filter.link cimport FilterContextPad, FilterLink + + +cdef class FilterLink: + cdef object _graph + cdef lib.AVFilterLink *ptr + + cdef FilterContextPad _input + cdef FilterContextPad _output + + +cdef FilterLink wrap_filter_link(Graph graph, lib.AVFilterLink *ptr) + +cdef class FilterPad: + cdef readonly Filter filter + cdef readonly FilterContext context + cdef readonly bint is_input + cdef readonly int index + + cdef const lib.AVFilterPad *base_ptr + + +cdef class FilterContextPad(FilterPad): + cdef FilterLink _link + + +cdef tuple alloc_filter_pads(Filter, const lib.AVFilterPad *ptr, bint is_input, FilterContext context=?) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/link.py b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/link.py new file mode 100644 index 0000000000000000000000000000000000000000..0e5b680ce87f1b2e6a2672f8dc6e53af7b7b0470 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/link.py @@ -0,0 +1,159 @@ +import weakref + +import cython +import cython.cimports.libav as lib +from cython.cimports.av.filter.graph import Graph + +_cinit_sentinel = cython.declare(object, object()) + + +@cython.final +@cython.cclass +class FilterLink: + def __cinit__(self, sentinel): + if sentinel is not _cinit_sentinel: + raise RuntimeError("cannot instantiate FilterLink") + + @property + def graph(self) -> Graph: + if g := self._graph(): + return g + else: + raise RuntimeError("graph is unallocated") + + @property + def input(self): + if self._input: + return self._input + cctx: cython.pointer[lib.AVFilterContext] = self.ptr.src + i: cython.Py_ssize_t + for i in range(cctx.nb_outputs): + if self.ptr == cctx.outputs[i]: + break + else: # nobreak + raise RuntimeError("could not find link in context") + graph: Graph = self.graph + ctx = graph._context_by_ptr[cython.cast(cython.long, cctx)] + self._input = ctx.outputs[i] + return self._input + + @property + def output(self): + if self._output: + return self._output + cctx: cython.pointer[lib.AVFilterContext] = self.ptr.dst + i: cython.Py_ssize_t + for i in range(cctx.nb_inputs): + if self.ptr == cctx.inputs[i]: + break + else: + raise RuntimeError("could not find link in context") + try: + graph: Graph = self.graph + ctx = graph._context_by_ptr[cython.cast(cython.long, cctx)] + except KeyError: + raise RuntimeError( + "could not find context in graph", (cctx.name, cctx.filter.name) + ) + self._output = ctx.inputs[i] + return self._output + + +@cython.cfunc +def wrap_filter_link(graph: Graph, ptr: cython.pointer[lib.AVFilterLink]) -> FilterLink: + link: FilterLink = FilterLink(_cinit_sentinel) + link._graph = weakref.ref(graph) + link.ptr = ptr + return link + + +@cython.cclass +class FilterPad: + def __cinit__(self, sentinel): + if sentinel is not _cinit_sentinel: + raise RuntimeError("cannot construct FilterPad") + + def __repr__(self): + _filter = self.filter.name + _io = "inputs" if self.is_input else "outputs" + + return ( + f"" + ) + + @property + def is_output(self): + return not self.is_input + + @property + def name(self): + return lib.avfilter_pad_get_name(self.base_ptr, self.index) + + +@cython.final +@cython.cclass +class FilterContextPad(FilterPad): + def __repr__(self): + _filter = self.filter.name + _io = "inputs" if self.is_input else "outputs" + context = self.context.name + + return f"" + + @property + def link(self): + if self._link: + return self._link + links: cython.pointer[cython.pointer[lib.AVFilterLink]] = ( + self.context.ptr.inputs if self.is_input else self.context.ptr.outputs + ) + link: cython.pointer[lib.AVFilterLink] = links[self.index] + if not link: + return + self._link = wrap_filter_link(self.context.graph, link) + return self._link + + @property + def linked(self): + link: FilterLink = self.link + if link: + return link.input if self.is_input else link.output + + +@cython.cfunc +def alloc_filter_pads( + filter: Filter, + ptr: cython.pointer[cython.const[lib.AVFilterPad]], + is_input: cython.bint, + context: FilterContext | None = None, +) -> tuple: + if not ptr: + return () + + pads: list = [] + + # We need to be careful and check our bounds if we know what they are, + # since the arrays on a AVFilterContext are not NULL terminated. + i: cython.int = 0 + count: cython.int + if context is None: + count = lib.avfilter_filter_pad_count(filter.ptr, not is_input) + else: + count = context.ptr.nb_inputs if is_input else context.ptr.nb_outputs + + pad: FilterPad + while i < count: + pad = ( + FilterPad(_cinit_sentinel) + if context is None + else FilterContextPad(_cinit_sentinel) + ) + pads.append(pad) + pad.filter = filter + pad.context = context + pad.is_input = is_input + pad.base_ptr = ptr + pad.index = i + i += 1 + + return tuple(pads) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/link.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/link.pyi new file mode 100644 index 0000000000000000000000000000000000000000..9d199a2722ae62461a7b6249424a7853ba6c17c3 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/link.pyi @@ -0,0 +1,2 @@ +class FilterLink: + pass diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..581c466a638352b583b0dc0236898e626a475970 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4dd9cab1312d1d150890524a1bbf7c075cb459e5fd060e57646555332a7092b +size 113856 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm.pxd new file mode 100644 index 0000000000000000000000000000000000000000..2729dd8be0e2d4d53d64c5348f4cf909f161be2c --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm.pxd @@ -0,0 +1,19 @@ +from av.audio.stream cimport AudioStream + + +cdef extern from "libavcodec/avcodec.h": + ctypedef struct AVCodecContext: + pass + +cdef extern from "libavformat/avformat.h": + ctypedef struct AVFormatContext: + pass + +cdef extern from "loudnorm_impl.h": + char* loudnorm_get_stats( + AVFormatContext* fmt_ctx, + int audio_stream_index, + const char* loudnorm_args + ) nogil + +cpdef bytes stats(str loudnorm_args, AudioStream stream) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm.py b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm.py new file mode 100644 index 0000000000000000000000000000000000000000..0be991ab6b17c5ef7050abcb69f55387f003a48d --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm.py @@ -0,0 +1,48 @@ +import cython +from cython.cimports.av.audio.stream import AudioStream +from cython.cimports.av.container.core import Container +from cython.cimports.libc.stdlib import free + +from av.logging import get_level, set_level + + +@cython.ccall +def stats(loudnorm_args: str, stream: AudioStream) -> bytes: + """ + Get loudnorm statistics for an audio stream. + + Args: + loudnorm_args (str): Arguments for the loudnorm filter (e.g. "i=-24.0:lra=7.0:tp=-2.0") + stream (AudioStream): Input audio stream to analyze + + Returns: + bytes: JSON string containing the loudnorm statistics + """ + + if "print_format=json" not in loudnorm_args: + loudnorm_args = loudnorm_args + ":print_format=json" + + container: Container = stream.container + format_ptr: cython.pointer[AVFormatContext] = container.ptr + container.ptr = cython.NULL # Prevent double-free + + stream_index: cython.int = stream.index + py_args: bytes = loudnorm_args.encode("utf-8") + c_args: cython.p_const_char = py_args + result: cython.p_char + + # Save log level since C function overwrite it. + level = get_level() + + with cython.nogil: + result = loudnorm_get_stats(format_ptr, stream_index, c_args) + + if result == cython.NULL: + raise RuntimeError("Failed to get loudnorm stats") + + py_result = result[:] # Make a copy of the string + free(result) # Free the C string + + set_level(level) + + return py_result diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm.pyi new file mode 100644 index 0000000000000000000000000000000000000000..c680f638d43a8908438d81c56c07387524488412 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm.pyi @@ -0,0 +1,3 @@ +from av.audio.stream import AudioStream + +def stats(loudnorm_args: str, stream: AudioStream) -> bytes: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm_impl.c b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm_impl.c new file mode 100644 index 0000000000000000000000000000000000000000..c4e18298152786e2c185a984b351c46d01772bb1 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm_impl.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + #include +#else + #include +#endif + +#ifdef _WIN32 + static CRITICAL_SECTION json_mutex; + static CONDITION_VARIABLE json_cond; + static int mutex_initialized = 0; +#else + static pthread_mutex_t json_mutex = PTHREAD_MUTEX_INITIALIZER; + static pthread_cond_t json_cond = PTHREAD_COND_INITIALIZER; +#endif + +static char json_buffer[2048] = {0}; +static int json_captured = 0; + +// Custom logging callback +static void logging_callback(void *ptr, int level, const char *fmt, va_list vl) { + char line[2048]; + vsnprintf(line, sizeof(line), fmt, vl); + + const char *json_start = strstr(line, "{"); + if (json_start) { + #ifdef _WIN32 + EnterCriticalSection(&json_mutex); + #else + pthread_mutex_lock(&json_mutex); + #endif + + size_t len = strnlen(json_start, sizeof(json_buffer) - 1); + memcpy(json_buffer, json_start, len); + json_buffer[len] = '\0'; + json_captured = 1; + + #ifdef _WIN32 + WakeConditionVariable(&json_cond); + LeaveCriticalSection(&json_mutex); + #else + pthread_cond_signal(&json_cond); + pthread_mutex_unlock(&json_mutex); + #endif + } +} + +char* loudnorm_get_stats( + AVFormatContext* fmt_ctx, + int audio_stream_index, + const char* loudnorm_args +) { + char* result = NULL; + json_captured = 0; // Reset the captured flag + memset(json_buffer, 0, sizeof(json_buffer)); // Clear the buffer + + #ifdef _WIN32 + // Initialize synchronization objects if needed + if (!mutex_initialized) { + InitializeCriticalSection(&json_mutex); + InitializeConditionVariable(&json_cond); + mutex_initialized = 1; + } + #endif + + av_log_set_callback(logging_callback); + + AVFilterGraph *filter_graph = NULL; + AVFilterContext *src_ctx = NULL, *sink_ctx = NULL, *loudnorm_ctx = NULL; + + AVCodec *codec = NULL; + AVCodecContext *codec_ctx = NULL; + int ret; + + AVCodecParameters *codecpar = fmt_ctx->streams[audio_stream_index]->codecpar; + codec = (AVCodec *)avcodec_find_decoder(codecpar->codec_id); + codec_ctx = avcodec_alloc_context3(codec); + avcodec_parameters_to_context(codec_ctx, codecpar); + avcodec_open2(codec_ctx, codec, NULL); + + char ch_layout_str[64]; + av_channel_layout_describe(&codecpar->ch_layout, ch_layout_str, sizeof(ch_layout_str)); + + filter_graph = avfilter_graph_alloc(); + + char args[512]; + snprintf(args, sizeof(args), + "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=%s", + fmt_ctx->streams[audio_stream_index]->time_base.num, + fmt_ctx->streams[audio_stream_index]->time_base.den, + codecpar->sample_rate, + av_get_sample_fmt_name(codec_ctx->sample_fmt), + ch_layout_str); + + avfilter_graph_create_filter(&src_ctx, avfilter_get_by_name("abuffer"), + "src", args, NULL, filter_graph); + avfilter_graph_create_filter(&sink_ctx, avfilter_get_by_name("abuffersink"), + "sink", NULL, NULL, filter_graph); + avfilter_graph_create_filter(&loudnorm_ctx, avfilter_get_by_name("loudnorm"), + "loudnorm", loudnorm_args, NULL, filter_graph); + + avfilter_link(src_ctx, 0, loudnorm_ctx, 0); + avfilter_link(loudnorm_ctx, 0, sink_ctx, 0); + avfilter_graph_config(filter_graph, NULL); + + AVPacket *packet = av_packet_alloc(); + AVFrame *frame = av_frame_alloc(); + AVFrame *filt_frame = av_frame_alloc(); + + while ((ret = av_read_frame(fmt_ctx, packet)) >= 0) { + if (packet->stream_index != audio_stream_index) { + av_packet_unref(packet); + continue; + } + + ret = avcodec_send_packet(codec_ctx, packet); + if (ret < 0) { + av_packet_unref(packet); + continue; + } + + while (ret >= 0) { + ret = avcodec_receive_frame(codec_ctx, frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; + if (ret < 0) goto end; + + ret = av_buffersrc_add_frame_flags(src_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF); + if (ret < 0) goto end; + + while (1) { + ret = av_buffersink_get_frame(sink_ctx, filt_frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; + if (ret < 0) goto end; + av_frame_unref(filt_frame); + } + } + av_packet_unref(packet); + } + + // Flush decoder + avcodec_send_packet(codec_ctx, NULL); + while (avcodec_receive_frame(codec_ctx, frame) >= 0) { + ret = av_buffersrc_add_frame(src_ctx, frame); + if (ret < 0) goto end; + } + + // Flush filter + ret = av_buffersrc_add_frame(src_ctx, NULL); + if (ret < 0) goto end; + while (av_buffersink_get_frame(sink_ctx, filt_frame) >= 0) { + av_frame_unref(filt_frame); + } + + // Pushes graph + avfilter_graph_free(&filter_graph); + +end: + avcodec_free_context(&codec_ctx); + avformat_close_input(&fmt_ctx); + av_frame_free(&filt_frame); + av_frame_free(&frame); + av_packet_free(&packet); + + #ifdef _WIN32 + EnterCriticalSection(&json_mutex); + while (!json_captured) { + if (!SleepConditionVariableCS(&json_cond, &json_mutex, 5000)) { // 5 second timeout + fprintf(stderr, "Timeout waiting for JSON data\n"); + break; + } + } + if (json_captured) { + result = _strdup(json_buffer); // Use _strdup on Windows + } + LeaveCriticalSection(&json_mutex); + #else + struct timespec timeout; + clock_gettime(CLOCK_REALTIME, &timeout); + timeout.tv_sec += 5; // 5 second timeout + + pthread_mutex_lock(&json_mutex); + while (json_captured == 0) { + int ret = pthread_cond_timedwait(&json_cond, &json_mutex, &timeout); + if (ret == ETIMEDOUT) { + fprintf(stderr, "Timeout waiting for JSON data\n"); + break; + } + } + if (json_captured) { + result = strdup(json_buffer); + } + pthread_mutex_unlock(&json_mutex); + #endif + + av_log_set_callback(av_log_default_callback); + return result; +} diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm_impl.h b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm_impl.h new file mode 100644 index 0000000000000000000000000000000000000000..7357e46689d5cbd5795ce00c5864f40e197e09dd --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/filter/loudnorm_impl.h @@ -0,0 +1,12 @@ +#ifndef AV_FILTER_LOUDNORM_H +#define AV_FILTER_LOUDNORM_H + +#include + +char* loudnorm_get_stats( + AVFormatContext* fmt_ctx, + int audio_stream_index, + const char* loudnorm_args +); + +#endif // AV_FILTER_LOUDNORM_H \ No newline at end of file diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/format.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/format.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..8915e55625f55b9712500cb3423aaff5b5c8b71a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/format.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60233ef3892b57459ff42d0668db23eafd5fd0e9471ced1cc3f9b952c1e07a47 +size 115568 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/format.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/format.pxd new file mode 100644 index 0000000000000000000000000000000000000000..4109f325c19d9e4f6bbbd9f804e16e991a2a161e --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/format.pxd @@ -0,0 +1,12 @@ +cimport libav as lib + + +cdef class ContainerFormat: + + cdef readonly str name + + cdef const lib.AVInputFormat *iptr + cdef const lib.AVOutputFormat *optr + + +cdef ContainerFormat build_container_format(const lib.AVInputFormat*, const lib.AVOutputFormat*) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/format.py b/.venvs/kimodo/lib/python3.10/site-packages/av/format.py new file mode 100644 index 0000000000000000000000000000000000000000..fec69f7a0730901329c63897f16d9749ed904611 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/format.py @@ -0,0 +1,168 @@ +from enum import Flag + +import cython +import cython.cimports.libav as lib + +_cinit_bypass_sentinel = cython.declare(object, object()) + + +@cython.cfunc +def build_container_format( + iptr: cython.pointer[cython.const[lib.AVInputFormat]], + optr: cython.pointer[cython.const[lib.AVOutputFormat]], +) -> ContainerFormat: + if not iptr and not optr: + raise ValueError("needs input format or output format") + format: ContainerFormat = ContainerFormat.__new__( + ContainerFormat, _cinit_bypass_sentinel + ) + format.iptr = iptr + format.optr = optr + format.name = optr.name if optr else iptr.name + return format + + +# fmt: off +class Flags(Flag): + no_file = lib.AVFMT_NOFILE + need_number: "Needs '%d' in filename." = lib.AVFMT_NEEDNUMBER + show_ids: "Show format stream IDs numbers." = lib.AVFMT_SHOW_IDS + global_header: "Format wants global header." = lib.AVFMT_GLOBALHEADER + no_timestamps: "Format does not need / have any timestamps." = lib.AVFMT_NOTIMESTAMPS + generic_index: "Use generic index building code." = lib.AVFMT_GENERIC_INDEX + ts_discont: "Format allows timestamp discontinuities" = lib.AVFMT_TS_DISCONT + variable_fps: "Format allows variable fps." = lib.AVFMT_VARIABLE_FPS + no_dimensions: "Format does not need width/height" = lib.AVFMT_NODIMENSIONS + no_streams: "Format does not require any streams" = lib.AVFMT_NOSTREAMS + no_bin_search: "Format does not allow to fall back on binary search via read_timestamp" = lib.AVFMT_NOBINSEARCH + no_gen_search: "Format does not allow to fall back on generic search" = lib.AVFMT_NOGENSEARCH + no_byte_seek: "Format does not allow seeking by bytes" = lib.AVFMT_NO_BYTE_SEEK + ts_nonstrict: "Format does not require strictly increasing timestamps, but they must still be monotonic." = lib.AVFMT_TS_NONSTRICT + ts_negative: "Format allows muxing negative timestamps." = lib.AVFMT_TS_NEGATIVE + # If not set the timestamp will be shifted in `av_write_frame()` and `av_interleaved_write_frame()` + # so they start from 0. The user or muxer can override this through AVFormatContext.avoid_negative_ts + seek_to_pts: "Seeking is based on PTS" = lib.AVFMT_SEEK_TO_PTS +# fmt: on + + +@cython.final +@cython.cclass +class ContainerFormat: + """Descriptor of a container format. + + :param str name: The name of the format. + :param str mode: ``'r'`` or ``'w'`` for input and output formats; defaults + to None which will grab either. + + """ + + def __cinit__(self, name, mode=None): + if name is _cinit_bypass_sentinel: + return + + # We need to hold onto the original name because AVInputFormat.name is + # actually comma-separated, and so we need to remember which one this was. + self.name = name + + # Searches comma-separated names. + if mode is None or mode == "r": + self.iptr = lib.av_find_input_format(name) + + if mode is None or mode == "w": + self.optr = lib.av_guess_format(name, cython.NULL, cython.NULL) + + if not self.iptr and not self.optr: + raise ValueError(f"no container format {name!r}") + + def __repr__(self): + return f"" + + @property + def input(self): + """An input-only view of this format.""" + if self.iptr == cython.NULL: + return None + elif self.optr == cython.NULL: + return self + else: + return build_container_format(self.iptr, cython.NULL) + + @property + def output(self): + """An output-only view of this format.""" + if self.optr == cython.NULL: + return None + elif self.iptr == cython.NULL: + return self + else: + return build_container_format(cython.NULL, self.optr) + + @property + def is_input(self): + return self.iptr != cython.NULL + + @property + def is_output(self): + return self.optr != cython.NULL + + @property + def long_name(self): + # We prefer the output names since the inputs may represent + # multiple formats. + return self.optr.long_name if self.optr else self.iptr.long_name + + @property + def extensions(self): + exts: set = set() + if self.iptr and self.iptr.extensions: + exts.update(self.iptr.extensions.split(",")) + if self.optr and self.optr.extensions: + exts.update(self.optr.extensions.split(",")) + return exts + + @property + def flags(self): + """ + Get the flags bitmask for the format. + + :rtype: int + """ + return (self.iptr.flags if self.iptr else 0) | ( + self.optr.flags if self.optr else 0 + ) + + @property + def no_file(self): + return bool(self.flags & lib.AVFMT_NOFILE) + + +@cython.cfunc +def get_output_format_names() -> set: + names: set = set() + ptr: cython.pointer[cython.const[lib.AVOutputFormat]] + opaque: cython.p_void = cython.NULL + while True: + ptr = lib.av_muxer_iterate(cython.address(opaque)) + if ptr: + names.add(ptr.name) + else: + break + return names + + +@cython.cfunc +def get_input_format_names() -> set: + names: set = set() + ptr: cython.pointer[cython.const[lib.AVInputFormat]] + opaque: cython.p_void = cython.NULL + while True: + ptr = lib.av_demuxer_iterate(cython.address(opaque)) + if ptr: + names.add(ptr.name) + else: + break + return names + + +formats_available = get_output_format_names() +formats_available.update(get_input_format_names()) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/format.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/format.pyi new file mode 100644 index 0000000000000000000000000000000000000000..81c5c42ca3e582a88d9dba128d08dbd0f2b4b92b --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/format.pyi @@ -0,0 +1,42 @@ +__all__ = ("Flags", "ContainerFormat", "formats_available") + +from enum import Flag +from typing import ClassVar, Literal, cast + +class Flags(Flag): + no_file = cast(ClassVar[Flags], ...) + need_number = cast(ClassVar[Flags], ...) + show_ids = cast(ClassVar[Flags], ...) + global_header = cast(ClassVar[Flags], ...) + no_timestamps = cast(ClassVar[Flags], ...) + generic_index = cast(ClassVar[Flags], ...) + ts_discont = cast(ClassVar[Flags], ...) + variable_fps = cast(ClassVar[Flags], ...) + no_dimensions = cast(ClassVar[Flags], ...) + no_streams = cast(ClassVar[Flags], ...) + no_bin_search = cast(ClassVar[Flags], ...) + no_gen_search = cast(ClassVar[Flags], ...) + no_byte_seek = cast(ClassVar[Flags], ...) + allow_flush = cast(ClassVar[Flags], ...) + ts_nonstrict = cast(ClassVar[Flags], ...) + ts_negative = cast(ClassVar[Flags], ...) + seek_to_pts = cast(ClassVar[Flags], ...) + +class ContainerFormat: + def __init__(self, name: str, mode: Literal["r", "w"] | None = None) -> None: ... + @property + def name(self) -> str: ... + @property + def long_name(self) -> str: ... + @property + def is_input(self) -> bool: ... + @property + def is_output(self) -> bool: ... + @property + def extensions(self) -> set[str]: ... + @property + def flags(self) -> int: ... + @property + def no_file(self) -> bool: ... + +formats_available: set[str] diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/frame.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/frame.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..1aa949619b1694927227c38d6ddaaa194bfa021d --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/frame.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02ee559286994f0e253929a84213940e6be67fab7da81138a29e57b7985ac61e +size 116304 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/frame.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/frame.pxd new file mode 100644 index 0000000000000000000000000000000000000000..6d7214b7caf4edae8f59075a32c805574ccdc514 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/frame.pxd @@ -0,0 +1,14 @@ +cimport libav as lib + +from av.packet cimport Packet +from av.sidedata.sidedata cimport _SideDataContainer + + +cdef class Frame: + cdef lib.AVFrame *ptr + # We define our own time. + cdef lib.AVRational _time_base + cdef _rebase_time(self, lib.AVRational) + cdef _SideDataContainer _side_data + cdef _copy_internal_attributes(self, Frame source, bint data_layout=?) + cdef _init_user_attributes(self) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/frame.py b/.venvs/kimodo/lib/python3.10/site-packages/av/frame.py new file mode 100644 index 0000000000000000000000000000000000000000..7e43de597fb7a51b0e7b7e9cd4232072e78e3d5d --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/frame.py @@ -0,0 +1,201 @@ +import cython +from cython.cimports.av.error import err_check +from cython.cimports.av.opaque import opaque_container +from cython.cimports.av.utils import avrational_to_fraction, to_avrational + +from av.sidedata.sidedata import SideDataContainer + + +@cython.cclass +class Frame: + """ + Base class for audio and video frames. + + See also :class:`~av.audio.frame.AudioFrame` and :class:`~av.video.frame.VideoFrame`. + """ + + def __cinit__(self, *args, **kwargs): + with cython.nogil: + self.ptr = lib.av_frame_alloc() + + def __dealloc__(self): + with cython.nogil: + lib.av_frame_free(cython.address(self.ptr)) + + def __repr__(self): + return f"" + + @cython.cfunc + def _copy_internal_attributes(self, source: Frame, data_layout: cython.bint = True): + # Mimic another frame + self._time_base = source._time_base + lib.av_frame_copy_props(self.ptr, source.ptr) + if data_layout: + # TODO: Assert we don't have any data yet. + self.ptr.format = source.ptr.format + self.ptr.width = source.ptr.width + self.ptr.height = source.ptr.height + self.ptr.ch_layout = source.ptr.ch_layout + + @cython.cfunc + def _init_user_attributes(self): + pass # Dummy to match the API of the others. + + @cython.cfunc + def _rebase_time(self, dst: lib.AVRational): + if not dst.num: + raise ValueError("Cannot rebase to zero time.") + + if not self._time_base.num: + self._time_base = dst + return + + if self._time_base.num == dst.num and self._time_base.den == dst.den: + return + + if self.ptr.pts != lib.AV_NOPTS_VALUE: + self.ptr.pts = lib.av_rescale_q(self.ptr.pts, self._time_base, dst) + + if self.ptr.duration != 0: + self.ptr.duration = lib.av_rescale_q( + self.ptr.duration, self._time_base, dst + ) + + self._time_base = dst + + @property + def dts(self): + """ + The decoding timestamp copied from the :class:`~av.packet.Packet` that triggered returning this frame in :attr:`time_base` units. + + (if frame threading isn't used) This is also the Presentation time of this frame calculated from only :attr:`.Packet.dts` values without pts values. + + :type: int | None + """ + if self.ptr.pkt_dts == lib.AV_NOPTS_VALUE: + return None + return self.ptr.pkt_dts + + @dts.setter + def dts(self, value): + if value is None: + self.ptr.pkt_dts = lib.AV_NOPTS_VALUE + else: + self.ptr.pkt_dts = value + + @property + def pts(self): + """ + The presentation timestamp in :attr:`time_base` units for this frame. + + This is the time at which the frame should be shown to the user. + + :type: int | None + """ + if self.ptr.pts == lib.AV_NOPTS_VALUE: + return None + return self.ptr.pts + + @pts.setter + def pts(self, value): + if value is None: + self.ptr.pts = lib.AV_NOPTS_VALUE + else: + self.ptr.pts = value + + @property + def duration(self): + """ + The duration of the frame in :attr:`time_base` units + + :type: int + """ + return self.ptr.duration + + @duration.setter + def duration(self, value): + self.ptr.duration = value + + @property + @cython.cdivision(True) + def time(self): + """ + The presentation time in seconds for this frame. + + This is the time at which the frame should be shown to the user. + + :type: float | None + """ + if self.ptr.pts == lib.AV_NOPTS_VALUE: + return None + return float(self.ptr.pts) * self._time_base.num / self._time_base.den + + @property + def time_base(self): + """ + The unit of time (in fractional seconds) in which timestamps are expressed. + + :type: fractions.Fraction | None + """ + if self._time_base.num: + return avrational_to_fraction(cython.address(self._time_base)) + + @time_base.setter + def time_base(self, value): + to_avrational(value, cython.address(self._time_base)) + + @property + def is_corrupt(self): + """ + Is this frame corrupt? + + :type: bool + """ + return self.ptr.decode_error_flags != 0 or bool( + self.ptr.flags & lib.AV_FRAME_FLAG_CORRUPT + ) + + @property + def key_frame(self): + """Is this frame a key frame? + + Wraps :ffmpeg:`AVFrame.key_frame`. + + """ + return bool(self.ptr.flags & lib.AV_FRAME_FLAG_KEY) + + @key_frame.setter + def key_frame(self, v): + # PyAV makes no guarantees this does anything. + if v: + self.ptr.flags |= lib.AV_FRAME_FLAG_KEY + else: + self.ptr.flags &= ~lib.AV_FRAME_FLAG_KEY + + @property + def side_data(self): + if self._side_data is None: + self._side_data = SideDataContainer(self) + return self._side_data + + def make_writable(self): + """ + Ensures that the frame data is writable. Copy the data to new buffer if it is not. + This is a wrapper around :ffmpeg:`av_frame_make_writable`. + """ + ret: cython.int = lib.av_frame_make_writable(self.ptr) + err_check(ret) + + @property + def opaque(self): + if self.ptr.opaque_ref is not cython.NULL: + return opaque_container.get( + cython.cast(cython.p_char, self.ptr.opaque_ref.data) + ) + + @opaque.setter + def opaque(self, v): + lib.av_buffer_unref(cython.address(self.ptr.opaque_ref)) + + if v is not None: + self.ptr.opaque_ref = opaque_container.add(v) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/frame.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/frame.pyi new file mode 100644 index 0000000000000000000000000000000000000000..f085fc0f420d90d51413635a9cd22097269a650f --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/frame.pyi @@ -0,0 +1,22 @@ +from fractions import Fraction +from typing import TypedDict + +from av.sidedata.motionvectors import MotionVectors + +class SideData(TypedDict, total=False): + MOTION_VECTORS: MotionVectors + +class Frame: + dts: int | None + pts: int | None + duration: int + time_base: Fraction | None + side_data: SideData + opaque: object + @property + def time(self) -> float | None: ... + @property + def is_corrupt(self) -> bool: ... + @property + def key_frame(self) -> bool: ... + def make_writable(self) -> None: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/index.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/index.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..0d2d96d7c617a45389a036bb11608be4babbd95a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/index.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36c48bf8b61e47ed703877779ead11397f21fcaf6da2cd6661c16c0ac100286d +size 139264 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/index.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/index.pxd new file mode 100644 index 0000000000000000000000000000000000000000..d7098c2f0518c1c9a76daaf86ddc84f0806c0c0d --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/index.pxd @@ -0,0 +1,12 @@ +cimport libav as lib + + +cdef class IndexEntry: + cdef const lib.AVIndexEntry *ptr + cdef _init(self, const lib.AVIndexEntry *ptr) + +cdef class IndexEntries: + cdef lib.AVStream *stream_ptr + cdef _init(self, lib.AVStream *ptr) + +cdef IndexEntries wrap_index_entries(lib.AVStream *ptr) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/index.py b/.venvs/kimodo/lib/python3.10/site-packages/av/index.py new file mode 100644 index 0000000000000000000000000000000000000000..b652bfdc13f0036b65a98d7a40bf8cb99e856545 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/index.py @@ -0,0 +1,149 @@ +import cython +import cython.cimports.libav as lib +from cython.cimports.libc.stdint import int64_t + +_cinit_bypass_sentinel = cython.declare(object, object()) + + +@cython.cfunc +def wrap_index_entry(ptr: cython.pointer[cython.const[lib.AVIndexEntry]]) -> IndexEntry: + obj: IndexEntry = IndexEntry(_cinit_bypass_sentinel) + obj._init(ptr) + return obj + + +@cython.final +@cython.cclass +class IndexEntry: + """A single entry from a stream's index. + + This is a thin wrapper around FFmpeg's ``AVIndexEntry``. + + The exact meaning of the fields depends on the container/demuxer. + """ + + def __cinit__(self, sentinel): + if sentinel is not _cinit_bypass_sentinel: + raise RuntimeError("cannot manually instantiate IndexEntry") + + @cython.cfunc + def _init(self, ptr: cython.pointer[cython.const[lib.AVIndexEntry]]): + self.ptr = ptr + + def __repr__(self): + return ( + f"" + ) + + @property + def pos(self): + return self.ptr.pos + + @property + def timestamp(self): + return self.ptr.timestamp + + @property + def flags(self): + return self.ptr.flags + + @property + def is_keyframe(self): + return bool(self.ptr.flags & lib.AVINDEX_KEYFRAME) + + @property + def is_discard(self): + return bool(self.ptr.flags & lib.AVINDEX_DISCARD_FRAME) + + @property + def size(self): + return self.ptr.size + + @property + def min_distance(self): + return self.ptr.min_distance + + +@cython.cfunc +def wrap_index_entries(ptr: cython.pointer[lib.AVStream]) -> IndexEntries: + obj: IndexEntries = IndexEntries(_cinit_bypass_sentinel) + obj._init(ptr) + return obj + + +@cython.final +@cython.cclass +class IndexEntries: + """A sequence-like view of FFmpeg's per-stream index entries. + + Exposed as :attr:`~av.stream.Stream.index_entries`. + + The index is provided by the demuxer and may be empty or incomplete depending + on the container format. This is useful for fast multi-seek loops (e.g., decoding + at a lower-than-native framerate). + """ + + def __cinit__(self, sentinel): + if sentinel is _cinit_bypass_sentinel: + return + raise RuntimeError("cannot manually instantiate IndexEntries") + + @cython.cfunc + def _init(self, ptr: cython.pointer[lib.AVStream]): + self.stream_ptr = ptr + + def __repr__(self): + return f"" + + def __len__(self) -> int: + with cython.nogil: + return lib.avformat_index_get_entries_count(self.stream_ptr) + + def __iter__(self): + for i in range(len(self)): + yield self[i] + + def __getitem__(self, index): + if isinstance(index, int): + n = len(self) + if index < 0: + index += n + if index < 0 or index >= n: + raise IndexError(f"Index entries {index} out of bounds for size {n}") + + c_idx: cython.int = index + entry: cython.pointer[cython.const[lib.AVIndexEntry]] + with cython.nogil: + entry = lib.avformat_index_get_entry(self.stream_ptr, c_idx) + if entry == cython.NULL: + raise IndexError("index entry not found") + + return wrap_index_entry(entry) + elif isinstance(index, slice): + start, stop, step = index.indices(len(self)) + return [self[i] for i in range(start, stop, step)] + else: + raise TypeError("Index must be an integer or a slice") + + def search_timestamp( + self, timestamp, *, backward: bool = True, any_frame: bool = False + ): + """Search the underlying index for ``timestamp``. + + This wraps FFmpeg's ``av_index_search_timestamp``. + + Returns an index into this object, or ``-1`` if no match is found. + """ + c_timestamp: int64_t = timestamp + flags: cython.int = 0 + + if backward: + flags |= lib.AVSEEK_FLAG_BACKWARD + if any_frame: + flags |= lib.AVSEEK_FLAG_ANY + + with cython.nogil: + idx = lib.av_index_search_timestamp(self.stream_ptr, c_timestamp, flags) + + return idx diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/index.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/index.pyi new file mode 100644 index 0000000000000000000000000000000000000000..b74262609a653a9db6bda44640a9476fe81650a9 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/index.pyi @@ -0,0 +1,21 @@ +from typing import Iterator, overload + +class IndexEntry: + pos: int + timestamp: int + flags: int + is_keyframe: bool + is_discard: bool + size: int + min_distance: int + +class IndexEntries: + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[IndexEntry]: ... + @overload + def __getitem__(self, index: int) -> IndexEntry: ... + @overload + def __getitem__(self, index: slice) -> list[IndexEntry]: ... + def search_timestamp( + self, timestamp, *, backward: bool = True, any_frame: bool = False + ) -> int: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/logging.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/logging.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..b2defe290d23058e3bf6d05fabb9939ab044da4a --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/logging.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78ab7ee13a30ddd851b37d66b2d832dbf6f88549d477156330274e941f46bf86 +size 154192 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/logging.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/logging.pxd new file mode 100644 index 0000000000000000000000000000000000000000..b5a99be02805f7b71972e3baaa4761209ea44ba5 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/logging.pxd @@ -0,0 +1,12 @@ +cimport libav as lib + + +cdef extern from "Python.h" nogil: + void PyErr_PrintEx(int set_sys_last_vars) + int Py_IsInitialized() + void PyErr_Display(object, object, object) + +cdef extern from "stdio.h" nogil: + cdef int vsnprintf(char *output, int n, const char *format, lib.va_list args) + +cpdef get_last_error() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/logging.py b/.venvs/kimodo/lib/python3.10/site-packages/av/logging.py new file mode 100644 index 0000000000000000000000000000000000000000..295779444fa797d067954f602b618b78b042e117 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/logging.py @@ -0,0 +1,382 @@ +# type: ignore +""" +FFmpeg has a logging system that it uses extensively. It's very noisy, so PyAV turns it +off by default. This unfortunately has the effect of making raised errors have less +detailed messages. It's therefore recommended to use VERBOSE when developing. + +.. _enable_logging: + +Enabling Logging +~~~~~~~~~~~~~~~~~ + +You can hook into the logging system with Python by setting the log level:: + + import av + + av.logging.set_level(av.logging.VERBOSE) + + +PyAV hooks into that system to translate FFmpeg logs into Python's +`logging system `_. + +If you are not already using Python's logging system, you can initialize it +quickly with:: + + import logging + logging.basicConfig() + + +Note that handling logs with Python sometimes doesn't play nicely with multi-threaded workflows. +An alternative is :func:`restore_default_callback`. + +This restores FFmpeg's default logging system, which prints to the terminal. +Like with setting the log level to ``None``, this may also result in raised errors +having less detailed messages. + + +API Reference +~~~~~~~~~~~~~ + +""" + +import logging +import sys +from threading import Lock, get_ident + +import cython +from cython.cimports.libc.stdio import fprintf, stderr +from cython.cimports.libc.stdlib import free, malloc + +# Library levels. +PANIC = lib.AV_LOG_PANIC # 0 +FATAL = lib.AV_LOG_FATAL # 8 +ERROR = lib.AV_LOG_ERROR +WARNING = lib.AV_LOG_WARNING +INFO = lib.AV_LOG_INFO +VERBOSE = lib.AV_LOG_VERBOSE +DEBUG = lib.AV_LOG_DEBUG +TRACE = lib.AV_LOG_TRACE + +# Mimicking stdlib. +CRITICAL = FATAL + + +@cython.ccall +def adapt_level(level: cython.int): + """Convert a library log level to a Python log level.""" + if level <= lib.AV_LOG_FATAL: # Includes PANIC + return 50 # logging.CRITICAL + elif level <= lib.AV_LOG_ERROR: + return 40 # logging.ERROR + elif level <= lib.AV_LOG_WARNING: + return 30 # logging.WARNING + elif level <= lib.AV_LOG_INFO: + return 20 # logging.INFO + elif level <= lib.AV_LOG_VERBOSE: + return 10 # logging.DEBUG + elif level <= lib.AV_LOG_DEBUG: + return 5 # Lower than any logging constant. + else: # lib.AV_LOG_TRACE + return 1 + + +level_threshold = cython.declare(object, None) +c_level_threshold = cython.declare(cython.int, lib.AV_LOG_QUIET) + +# ... but lets limit ourselves to WARNING (assuming nobody already did this). +if "libav" not in logging.Logger.manager.loggerDict: + logging.getLogger("libav").setLevel(logging.WARNING) + + +def get_level(): + """Returns the current log level. See :func:`set_level`.""" + return level_threshold + + +def set_level(level): + """set_level(level) + + Sets PyAV's log level. It can be set to constants available in this + module: ``PANIC``, ``FATAL``, ``ERROR``, ``WARNING``, ``INFO``, + ``VERBOSE``, ``DEBUG``, or ``None`` (the default). + + PyAV defaults to totally ignoring all ffmpeg logs. This has the side effect of + making certain Exceptions have no messages. It's therefore recommended to use: + + av.logging.set_level(av.logging.VERBOSE) + + When developing your application. + """ + global level_threshold + global c_level_threshold + + if level is None: + level_threshold = level + lib.av_log_set_callback(nolog_callback) + elif type(level) is int: + level_threshold = level + c_level_threshold = level + lib.av_log_set_callback(log_callback) + else: + raise ValueError("level must be: int | None") + + +def set_libav_level(level): + """Set libav's log level. It can be set to constants available in this + module: ``PANIC``, ``FATAL``, ``ERROR``, ``WARNING``, ``INFO``, + ``VERBOSE``, ``DEBUG``. + + When PyAV logging is disabled, setting this will change the level of + the logs printed to the terminal. + """ + lib.av_log_set_level(level) + + +def restore_default_callback(): + """Revert back to FFmpeg's log callback, which prints to the terminal.""" + lib.av_log_set_callback(lib.av_log_default_callback) + + +skip_repeated = cython.declare(cython.bint, True) +skip_lock = cython.declare(object, Lock()) +last_log = cython.declare(object, None) +skip_count = cython.declare(cython.int, 0) + + +def get_skip_repeated(): + """Will identical logs be emitted?""" + return skip_repeated + + +def set_skip_repeated(v): + """Set if identical logs will be emitted""" + global skip_repeated + skip_repeated = bool(v) + + +# For error reporting. +last_error = cython.declare(object, None) +error_count = cython.declare(cython.int, 0) + + +@cython.ccall +def get_last_error(): + """Get the last log that was at least ``ERROR``.""" + if error_count: + with skip_lock: + return error_count, last_error + else: + return 0, None + + +global_captures = cython.declare(list, []) +thread_captures = cython.declare(dict, {}) + + +@cython.final +@cython.cclass +class Capture: + """A context manager for capturing logs. + + :param bool local: Should logs from all threads be captured, or just one + this object is constructed in? + + e.g.:: + + with Capture() as logs: + # Do something. + for log in logs: + print(log.message) + + """ + + logs = cython.declare(list, visibility="readonly") + captures = cython.declare(list, visibility="private") + + def __init__(self, local: cython.bint = True): + self.logs = [] + if local: + self.captures = thread_captures.setdefault(get_ident(), []) + else: + self.captures = global_captures + + def __enter__(self): + self.captures.append(self.logs) + return self.logs + + def __exit__(self, type_, value, traceback): + self.captures.pop() + + +log_context = cython.struct( + class_=cython.pointer[lib.AVClass], + name=cython.p_char, +) + +item_name_func = cython.typedef("const char *(*item_name_func)(void *) noexcept nogil") + + +@cython.cfunc +@cython.nogil +@cython.exceptval(check=False) +def log_context_name(ptr: cython.p_void) -> cython.p_char: + obj: cython.pointer[log_context] = cython.cast(cython.pointer[log_context], ptr) + return obj.name + + +log_class = cython.declare(lib.AVClass) +log_class.item_name = cython.cast(item_name_func, log_context_name) + + +@cython.ccall +def log(level: cython.int, name: str, message: str): + """Send a log through the library logging system. + + This is mostly for testing. + """ + obj: cython.pointer[log_context] = cython.cast( + cython.pointer[log_context], malloc(cython.sizeof(log_context)) + ) + obj.class_ = cython.address(log_class) + obj.name = name + message_bytes: bytes = message.encode("utf-8") + + lib.av_log( + cython.cast(cython.p_void, obj), + level, + "%s", + cython.cast(cython.p_char, message_bytes), + ) + free(obj) + + +@cython.cfunc +def log_callback_gil( + level: cython.int, c_name: cython.p_const_char, c_message: cython.p_char +): + global error_count + global skip_count + global last_log + global last_error + + name = cython.cast(str, c_name) if c_name is not cython.NULL else "" + message = cython.cast(bytes, c_message).decode("utf8", "backslashreplace") + log = (level, name, message) + + # We have to filter it ourselves, but we will still process it in general so + # it is available to our error handling. + # Note that FFmpeg's levels are backwards from Python's. + is_interesting: cython.bint = level <= level_threshold + + # Skip messages which are identical to the previous. + # TODO: Be smarter about threads. + is_repeated: cython.bint = False + repeat_log: object = None + + with skip_lock: + if is_interesting: + is_repeated = skip_repeated and last_log == log + + if is_repeated: + skip_count += 1 + + elif skip_count: + # Now that we have hit the end of the repeat cycle, tally up how many. + if skip_count == 1: + repeat_log = last_log + else: + repeat_log = ( + last_log[0], + last_log[1], + "%s (repeated %d more times)" % (last_log[2], skip_count), + ) + skip_count = 0 + + last_log = log + + # Hold onto errors for err_check. + if level == lib.AV_LOG_ERROR: + error_count += 1 + last_error = log + + if repeat_log is not None: + log_callback_emit(repeat_log) + + if is_interesting and not is_repeated: + log_callback_emit(log) + + +@cython.cfunc +def log_callback_emit(log): + lib_level, name, message = log + + captures = thread_captures.get(get_ident()) or global_captures + if captures: + captures[-1].append(log) + return + + py_level = adapt_level(lib_level) + + logger_name = "libav." + name if name else "libav.generic" + logger = logging.getLogger(logger_name) + logger.log(py_level, message.strip()) + + +@cython.cfunc +@cython.nogil +@cython.exceptval(check=False) +def log_callback( + ptr: cython.p_void, + level: cython.int, + format: cython.p_const_char, + args: lib.va_list, +) -> cython.void: + inited: cython.bint = Py_IsInitialized() + if not inited: + return + if level > c_level_threshold and level != lib.AV_LOG_ERROR: + return + + # Format the message. + message: cython.char[1024] + vsnprintf(message, 1023, format, args) + + # Get the name. + name: cython.p_const_char = cython.NULL + cls: cython.pointer[lib.AVClass] = ( + cython.cast(cython.pointer[cython.pointer[lib.AVClass]], ptr)[0] + if ptr + else cython.NULL + ) + if cls and cls.item_name: + name = cls.item_name(ptr) + + with cython.gil: + try: + log_callback_gil(level, name, message) + except Exception: + fprintf( + stderr, + "av.logging: exception while handling %s[%d]: %s\n", + name, + level, + message, + ) + # For some reason PyErr_PrintEx(0) won't work. + exc, type_, tb = sys.exc_info() + PyErr_Display(exc, type_, tb) + + +@cython.cfunc +@cython.nogil +@cython.exceptval(check=False) +def nolog_callback( + ptr: cython.p_void, + level: cython.int, + format: cython.p_const_char, + args: lib.va_list, +) -> cython.void: + pass + + +lib.av_log_set_callback(nolog_callback) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/logging.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/logging.pyi new file mode 100644 index 0000000000000000000000000000000000000000..8c32de77d21c75ca4f38fbd78ecb5d7c2871c2d6 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/logging.pyi @@ -0,0 +1,33 @@ +from typing import Any, Callable + +PANIC: int +FATAL: int +ERROR: int +WARNING: int +INFO: int +VERBOSE: int +DEBUG: int +TRACE: int +CRITICAL: int + +def adapt_level(level: int) -> int: ... +def get_level() -> int | None: ... +def set_level(level: int | None) -> None: ... +def set_libav_level(level: int) -> None: ... +def restore_default_callback() -> None: ... +def get_skip_repeated() -> bool: ... +def set_skip_repeated(v: bool) -> None: ... +def get_last_error() -> tuple[int, tuple[int, str, str] | None]: ... +def log(level: int, name: str, message: str) -> None: ... + +class Capture: + logs: list[tuple[int, str, str]] + + def __init__(self, local: bool = True) -> None: ... + def __enter__(self) -> list[tuple[int, str, str]]: ... + def __exit__( + self, + type_: type | None, + value: Exception | None, + traceback: Callable[..., Any] | None, + ) -> None: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/opaque.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/opaque.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..37bcd5e151fb41b4bdaf0ba6f7e8e62abbb0f2a3 Binary files /dev/null and b/.venvs/kimodo/lib/python3.10/site-packages/av/opaque.cpython-310-darwin.so differ diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/opaque.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/opaque.pxd new file mode 100644 index 0000000000000000000000000000000000000000..998e1339745fc93ab61197bdc8bd03b17905282e --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/opaque.pxd @@ -0,0 +1,11 @@ +cimport libav as lib + + +cdef class OpaqueContainer: + cdef dict _objects + cdef lib.AVBufferRef *add(self, object v) + cdef object get(self, char *name) + cdef object pop(self, char *name) + + +cdef OpaqueContainer opaque_container diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/opaque.py b/.venvs/kimodo/lib/python3.10/site-packages/av/opaque.py new file mode 100644 index 0000000000000000000000000000000000000000..d6696289bd14d5b27ccefa6fd16c7a0f1ed1bac9 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/opaque.py @@ -0,0 +1,58 @@ +# type:ignore +import cython +import cython.cimports.libav as lib +from cython import NULL, sizeof +from cython.cimports.libc.stdint import uint8_t, uintptr_t +from cython.cimports.libc.string import memcpy + +u8ptr = cython.typedef(cython.pointer[uint8_t]) + + +@cython.cfunc +@cython.exceptval(check=False) +@cython.nogil +def key_free(opaque: cython.p_void, data: u8ptr) -> cython.void: + name: cython.p_char = cython.cast(cython.p_char, data) + with cython.gil: + opaque_container.pop(name) + lib.av_free(data) + + +@cython.final +@cython.cclass +class OpaqueContainer: + def __cinit__(self): + self._objects = {} + + @cython.cfunc + def add(self, v: object) -> cython.pointer[lib.AVBufferRef]: + # Use object's memory address as key + key: uintptr_t = cython.cast(cython.longlong, id(v)) + self._objects[key] = v + + data: u8ptr = cython.cast(u8ptr, lib.av_malloc(sizeof(uintptr_t))) + if data == NULL: + raise MemoryError("Failed to allocate memory for key") + + memcpy(data, cython.address(key), sizeof(uintptr_t)) + + # Create the buffer with our free callback + buffer_ref: cython.pointer[lib.AVBufferRef] = lib.av_buffer_create( + data, sizeof(uintptr_t), key_free, NULL, 0 + ) + + if buffer_ref == NULL: + raise MemoryError("Failed to create AVBufferRef") + + return buffer_ref + + def get(self, name) -> object: + key: uintptr_t = cython.cast(cython.pointer[uintptr_t], name)[0] + return self._objects.get(key) + + def pop(self, name) -> object: + key: uintptr_t = cython.cast(cython.pointer[uintptr_t], name)[0] + return self._objects.pop(key, None) + + +opaque_container: OpaqueContainer = OpaqueContainer() diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/packet.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/packet.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..24e8c64185db960a8f9b50ff4617d9eb7ca941fc --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/packet.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97f899ebe894aed9982f78ea4b4470c4c1b6be5322724e41232fb5f1d294c85c +size 164224 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/packet.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/packet.pxd new file mode 100644 index 0000000000000000000000000000000000000000..1546a1315aa35ae9f2599e9e6f79e4e8cef2e1a6 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/packet.pxd @@ -0,0 +1,23 @@ +from cython.cimports.libc.stdint import uint8_t + +cimport libav as lib + +from av.buffer cimport Buffer, ByteSource +from av.stream cimport Stream + + +cdef class PacketSideData(Buffer): + cdef uint8_t *data + cdef size_t size + cdef lib.AVPacketSideDataType dtype + + cdef size_t _buffer_size(self) + cdef void* _buffer_ptr(self) + cdef bint _buffer_writable(self) + +cdef class Packet(Buffer): + cdef lib.AVPacket* ptr + cdef Stream _stream + cdef _rebase_time(self, lib.AVRational) + cdef size_t _buffer_size(self) + cdef void* _buffer_ptr(self) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/packet.py b/.venvs/kimodo/lib/python3.10/site-packages/av/packet.py new file mode 100644 index 0000000000000000000000000000000000000000..a69cf38c10528330356ad9eb85968203f5c0e89e --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/packet.py @@ -0,0 +1,495 @@ +from typing import Iterator, Literal, get_args + +import cython +from cython.cimports import libav as lib +from cython.cimports.av.buffer import Buffer, ByteSource, bytesource +from cython.cimports.av.error import err_check +from cython.cimports.av.opaque import opaque_container +from cython.cimports.av.utils import avrational_to_fraction, to_avrational +from cython.cimports.cpython.ref import Py_DECREF, Py_INCREF +from cython.cimports.libc.stdint import uint8_t +from cython.cimports.libc.string import memcpy + +# Check https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/packet.h#L41 +# for new additions in the future ffmpeg releases +# Note: the order must follow that of the AVPacketSideDataType enum def +PktSideDataT = Literal[ + "palette", + "new_extradata", + "param_change", + "h263_mb_info", + "replay_gain", + "display_matrix", + "stereo_3d", + "audio_service_type", + "quality_stats", + "fallback_track", + "cpb_properties", + "skip_samples", + "jp_dual_mono", + "strings_metadata", + "subtitle_position", + "matroska_block_additional", + "webvtt_identifier", + "webvtt_settings", + "metadata_update", + "mpegts_stream_id", + "mastering_display_metadata", + "spherical", + "content_light_level", + "a53_cc", + "encryption_init_info", + "encryption_info", + "afd", + "prft", + "icc_profile", + "dovi_conf", + "s12m_timecode", + "dynamic_hdr10_plus", + "iamf_mix_gain_param", + "iamf_info_param", + "iamf_recon_gain_info_param", + "ambient_viewing_environment", + "frame_cropping", + "lcevc", + "3d_reference_displays", + "rtcp_sr", +] + + +def packet_sidedata_type_to_literal(dtype: lib.AVPacketSideDataType) -> PktSideDataT: + return get_args(PktSideDataT)[cython.cast(int, dtype)] + + +def packet_sidedata_type_from_literal(dtype: PktSideDataT) -> lib.AVPacketSideDataType: + return get_args(PktSideDataT).index(dtype) + + +@cython.final +@cython.cclass +class PacketSideData(Buffer): + @staticmethod + def from_packet(packet: Packet, data_type: PktSideDataT) -> PacketSideData: + """create new PacketSideData by copying an existing packet's side data + + :param packet: Source packet + :type packet: :class:`~av.packet.Packet` + :param data_type: side data type + :return: newly created copy of the side data if the side data of the + requested type is found in the packet, else an empty object + :rtype: :class:`~av.packet.PacketSideData` + """ + + dtype = packet_sidedata_type_from_literal(data_type) + return _packet_sidedata_from_packet(packet.ptr, dtype) + + def __cinit__(self, dtype: lib.AVPacketSideDataType, size: cython.size_t): + self.dtype = dtype + with cython.nogil: + if size: + self.data = cython.cast(cython.p_uchar, lib.av_malloc(size)) + if self.data == cython.NULL: + raise MemoryError("Failed to allocate memory") + else: + self.data = cython.NULL + self.size = size + + def __dealloc__(self): + with cython.nogil: + lib.av_freep(cython.address(self.data)) + + def to_packet(self, packet: Packet, move: cython.bint = False): + """copy or move side data to the specified packet + + :param packet: Target packet + :type packet: :class:`~av.packet.Packet` + :param move: True to move the data from this object to the packet, + defaults to False. + :type move: bool + """ + if self.size == 0: + # nothing to add, should clear existing side_data in packet? + return + + data = self.data + + with cython.nogil: + if not move: + data = cython.cast(cython.p_uchar, lib.av_malloc(self.size)) + if data == cython.NULL: + raise MemoryError("Failed to allocate memory") + memcpy(data, self.data, self.size) + + res = lib.av_packet_add_side_data(packet.ptr, self.dtype, data, self.size) + err_check(res) + + if move: + self.data = cython.NULL + self.size = 0 + + @property + def data_type(self) -> str: + """ + The type of this packet side data. + + :type: str + """ + return packet_sidedata_type_to_literal(self.dtype) + + @property + def data_desc(self) -> str: + """ + The description of this packet side data type. + + :type: str + """ + + return lib.av_packet_side_data_name(self.dtype) + + @property + def data_size(self) -> int: + """ + The size in bytes of this packet side data. + + :type: int + """ + return self.size + + # Buffer protocol implementation + @cython.cfunc + def _buffer_size(self) -> cython.size_t: + return self.size + + @cython.cfunc + def _buffer_ptr(self) -> cython.p_void: + return self.data + + @cython.cfunc + def _buffer_writable(self) -> cython.bint: + return True + + def __bool__(self) -> bool: + """ + True if this object holds side data. + + :type: bool + """ + return self.data != cython.NULL + + +@cython.cfunc +def _packet_sidedata_from_packet( + packet: cython.pointer[lib.AVPacket], dtype: lib.AVPacketSideDataType +) -> PacketSideData: + with cython.nogil: + c_ptr = lib.av_packet_side_data_get( + packet.side_data, packet.side_data_elems, dtype + ) + found: cython.bint = c_ptr != cython.NULL + + sdata = PacketSideData(dtype, c_ptr.size if found else 0) + + with cython.nogil: + if found: + memcpy(sdata.data, c_ptr.data, c_ptr.size) + + return sdata + + +@cython.cfunc +@cython.nogil +@cython.exceptval(check=False) +def _python_free( + opaque: cython.p_void, + data: cython.pointer[uint8_t], +) -> cython.void: + if opaque != cython.NULL: + with cython.gil: + Py_DECREF(cython.cast(object, opaque)) + + +@cython.final +@cython.cclass +class Packet(Buffer): + """A packet of encoded data within a :class:`~av.format.Stream`. + + This may, or may not include a complete object within a stream. + :meth:`decode` must be called to extract encoded data. + """ + + def __cinit__(self, input=None): + with cython.nogil: + self.ptr = lib.av_packet_alloc() + + def __dealloc__(self): + with cython.nogil: + lib.av_packet_free(cython.address(self.ptr)) + + def __init__(self, input=None): + size: cython.size_t = 0 + source: ByteSource = None + buf: cython.pointer[lib.AVBufferRef] + + if input is None: + return + + if isinstance(input, int): + size = input + if size: + err_check(lib.av_new_packet(self.ptr, size)) + else: + source = bytesource(input) + size = source.length + if size: + # Create a buffer that references the source data directly. + Py_INCREF(source) + buf = lib.av_buffer_create( + source.ptr, + size, + _python_free, + cython.cast(cython.p_void, source), + 0, + ) + if buf == cython.NULL: + Py_DECREF(source) + raise MemoryError("Could not allocate AVBufferRef") + self.ptr.buf = buf + self.ptr.data = source.ptr + self.ptr.size = size + + def __repr__(self): + stream = self._stream.index if self._stream else 0 + return ( + f"av.{self.__class__.__name__} of #{stream}, dts={self.dts}," + f" pts={self.pts}; {self.ptr.size} bytes at 0x{id(self):x}>" + ) + + # Buffer protocol. + @cython.cfunc + def _buffer_size(self) -> cython.size_t: + return self.ptr.size + + @cython.cfunc + def _buffer_ptr(self) -> cython.p_void: + return self.ptr.data + + @cython.cfunc + def _rebase_time(self, dst: lib.AVRational): + if not dst.num: + raise ValueError("Cannot rebase to zero time.") + + if not self.ptr.time_base.num: + self.ptr.time_base = dst + return + + if self.ptr.time_base.num == dst.num and self.ptr.time_base.den == dst.den: + return + + lib.av_packet_rescale_ts(self.ptr, self.ptr.time_base, dst) + self.ptr.time_base = dst + + def decode(self): + """ + Send the packet's data to the decoder and return a list of + :class:`.AudioFrame`, :class:`.VideoFrame` or :class:`.SubtitleSet`. + """ + return self._stream.decode(self) + + @property + def stream_index(self): + return self.ptr.stream_index + + @property + def stream(self): + """ + The :class:`Stream` this packet was demuxed from. + """ + return self._stream + + @stream.setter + def stream(self, stream: Stream): + self._stream = stream + self.ptr.stream_index = stream.ptr.index + + @property + def time_base(self): + """ + The unit of time (in fractional seconds) in which timestamps are expressed. + + :type: fractions.Fraction + """ + return avrational_to_fraction(cython.address(self.ptr.time_base)) + + @time_base.setter + def time_base(self, value): + to_avrational(value, cython.address(self.ptr.time_base)) + + @property + def pts(self): + """ + The presentation timestamp in :attr:`time_base` units for this packet. + + This is the time at which the packet should be shown to the user. + + :type: int | None + """ + if self.ptr.pts != lib.AV_NOPTS_VALUE: + return self.ptr.pts + + @pts.setter + def pts(self, v): + if v is None: + self.ptr.pts = lib.AV_NOPTS_VALUE + else: + self.ptr.pts = v + + @property + def dts(self): + """ + The decoding timestamp in :attr:`time_base` units for this packet. + + :type: int | None + """ + if self.ptr.dts != lib.AV_NOPTS_VALUE: + return self.ptr.dts + + @dts.setter + def dts(self, v): + if v is None: + self.ptr.dts = lib.AV_NOPTS_VALUE + else: + self.ptr.dts = v + + @property + def pos(self): + """ + The byte position of this packet within the :class:`.Stream`. + + Returns `None` if it is not known. + + :type: int | None + """ + if self.ptr.pos != -1: + return self.ptr.pos + + @property + def size(self): + """ + The size in bytes of this packet's data. + + :type: int + """ + return self.ptr.size + + @property + def duration(self): + """ + The duration in :attr:`time_base` units for this packet. + + Returns `None` if it is not known. + + :type: int + """ + if self.ptr.duration != lib.AV_NOPTS_VALUE: + return self.ptr.duration + + @duration.setter + def duration(self, v): + if v is None: + self.ptr.duration = lib.AV_NOPTS_VALUE + else: + self.ptr.duration = v + + @property + def is_keyframe(self): + return bool(self.ptr.flags & lib.AV_PKT_FLAG_KEY) + + @is_keyframe.setter + def is_keyframe(self, v): + if v: + self.ptr.flags |= lib.AV_PKT_FLAG_KEY + else: + self.ptr.flags &= ~(lib.AV_PKT_FLAG_KEY) + + @property + def is_corrupt(self): + return bool(self.ptr.flags & lib.AV_PKT_FLAG_CORRUPT) + + @is_corrupt.setter + def is_corrupt(self, v): + if v: + self.ptr.flags |= lib.AV_PKT_FLAG_CORRUPT + else: + self.ptr.flags &= ~(lib.AV_PKT_FLAG_CORRUPT) + + @property + def is_discard(self): + return bool(self.ptr.flags & lib.AV_PKT_FLAG_DISCARD) + + @property + def is_trusted(self): + return bool(self.ptr.flags & lib.AV_PKT_FLAG_TRUSTED) + + @property + def is_disposable(self): + return bool(self.ptr.flags & lib.AV_PKT_FLAG_DISPOSABLE) + + @property + def opaque(self): + if self.ptr.opaque_ref is not cython.NULL: + return opaque_container.get( + cython.cast(cython.p_char, self.ptr.opaque_ref.data) + ) + + @opaque.setter + def opaque(self, v): + lib.av_buffer_unref(cython.address(self.ptr.opaque_ref)) + + if v is None: + return + self.ptr.opaque_ref = opaque_container.add(v) + + def has_sidedata(self, dtype: str) -> bool: + """True if this packet has the specified side data + + :param dtype: side data type + :type dtype: str + """ + + dtype2 = packet_sidedata_type_from_literal(dtype) + return ( + lib.av_packet_side_data_get( + self.ptr.side_data, self.ptr.side_data_elems, dtype2 + ) + != cython.NULL + ) + + def get_sidedata(self, dtype: str) -> PacketSideData: + """get a copy of the side data + + :param dtype: side data type (:meth:`~av.packet.PacketSideData.sidedata_types` for the full list of options) + :type dtype: str + :return: newly created copy of the side data if the side data of the + requested type is found in the packet, else an empty object + :rtype: :class:`~av.packet.PacketSideData` + """ + return PacketSideData.from_packet(self, dtype) + + def set_sidedata(self, sidedata: PacketSideData, move: cython.bint = False): + """copy or move side data to this packet + + :param sidedata: Source packet side data + :type sidedata: :class:`~av.packet.PacketSideData` + :param move: If True, move the data from `sidedata` object, defaults to False + :type move: bool + """ + sidedata.to_packet(self, move) + + def iter_sidedata(self) -> Iterator[PacketSideData]: + """iterate over side data of this packet. + + :yield: :class:`~av.packet.PacketSideData` object + """ + + for i in range(self.ptr.side_data_elems): + yield _packet_sidedata_from_packet(self.ptr, self.ptr.side_data[i].type) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/packet.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/packet.pyi new file mode 100644 index 0000000000000000000000000000000000000000..3a0af39effded2dc7345b6a8b982febb71cb3e96 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/packet.pyi @@ -0,0 +1,111 @@ +from fractions import Fraction +from typing import Generic, Iterator, Literal, TypeVar, overload + +from av.audio.frame import AudioFrame +from av.audio.stream import AudioStream +from av.stream import Stream +from av.subtitles.stream import SubtitleStream +from av.subtitles.subtitle import AssSubtitle, BitmapSubtitle +from av.video.frame import VideoFrame +from av.video.stream import VideoStream + +from .buffer import Buffer +from .stream import Stream + +# Sync with definition in 'packet.py' +PktSideDataT = Literal[ + "palette", + "new_extradata", + "param_change", + "h263_mb_info", + "replay_gain", + "display_matrix", + "stereo_3d", + "audio_service_type", + "quality_stats", + "fallback_track", + "cpb_properties", + "skip_samples", + "jp_dual_mono", + "strings_metadata", + "subtitle_position", + "matroska_block_additional", + "webvtt_identifier", + "webvtt_settings", + "metadata_update", + "mpegts_stream_id", + "mastering_display_metadata", + "spherical", + "content_light_level", + "a53_cc", + "encryption_init_info", + "encryption_info", + "afd", + "prft", + "icc_profile", + "dovi_conf", + "s12m_timecode", + "dynamic_hdr10_plus", + "iamf_mix_gain_param", + "iamf_info_param", + "iamf_recon_gain_info_param", + "ambient_viewing_environment", + "frame_cropping", + "lcevc", + "3d_reference_displays", + "rtcp_sr", +] + +class PacketSideData(Buffer): + @staticmethod + def from_packet(packet: Packet[Stream], dtype: PktSideDataT) -> PacketSideData: ... + def to_packet(self, packet: Packet[Stream], move: bool = False): ... + @property + def data_type(self) -> str: ... + @property + def data_desc(self) -> str: ... + @property + def data_size(self) -> int: ... + def __bool__(self) -> bool: ... + +def packet_sidedata_type_to_literal(dtype: int) -> PktSideDataT: ... +def packet_sidedata_type_from_literal(dtype: PktSideDataT) -> int: ... + +# TypeVar for stream types - bound to Stream so it can be any stream type +StreamT = TypeVar("StreamT", bound=Stream) + +class Packet(Buffer, Generic[StreamT]): + stream: StreamT + stream_index: int + time_base: Fraction + pts: int | None + dts: int | None + pos: int | None + size: int + duration: int | None + opaque: object + is_keyframe: bool + is_corrupt: bool + is_discard: bool + is_trusted: bool + is_disposable: bool + + def __init__(self: Packet[Stream], input: int | bytes | None = None) -> None: ... + + # Overloads that return the same type as the stream's decode method + @overload + def decode(self: Packet[VideoStream]) -> list[VideoFrame]: ... + @overload + def decode(self: Packet[AudioStream]) -> list[AudioFrame]: ... + @overload + def decode( + self: Packet[SubtitleStream], + ) -> list[AssSubtitle] | list[BitmapSubtitle]: ... + @overload + def decode( + self, + ) -> list[VideoFrame | AudioFrame | AssSubtitle | BitmapSubtitle]: ... + def has_sidedata(self, dtype: PktSideDataT) -> bool: ... + def get_sidedata(self, dtype: PktSideDataT) -> PacketSideData: ... + def set_sidedata(self, sidedata: PacketSideData, move: bool = False) -> None: ... + def iter_sidedata(self) -> Iterator[PacketSideData]: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/plane.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/plane.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..caac886cb159a407197695bc552c1de22c1d61ba --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/plane.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e59182e03704a10f8dc0083bc97c2369bdedf2d4eeeb3eff1dd8daa90ebd86a +size 112624 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/plane.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/plane.pxd new file mode 100644 index 0000000000000000000000000000000000000000..2066bfea89d6214dcaaee80ee0c54773ccb05976 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/plane.pxd @@ -0,0 +1,9 @@ +from av.buffer cimport Buffer +from av.frame cimport Frame + + +cdef class Plane(Buffer): + cdef Frame frame + cdef int index + cdef size_t _buffer_size(self) + cdef void* _buffer_ptr(self) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/plane.py b/.venvs/kimodo/lib/python3.10/site-packages/av/plane.py new file mode 100644 index 0000000000000000000000000000000000000000..f84a99c3da2eb487be3fb180f8a0051851ac0281 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/plane.py @@ -0,0 +1,24 @@ +import cython + + +@cython.cclass +class Plane(Buffer): + """ + Base class for audio and video planes. + + See also :class:`~av.audio.plane.AudioPlane` and :class:`~av.video.plane.VideoPlane`. + """ + + def __cinit__(self, frame: Frame, index: cython.int): + self.frame = frame + self.index = index + + def __repr__(self): + return ( + f"" + ) + + @cython.cfunc + def _buffer_ptr(self) -> cython.p_void: + return self.frame.ptr.extended_data[self.index] diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/plane.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/plane.pyi new file mode 100644 index 0000000000000000000000000000000000000000..99594d11a23d021fb0181b6bfe5ecc1bcbc43c1b --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/plane.pyi @@ -0,0 +1,8 @@ +from .buffer import Buffer +from .frame import Frame + +class Plane(Buffer): + frame: Frame + index: int + + def __init__(self, frame: Frame, index: int) -> None: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/py.typed b/.venvs/kimodo/lib/python3.10/site-packages/av/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/__init__.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/__init__.pxd new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/__init__.py b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/encparams.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/encparams.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..7766ef73022dce0fe6b6aa51b5b950d43c36985d --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/encparams.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f28f4c551fb36c4c387198d9258abf021c5d64e32b49dce4a8526defc4eb202 +size 135280 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/encparams.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/encparams.pxd new file mode 100644 index 0000000000000000000000000000000000000000..031b59a421978dfa21091ffa19d97dec91f1dec5 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/encparams.pxd @@ -0,0 +1,11 @@ +cimport libav as lib + +from av.sidedata.sidedata cimport SideData + + +cdef class VideoEncParams(SideData): + pass + + +cdef class VideoBlockParams: + cdef lib.AVVideoBlockParams *ptr diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/encparams.py b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/encparams.py new file mode 100644 index 0000000000000000000000000000000000000000..d0e78e8a126d20cabdb1970a9f3b6dc6b50089c6 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/encparams.py @@ -0,0 +1,186 @@ +from enum import IntEnum + +import cython +from cython.cimports import libav as lib +from cython.cimports.av.sidedata.sidedata import SideData +from cython.cimports.libc.stdint import uint8_t + + +class VideoEncParamsType(IntEnum): + NONE = lib.AV_VIDEO_ENC_PARAMS_NONE + VP9 = lib.AV_VIDEO_ENC_PARAMS_VP9 + H264 = lib.AV_VIDEO_ENC_PARAMS_H264 + MPEG2 = lib.AV_VIDEO_ENC_PARAMS_MPEG2 + + +@cython.final +@cython.cclass +class VideoEncParams(SideData): + def __repr__(self): + return f"" + + @property + def nb_blocks(self): + """ + Number of blocks in the array + May be 0, in which case no per-block information is present. In this case + the values of blocks_offset / block_size are unspecified and should not + be accessed. + """ + return cython.cast( + cython.pointer[lib.AVVideoEncParams], self.ptr.data + ).nb_blocks + + @property + def blocks_offset(self): + """ + Offset in bytes from the beginning of this structure at which the array of blocks starts. + """ + return cython.cast( + cython.pointer[lib.AVVideoEncParams], self.ptr.data + ).blocks_offset + + @property + def block_size(self): + """ + Size of each block in bytes. May not match sizeof(AVVideoBlockParams). + """ + return cython.cast( + cython.pointer[lib.AVVideoEncParams], self.ptr.data + ).block_size + + @property + def codec_type(self): + """ + Type of the parameters (the codec they are used with). + """ + t: lib.AVVideoEncParamsType = cython.cast( + cython.pointer[lib.AVVideoEncParams], self.ptr.data + ).type + return VideoEncParamsType(cython.cast(cython.int, t)) + + @property + def qp(self): + """ + Base quantisation parameter for the frame. The final quantiser for a + given block in a given plane is obtained from this value, possibly + combined with `delta_qp` and the per-block delta in a manner + documented for each type. + """ + return cython.cast(cython.pointer[lib.AVVideoEncParams], self.ptr.data).qp + + @property + def delta_qp(self): + """ + Quantisation parameter offset from the base (per-frame) qp for a given + plane (first index) and AC/DC coefficients (second index). + """ + p: cython.pointer[lib.AVVideoEncParams] = cython.cast( + cython.pointer[lib.AVVideoEncParams], self.ptr.data + ) + return [[p.delta_qp[i][j] for j in range(2)] for i in range(4)] + + def block_params(self, idx): + """ + Get the encoding parameters for a given block + """ + # Validate given index + if idx < 0 or idx >= self.nb_blocks: + raise ValueError("Expected idx in range [0, nb_blocks)") + + return VideoBlockParams(self, idx) + + def qp_map(self): + """ + Convenience method that creates a 2-D map with the quantization parameters per macroblock. + Only for MPEG2 and H264 encoded videos. + """ + import numpy as np + + mb_h: cython.int = (self.frame.ptr.height + 15) // 16 + mb_w: cython.int = (self.frame.ptr.width + 15) // 16 + nb_mb: cython.int = mb_h * mb_w + block_idx, x, y = cython.declare(cython.int) + block: VideoBlockParams + + if self.nb_blocks != nb_mb: + raise RuntimeError( + "Expected frame size to match number of blocks in side data" + ) + + type: lib.AVVideoEncParamsType = cython.cast( + cython.pointer[lib.AVVideoEncParams], self.ptr.data + ).type + if ( + type != lib.AVVideoEncParamsType.AV_VIDEO_ENC_PARAMS_MPEG2 + and type != lib.AVVideoEncParamsType.AV_VIDEO_ENC_PARAMS_H264 + ): + raise ValueError("Expected MPEG2 or H264") + + # Create a 2-D map with the number of macroblocks + map = np.empty((mb_h, mb_w), dtype=np.int32) + + # Fill map with quantization parameter per macroblock + for block_idx in range(nb_mb): + block = VideoBlockParams(self, block_idx) + y = block.src_y // 16 + x = block.src_x // 16 + map[y, x] = self.qp + block.delta_qp + + return map + + +@cython.final +@cython.cclass +class VideoBlockParams: + def __init__(self, video_enc_params: VideoEncParams, idx: cython.int) -> None: + base: cython.pointer[uint8_t] = cython.cast( + cython.pointer[uint8_t], video_enc_params.ptr.data + ) + offset: cython.Py_ssize_t = ( + video_enc_params.blocks_offset + idx * video_enc_params.block_size + ) + self.ptr = cython.cast(cython.pointer[lib.AVVideoBlockParams], base + offset) + + def __repr__(self): + return f"" + + @property + def src_x(self): + """ + Horizontal distance in luma pixels from the top-left corner of the visible frame + to the top-left corner of the block. + Can be negative if top/right padding is present on the coded frame. + """ + return self.ptr.src_x + + @property + def src_y(self): + """ + Vertical distance in luma pixels from the top-left corner of the visible frame + to the top-left corner of the block. + Can be negative if top/right padding is present on the coded frame. + """ + return self.ptr.src_y + + @property + def w(self): + """ + Width of the block in luma pixels + """ + return self.ptr.w + + @property + def h(self): + """ + Height of the block in luma pixels + """ + return self.ptr.h + + @property + def delta_qp(self): + """ + Difference between this block's final quantization parameter and the + corresponding per-frame value. + """ + return self.ptr.delta_qp diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/encparams.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/encparams.pyi new file mode 100644 index 0000000000000000000000000000000000000000..de962ba512a2056bc90e2944012ff50878305ded --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/encparams.pyi @@ -0,0 +1,27 @@ +from enum import IntEnum +from typing import Any, cast + +import numpy as np + +class VideoEncParamsType(IntEnum): + NONE = cast(int, ...) + VP9 = cast(int, ...) + H264 = cast(int, ...) + MPEG2 = cast(int, ...) + +class VideoEncParams: + nb_blocks: int + blocks_offset: int + block_size: int + codec_type: VideoEncParamsType + qp: int + delta_qp: int + def block_params(self, idx: int) -> VideoBlockParams: ... + def qp_map(self) -> np.ndarray[Any, Any]: ... + +class VideoBlockParams: + src_x: int + src_y: int + w: int + h: int + delta_qp: int diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/motionvectors.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/motionvectors.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..8ef3b5d8765e464f1a0dc2d8ed935aa5b5cd383d --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/motionvectors.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f66cd3bdd433ab6da018557d363d38e380fa62731eb05005a0391bcc24168eb +size 142240 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/motionvectors.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/motionvectors.pxd new file mode 100644 index 0000000000000000000000000000000000000000..0786483258c6c7c0c6810f68b58958c80ff7029c --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/motionvectors.pxd @@ -0,0 +1,14 @@ +cimport libav as lib + +from av.frame cimport Frame +from av.sidedata.sidedata cimport SideData + + +cdef class MotionVectors(SideData): + cdef dict _vectors + cdef Py_ssize_t _len + + +cdef class MotionVector: + cdef MotionVectors parent + cdef lib.AVMotionVector *ptr diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/motionvectors.py b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/motionvectors.py new file mode 100644 index 0000000000000000000000000000000000000000..369832ba6b607d87c2dfb1c01c5205c7c34a9acf --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/motionvectors.py @@ -0,0 +1,136 @@ +import cython +from cython.cimports import libav as lib +from cython.cimports.av.sidedata.sidedata import SideData +from cython.cimports.libc.stdint import uintptr_t + +_cinit_bypass_sentinel = cython.declare(object, object()) + + +@cython.final +@cython.cclass +class MotionVectors(SideData): + def __init__(self, sentinel, frame: Frame, index: cython.int): + SideData.__init__(self, sentinel, frame, index) + self._vectors = {} + self._len = self.ptr.size // cython.sizeof(lib.AVMotionVector) + + def __repr__(self): + return ( + f"" + ) + + def __len__(self): + return self._len + + def __getitem__(self, index: cython.Py_ssize_t): + try: + return self._vectors[index] + except KeyError: + pass + + if index >= self._len: + raise IndexError(index) + + vector = self._vectors[index] = MotionVector( + _cinit_bypass_sentinel, self, index + ) + return vector + + def __iter__(self): + """Iterate over all motion vectors.""" + for i in range(self._len): + yield self[i] + + def to_ndarray(self): + """ + Convert motion vectors to a NumPy structured array. + + Returns a NumPy array with fields corresponding to the AVMotionVector structure. + """ + import numpy as np + + return np.frombuffer( + self, + dtype=np.dtype( + [ + ("source", "int32"), + ("w", "uint8"), + ("h", "uint8"), + ("src_x", "int16"), + ("src_y", "int16"), + ("dst_x", "int16"), + ("dst_y", "int16"), + ("flags", "uint64"), + ("motion_x", "int32"), + ("motion_y", "int32"), + ("motion_scale", "uint16"), + ], + align=True, + ), + ) + + +@cython.final +@cython.cclass +class MotionVector: + """ + Represents a single motion vector from video frame data. + + Motion vectors describe the motion of a block of pixels between frames. + """ + + def __init__(self, sentinel, parent: MotionVectors, index: cython.int): + if sentinel is not _cinit_bypass_sentinel: + raise RuntimeError("cannot manually instantiate MotionVector") + self.parent = parent + base: cython.pointer[lib.AVMotionVector] = cython.cast( + cython.pointer[lib.AVMotionVector], parent.ptr.data + ) + self.ptr = base + index + + def __repr__(self): + return ( + f"" + ) + + @property + def source(self): + return self.ptr.source + + @property + def w(self): + return self.ptr.w + + @property + def h(self): + return self.ptr.h + + @property + def src_x(self): + return self.ptr.src_x + + @property + def src_y(self): + return self.ptr.src_y + + @property + def dst_x(self): + return self.ptr.dst_x + + @property + def dst_y(self): + return self.ptr.dst_y + + @property + def motion_x(self): + return self.ptr.motion_x + + @property + def motion_y(self): + return self.ptr.motion_y + + @property + def motion_scale(self): + return self.ptr.motion_scale diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/motionvectors.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/motionvectors.pyi new file mode 100644 index 0000000000000000000000000000000000000000..6b9971fb449b70a9494da6239fce25df1d24c965 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/motionvectors.pyi @@ -0,0 +1,25 @@ +from typing import Any, overload + +import numpy as np + +from .sidedata import SideData + +class MotionVectors(SideData): + @overload + def __getitem__(self, index: int) -> MotionVector: ... + @overload + def __getitem__(self, index: slice) -> list[MotionVector]: ... + def __len__(self) -> int: ... + def to_ndarray(self) -> np.ndarray[Any, Any]: ... + +class MotionVector: + source: int + w: int + h: int + src_x: int + src_y: int + dst_x: int + dst_y: int + motion_x: int + motion_y: int + motion_scale: int diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/sidedata.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/sidedata.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..1652af8d0f3cf6d62b9a5f78f184839cc3b0872f --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/sidedata.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c44f6a4061d52187643ca15b97c12e663993690f73b606c454e62abe263ef40a +size 137056 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/sidedata.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/sidedata.pxd new file mode 100644 index 0000000000000000000000000000000000000000..d97903c16df474b7570cb50292ef07ccdc6e02c0 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/sidedata.pxd @@ -0,0 +1,19 @@ +cimport libav as lib + +from av.buffer cimport Buffer +from av.dictionary cimport Dictionary, wrap_dictionary +from av.frame cimport Frame + + +cdef class SideData(Buffer): + cdef Frame frame + cdef lib.AVFrameSideData *ptr + cdef Dictionary metadata + +cdef SideData wrap_side_data(Frame frame, int index) +cdef int get_display_rotation(Frame frame) + +cdef class _SideDataContainer: + cdef Frame frame + cdef list _by_index + cdef dict _by_type diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/sidedata.py b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/sidedata.py new file mode 100644 index 0000000000000000000000000000000000000000..f19aeac06ae798d71a59073fe8b98c90a366310b --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/sidedata.py @@ -0,0 +1,135 @@ +from collections.abc import Mapping +from enum import Enum + +import cython +from cython.cimports.libc.stdint import int32_t, uintptr_t + +from av.sidedata.encparams import VideoEncParams +from av.sidedata.motionvectors import MotionVectors + +_cinit_bypass_sentinel = cython.declare(object, object()) + + +class Type(Enum): + """ + Enum class representing different types of frame data in audio/video processing. + Values are mapped to corresponding AV_FRAME_DATA constants from FFmpeg. + + From: https://github.com/FFmpeg/FFmpeg/blob/master/libavutil/frame.h + """ + + PANSCAN = lib.AV_FRAME_DATA_PANSCAN + A53_CC = lib.AV_FRAME_DATA_A53_CC + STEREO3D = lib.AV_FRAME_DATA_STEREO3D + MATRIXENCODING = lib.AV_FRAME_DATA_MATRIXENCODING + DOWNMIX_INFO = lib.AV_FRAME_DATA_DOWNMIX_INFO + REPLAYGAIN = lib.AV_FRAME_DATA_REPLAYGAIN + DISPLAYMATRIX = lib.AV_FRAME_DATA_DISPLAYMATRIX + AFD = lib.AV_FRAME_DATA_AFD + MOTION_VECTORS = lib.AV_FRAME_DATA_MOTION_VECTORS + SKIP_SAMPLES = lib.AV_FRAME_DATA_SKIP_SAMPLES + AUDIO_SERVICE_TYPE = lib.AV_FRAME_DATA_AUDIO_SERVICE_TYPE + MASTERING_DISPLAY_METADATA = lib.AV_FRAME_DATA_MASTERING_DISPLAY_METADATA + GOP_TIMECODE = lib.AV_FRAME_DATA_GOP_TIMECODE + SPHERICAL = lib.AV_FRAME_DATA_SPHERICAL + CONTENT_LIGHT_LEVEL = lib.AV_FRAME_DATA_CONTENT_LIGHT_LEVEL + ICC_PROFILE = lib.AV_FRAME_DATA_ICC_PROFILE + S12M_TIMECODE = lib.AV_FRAME_DATA_S12M_TIMECODE + DYNAMIC_HDR_PLUS = lib.AV_FRAME_DATA_DYNAMIC_HDR_PLUS + REGIONS_OF_INTEREST = lib.AV_FRAME_DATA_REGIONS_OF_INTEREST + VIDEO_ENC_PARAMS = lib.AV_FRAME_DATA_VIDEO_ENC_PARAMS + SEI_UNREGISTERED = lib.AV_FRAME_DATA_SEI_UNREGISTERED + FILM_GRAIN_PARAMS = lib.AV_FRAME_DATA_FILM_GRAIN_PARAMS + DETECTION_BBOXES = lib.AV_FRAME_DATA_DETECTION_BBOXES + DOVI_RPU_BUFFER = lib.AV_FRAME_DATA_DOVI_RPU_BUFFER + DOVI_METADATA = lib.AV_FRAME_DATA_DOVI_METADATA + DYNAMIC_HDR_VIVID = lib.AV_FRAME_DATA_DYNAMIC_HDR_VIVID + AMBIENT_VIEWING_ENVIRONMENT = lib.AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT + VIDEO_HINT = lib.AV_FRAME_DATA_VIDEO_HINT + + +@cython.cfunc +def wrap_side_data(frame: Frame, index: cython.int) -> SideData: + if frame.ptr.side_data[index].type == lib.AV_FRAME_DATA_MOTION_VECTORS: + return MotionVectors(_cinit_bypass_sentinel, frame, index) + elif frame.ptr.side_data[index].type == lib.AV_FRAME_DATA_VIDEO_ENC_PARAMS: + return VideoEncParams(_cinit_bypass_sentinel, frame, index) + else: + return SideData(_cinit_bypass_sentinel, frame, index) + + +@cython.cfunc +def get_display_rotation(frame: Frame) -> cython.int: + for i in range(frame.ptr.nb_side_data): + if frame.ptr.side_data[i].type == lib.AV_FRAME_DATA_DISPLAYMATRIX: + return int( + lib.av_display_rotation_get( + cython.cast( + cython.pointer[cython.const[int32_t]], + frame.ptr.side_data[i].data, + ) + ) + ) + return 0 + + +@cython.cclass +class SideData(Buffer): + def __init__(self, sentinel, frame: Frame, index: cython.int): + if sentinel is not _cinit_bypass_sentinel: + raise RuntimeError("cannot manually instatiate SideData") + self.frame = frame + self.ptr = frame.ptr.side_data[index] + self.metadata = wrap_dictionary(self.ptr.metadata) + + @cython.cfunc + def _buffer_size(self) -> cython.size_t: + return self.ptr.size + + @cython.cfunc + def _buffer_ptr(self) -> cython.p_void: + return self.ptr.data + + @cython.cfunc + def _buffer_writable(self) -> cython.bint: + return False + + def __repr__(self): + return f"" + + @property + def type(self): + return Type(self.ptr.type) + + +@cython.cclass +class _SideDataContainer: + def __init__(self, frame: Frame): + self.frame = frame + self._by_index: list = [] + self._by_type: dict = {} + + i: cython.Py_ssize_t + data: SideData + + for i in range(self.frame.ptr.nb_side_data): + data = wrap_side_data(frame, i) + self._by_index.append(data) + self._by_type[data.type] = data + + def __len__(self): + return len(self._by_index) + + def __iter__(self): + return iter(self._by_index) + + def __getitem__(self, key): + if isinstance(key, int): + return self._by_index[key] + if isinstance(key, str): + return self._by_type[Type[key]] + return self._by_type[key] + + +class SideDataContainer(_SideDataContainer, Mapping): + pass diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/sidedata.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/sidedata.pyi new file mode 100644 index 0000000000000000000000000000000000000000..0093fabd098d30c317e659c4b9dceb1067223965 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/sidedata/sidedata.pyi @@ -0,0 +1,52 @@ +from collections.abc import Mapping +from enum import Enum +from typing import ClassVar, Iterator, Sequence, cast, overload + +from av.buffer import Buffer +from av.frame import Frame + +class Type(Enum): + PANSCAN = cast(ClassVar[Type], ...) + A53_CC = cast(ClassVar[Type], ...) + STEREO3D = cast(ClassVar[Type], ...) + MATRIXENCODING = cast(ClassVar[Type], ...) + DOWNMIX_INFO = cast(ClassVar[Type], ...) + REPLAYGAIN = cast(ClassVar[Type], ...) + DISPLAYMATRIX = cast(ClassVar[Type], ...) + AFD = cast(ClassVar[Type], ...) + MOTION_VECTORS = cast(ClassVar[Type], ...) + SKIP_SAMPLES = cast(ClassVar[Type], ...) + AUDIO_SERVICE_TYPE = cast(ClassVar[Type], ...) + MASTERING_DISPLAY_METADATA = cast(ClassVar[Type], ...) + GOP_TIMECODE = cast(ClassVar[Type], ...) + SPHERICAL = cast(ClassVar[Type], ...) + CONTENT_LIGHT_LEVEL = cast(ClassVar[Type], ...) + ICC_PROFILE = cast(ClassVar[Type], ...) + S12M_TIMECODE = cast(ClassVar[Type], ...) + DYNAMIC_HDR_PLUS = cast(ClassVar[Type], ...) + REGIONS_OF_INTEREST = cast(ClassVar[Type], ...) + VIDEO_ENC_PARAMS = cast(ClassVar[Type], ...) + SEI_UNREGISTERED = cast(ClassVar[Type], ...) + FILM_GRAIN_PARAMS = cast(ClassVar[Type], ...) + DETECTION_BBOXES = cast(ClassVar[Type], ...) + DOVI_RPU_BUFFER = cast(ClassVar[Type], ...) + DOVI_METADATA = cast(ClassVar[Type], ...) + DYNAMIC_HDR_VIVID = cast(ClassVar[Type], ...) + AMBIENT_VIEWING_ENVIRONMENT = cast(ClassVar[Type], ...) + VIDEO_HINT = cast(ClassVar[Type], ...) + +class SideData(Buffer): + type: Type + +class SideDataContainer(Mapping): + frame: Frame + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[SideData]: ... + @overload + def __getitem__(self, key: str | int | Type) -> SideData: ... + @overload + def __getitem__(self, key: slice) -> Sequence[SideData]: ... + @overload + def __getitem__( + self, key: str | int | Type | slice + ) -> SideData | Sequence[SideData]: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/stream.cpython-310-darwin.so b/.venvs/kimodo/lib/python3.10/site-packages/av/stream.cpython-310-darwin.so new file mode 100644 index 0000000000000000000000000000000000000000..1041d2d76ace607c773039471e6331458ef0eec1 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/stream.cpython-310-darwin.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1cec4157f825fbc4f1e89f77a67e8fb4519df4daa3a23f70f5cb2f93f5038b8e +size 137712 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/stream.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/stream.pxd new file mode 100644 index 0000000000000000000000000000000000000000..89d41e559dc31a66564e49011d9ab7f5c2667592 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/stream.pxd @@ -0,0 +1,35 @@ +cimport libav as lib + +from av.codec.context cimport CodecContext +from av.container.core cimport Container +from av.frame cimport Frame +from av.index cimport IndexEntries +from av.packet cimport Packet + + +cdef class Stream: + cdef lib.AVStream *ptr + + # Stream attributes. + cdef readonly Container container + cdef readonly dict metadata + + # CodecContext attributes. + cdef readonly CodecContext codec_context + + cdef readonly IndexEntries index_entries + + # Private API. + cdef _init(self, Container, lib.AVStream*, CodecContext) + cdef _finalize_for_output(self) + cdef _set_id(self, value) + + +cdef Stream wrap_stream(Container, lib.AVStream*, CodecContext) + + +cdef class DataStream(Stream): + pass + +cdef class AttachmentStream(Stream): + pass diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/stream.py b/.venvs/kimodo/lib/python3.10/site-packages/av/stream.py new file mode 100644 index 0000000000000000000000000000000000000000..f96030e266404f5da817345aa048b13299c34d79 --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/stream.py @@ -0,0 +1,365 @@ +from enum import IntEnum, IntFlag + +import cython +from cython.cimports import libav as lib +from cython.cimports.av.error import err_check +from cython.cimports.av.index import wrap_index_entries +from cython.cimports.av.utils import ( + avdict_to_dict, + avrational_to_fraction, + dict_to_avdict, + to_avrational, +) + + +class Disposition(IntFlag): + default = 1 << 0 + dub = 1 << 1 + original = 1 << 2 + comment = 1 << 3 + lyrics = 1 << 4 + karaoke = 1 << 5 + forced = 1 << 6 + hearing_impaired = 1 << 7 + visual_impaired = 1 << 8 + clean_effects = 1 << 9 + attached_pic = 1 << 10 + timed_thumbnails = 1 << 11 + non_diegetic = 1 << 12 + captions = 1 << 16 + descriptions = 1 << 17 + metadata = 1 << 18 + dependent = 1 << 19 + still_image = 1 << 20 + multilayer = 1 << 21 + + +class Discard(IntEnum): + none = lib.AVDISCARD_NONE + default = lib.AVDISCARD_DEFAULT + nonref = lib.AVDISCARD_NONREF + bidir = lib.AVDISCARD_BIDIR + nonintra = lib.AVDISCARD_NONINTRA + nonkey = lib.AVDISCARD_NONKEY + all = lib.AVDISCARD_ALL + + +_cinit_bypass_sentinel = cython.declare(object, object()) + + +@cython.cfunc +def wrap_stream( + container: Container, + c_stream: cython.pointer[lib.AVStream], + codec_context: CodecContext, +) -> Stream: + """Build an av.Stream for an existing AVStream. + + The AVStream MUST be fully constructed and ready for use before this is called. + """ + + # This better be the right one... + assert container.ptr.streams[c_stream.index] == c_stream + + py_stream: Stream + + from av.audio.stream import AudioStream + from av.subtitles.stream import SubtitleStream + from av.video.stream import VideoStream + + if c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_VIDEO: + py_stream = VideoStream.__new__(VideoStream, _cinit_bypass_sentinel) + elif c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_AUDIO: + py_stream = AudioStream.__new__(AudioStream, _cinit_bypass_sentinel) + elif c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_SUBTITLE: + py_stream = SubtitleStream.__new__(SubtitleStream, _cinit_bypass_sentinel) + elif c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_ATTACHMENT: + py_stream = AttachmentStream.__new__(AttachmentStream, _cinit_bypass_sentinel) + elif c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_DATA: + py_stream = DataStream.__new__(DataStream, _cinit_bypass_sentinel) + else: + py_stream = Stream.__new__(Stream, _cinit_bypass_sentinel) + + py_stream._init(container, c_stream, codec_context) + return py_stream + + +@cython.cclass +class Stream: + """ + A single stream of audio, video or subtitles within a :class:`.Container`. + + :: + + >>> fh = av.open(video_path) + >>> stream = fh.streams.video[0] + >>> stream + + + This encapsulates a :class:`.CodecContext`, located at :attr:`Stream.codec_context`. + Attribute access is passed through to that context when attributes are missing + on the stream itself. E.g. ``stream.options`` will be the options on the + context. + """ + + def __cinit__(self, name): + if name is _cinit_bypass_sentinel: + return + raise RuntimeError("cannot manually instantiate Stream") + + @cython.cfunc + def _init( + self, + container: Container, + stream: cython.pointer[lib.AVStream], + codec_context: CodecContext, + ): + self.container = container + self.ptr = stream + self.index_entries = wrap_index_entries(self.ptr) + + self.codec_context = codec_context + if self.codec_context: + self.codec_context.stream_index = stream.index + + self.metadata = avdict_to_dict( + stream.metadata, + encoding=self.container.metadata_encoding, + errors=self.container.metadata_errors, + ) + + def __repr__(self): + name = getattr(self, "name", None) + return ( + f"'}/" + f"{name or ''} at 0x{id(self):x}>" + ) + + def __setattr__(self, name, value): + if name == "id": + self._set_id(value) + return + if name == "disposition": + self.ptr.disposition = value + return + if name == "discard": + self.ptr.discard = Discard(value).value + return + if name == "time_base": + to_avrational(value, cython.address(self.ptr.time_base)) + return + + # Convenience setter for codec context properties. + if self.codec_context is not None: + setattr(self.codec_context, name, value) + + @cython.cfunc + def _finalize_for_output(self): + dict_to_avdict( + cython.address(self.ptr.metadata), + self.metadata, + encoding=self.container.metadata_encoding, + errors=self.container.metadata_errors, + ) + + if self.codec_context is None: + return + + if not self.ptr.time_base.num: + self.ptr.time_base = self.codec_context.ptr.time_base + + # It prefers if we pass it parameters via this other object. Let's just copy what we want. + err_check( + lib.avcodec_parameters_from_context( + self.ptr.codecpar, self.codec_context.ptr + ) + ) + + @property + def id(self): + """ + The format-specific ID of this stream. + + :type: int + + """ + return self.ptr.id + + @cython.cfunc + def _set_id(self, value): + if value is None: + self.ptr.id = 0 + else: + self.ptr.id = value + + @property + def profiles(self): + """ + List the available profiles for this stream. + + :type: list[str] + """ + if self.codec_context: + return self.codec_context.profiles + else: + return [] + + @property + def profile(self): + """ + The profile of this stream. + + :type: str + """ + if self.codec_context: + return self.codec_context.profile + else: + return None + + @property + def index(self): + """ + The index of this stream in its :class:`.Container`. + + :type: int + """ + return self.ptr.index + + @property + def time_base(self): + """ + The unit of time (in fractional seconds) in which timestamps are expressed. + + :type: fractions.Fraction | None + + """ + return avrational_to_fraction(cython.address(self.ptr.time_base)) + + @property + def start_time(self): + """ + The presentation timestamp in :attr:`time_base` units of the first + frame in this stream. + + :type: int | None + """ + if self.ptr.start_time != lib.AV_NOPTS_VALUE: + return self.ptr.start_time + + @property + def duration(self): + """ + The duration of this stream in :attr:`time_base` units. + + :type: int | None + + """ + if self.ptr.duration != lib.AV_NOPTS_VALUE: + return self.ptr.duration + + @property + def frames(self): + """ + The number of frames this stream contains. + + Returns ``0`` if it is not known. + + :type: int + """ + return self.ptr.nb_frames + + @property + def language(self): + """ + The language of the stream. + + :type: str | None + """ + return self.metadata.get("language") + + @property + def disposition(self): + return Disposition(self.ptr.disposition) + + @property + def discard(self): + """ + Controls which packets of this stream are discarded by the demuxer. + + Set this to e.g. :attr:`Discard.all` on streams you don't need so that + :meth:`.Container.demux` and :meth:`.Container.seek` skip them, avoiding + the cost of synchronizing streams you never read. + + :type: Discard + """ + return Discard(self.ptr.discard) + + @property + def type(self): + """ + The type of the stream. + + :type: Literal["audio", "video", "subtitle", "data", "attachment"] + """ + media_type = lib.av_get_media_type_string(self.ptr.codecpar.codec_type) + return "unknown" if media_type == cython.NULL else media_type + + +@cython.final +@cython.cclass +class DataStream(Stream): + def __repr__(self): + return ( + f"'} at 0x{id(self):x}>" + ) + + @property + def name(self): + desc: cython.pointer[cython.const[lib.AVCodecDescriptor]] = ( + lib.avcodec_descriptor_get(self.ptr.codecpar.codec_id) + ) + if desc == cython.NULL: + return None + return desc.name + + +@cython.final +@cython.cclass +class AttachmentStream(Stream): + """ + An :class:`AttachmentStream` represents a stream of attachment data within a media container. + Typically used to attach font files that are referenced in ASS/SSA Subtitle Streams. + """ + + @property + def name(self): + """ + Returns the file name of the attachment. + + :rtype: str | None + """ + return self.metadata.get("filename") + + @property + def mimetype(self): + """ + Returns the MIME type of the attachment. + + :rtype: str | None + """ + return self.metadata.get("mimetype") + + @property + def data(self): + """Return the raw attachment payload as bytes.""" + extradata: cython.p_uchar = self.ptr.codecpar.extradata + size: cython.Py_ssize_t = self.ptr.codecpar.extradata_size + if extradata == cython.NULL or size <= 0: + return b"" + + payload = bytearray(size) + for i in range(size): + payload[i] = extradata[i] + + return bytes(payload) diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/stream.pyi b/.venvs/kimodo/lib/python3.10/site-packages/av/stream.pyi new file mode 100644 index 0000000000000000000000000000000000000000..f9148021fa905bb6be770918a16fae638bffe8df --- /dev/null +++ b/.venvs/kimodo/lib/python3.10/site-packages/av/stream.pyi @@ -0,0 +1,75 @@ +from enum import IntEnum, IntFlag +from fractions import Fraction +from typing import Literal, cast + +from .codec import Codec, CodecContext +from .container import Container +from .index import IndexEntries + +class Disposition(IntFlag): + default = cast(int, ...) + dub = cast(int, ...) + original = cast(int, ...) + comment = cast(int, ...) + lyrics = cast(int, ...) + karaoke = cast(int, ...) + forced = cast(int, ...) + hearing_impaired = cast(int, ...) + visual_impaired = cast(int, ...) + clean_effects = cast(int, ...) + attached_pic = cast(int, ...) + timed_thumbnails = cast(int, ...) + non_diegetic = cast(int, ...) + captions = cast(int, ...) + descriptions = cast(int, ...) + metadata = cast(int, ...) + dependent = cast(int, ...) + still_image = cast(int, ...) + multilayer = cast(int, ...) + +class Discard(IntEnum): + none = cast(int, ...) + default = cast(int, ...) + nonref = cast(int, ...) + bidir = cast(int, ...) + nonintra = cast(int, ...) + nonkey = cast(int, ...) + all = cast(int, ...) + +class Stream: + name: str | None + container: Container + codec: Codec + codec_context: CodecContext + metadata: dict[str, str] + index_entries: IndexEntries + id: int + profiles: list[str] + profile: str | None + index: int + options: dict[str, object] + time_base: Fraction | None + average_rate: Fraction | None + base_rate: Fraction | None + guessed_rate: Fraction | None + start_time: int | None + duration: int | None + disposition: Disposition + discard: Discard + frames: int + language: str | None + type: Literal["video", "audio", "data", "subtitle", "attachment", "unknown"] + + # From context + codec_tag: str + +class DataStream(Stream): + type: Literal["data"] + name: str | None + +class AttachmentStream(Stream): + type: Literal["attachment"] + @property + def mimetype(self) -> str | None: ... + @property + def data(self) -> bytes: ... diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/subtitles/__init__.pxd b/.venvs/kimodo/lib/python3.10/site-packages/av/subtitles/__init__.pxd new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venvs/kimodo/lib/python3.10/site-packages/av/subtitles/__init__.py b/.venvs/kimodo/lib/python3.10/site-packages/av/subtitles/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391