diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..d7b97b3a7 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Line endings normalization +fd7c7e1cbd8e1d7bdb2ec2423c3cf9f95a3abed1 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..47db1e621 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/ARM.yml b/.github/workflows/ARM.yml deleted file mode 100644 index af257bcb8..000000000 --- a/.github/workflows/ARM.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Main (ARM) - -on: - push: - branches: - - master - pull_request: - -jobs: - build-test-arm: - name: Build and Test ARM64 - runs-on: [self-hosted, linux, ARM64] - timeout-minutes: 15 - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: '6.0.x' - - - name: Clean previous install - run: | - pip uninstall -y pythonnet - - - name: Install dependencies - run: | - pip install --upgrade -r requirements.txt - pip install pytest numpy # for tests - - - name: Build and Install - run: | - pip install -v . - - - name: Set Python DLL path (non Windows) - run: | - echo PYTHONNET_PYDLL=$(python -m find_libpython) >> $GITHUB_ENV - - - name: Embedding tests - run: dotnet test --logger "console;verbosity=detailed" src/embed_tests/ - - - name: Python Tests (Mono) - run: python -m pytest --runtime mono - - - name: Python Tests (.NET Core) - run: python -m pytest --runtime coreclr - - - name: Python tests run from .NET - run: dotnet test src/python_tests_runner/ - - #- name: Perf tests - # run: | - # pip install --force --no-deps --target src/perf_tests/baseline/ pythonnet==2.5.2 - # dotnet test --configuration Release --logger "console;verbosity=detailed" src/perf_tests/ diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5b782c8b4..e892009f2 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,9 +6,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v6 - name: Doxygen Action - uses: mattnotmitt/doxygen-action@1.9.4 + uses: mattnotmitt/doxygen-action@1.12.0 with: working-directory: "doc/" @@ -19,7 +19,7 @@ jobs: - name: Upload artifact # Automatically uploads an artifact from the './_site' directory by default - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v4 with: path: doc/build/html/ @@ -37,4 +37,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2cc793621..8ead06fba 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,86 +9,116 @@ on: jobs: build-test: name: Build and Test - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os.instance }} timeout-minutes: 15 strategy: fail-fast: false matrix: - os: [windows, ubuntu, macos] - python: ["3.7", "3.8", "3.9", "3.10"] - platform: [x64, x86] - exclude: - - os: ubuntu - platform: x86 - - os: macos + os: + - category: windows platform: x86 + instance: windows-latest + suffix: -windows-x86-none - steps: - - name: Set Environment on macOS - uses: maxim-lobanov/setup-xamarin@v1 - if: ${{ matrix.os == 'macos' }} - with: - mono-version: latest + - category: windows + platform: x64 + instance: windows-latest + suffix: -windows-x86_64-none + + - category: ubuntu + platform: x64 + instance: ubuntu-22.04 + suffix: "" + + - category: ubuntu + platform: arm64 + instance: ubuntu-22.04-arm + suffix: "" + - category: macos + platform: x64 + instance: macos-15 + suffix: -macos-x86_64-none + + - category: macos + platform: arm64 + instance: macos-15 + suffix: -macos-aarch64-none + + python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + exclude: + # Fails with initfs_encoding error + - os: + category: windows + platform: x86 + python: "3.10" + + # Fails to find pytest + - os: + category: windows + platform: x64 + python: '3.10' + + # fails to call mono methods + - os: + category: windows + platform: x86 + python: '3.13' + + env: + NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages + steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v6 + # Use main until support for architecture lands - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@main with: - dotnet-version: '6.0.x' + dotnet-version: '10.0' + architecture: ${{ matrix.os.platform }} + + - run: dotnet restore + + - name: Install Mono + uses: pythonnet/clr-loader/.github/actions/install-mono@main + with: + arch: ${{ matrix.os.platform }} - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2 + uses: astral-sh/setup-uv@v7 with: - python-version: ${{ matrix.python }} - architecture: ${{ matrix.platform }} - - - name: Install dependencies - run: | - pip install --upgrade -r requirements.txt - pip install numpy # for tests - - - name: Build and Install - run: | - pip install -v . - - - name: Set Python DLL path (non Windows) - if: ${{ matrix.os != 'windows' }} - run: | - echo PYTHONNET_PYDLL=$(python -m find_libpython) >> $GITHUB_ENV - - - name: Set Python DLL path (Windows) - if: ${{ matrix.os == 'windows' }} - run: | - Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append -InputObject "PYTHONNET_PYDLL=$(python -m find_libpython)" - - - name: Embedding tests - run: dotnet test --runtime any-${{ matrix.platform }} --logger "console;verbosity=detailed" src/embed_tests/ + python-version: cpython-${{ matrix.python }}${{ matrix.os.suffix }} + cache-python: true + activate-environment: true + enable-cache: true + + - name: Synchronize the virtual environment + run: uv sync --managed-python + + - name: Embedding tests (Mono/.NET Framework) + if: always() + run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net472 --logger "console;verbosity=detailed" src/embed_tests/ env: MONO_THREADS_SUSPEND: preemptive # https://github.com/mono/mono/issues/21466 - - name: Python Tests (Mono) - if: ${{ matrix.os != 'windows' }} - run: pytest --runtime mono + - name: Embedding tests (.NET Core) + if: always() + run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net10.0 --logger "console;verbosity=detailed" src/embed_tests/ - # TODO: Run these tests on Windows x86 - name: Python Tests (.NET Core) - if: ${{ matrix.platform == 'x64' }} + if: always() run: pytest --runtime coreclr + - name: Python Tests (Mono) + if: always() + run: pytest --runtime mono + - name: Python Tests (.NET Framework) - if: ${{ matrix.os == 'windows' }} + if: ${{ matrix.os.category == 'windows' }} run: pytest --runtime netfx - name: Python tests run from .NET - run: dotnet test --runtime any-${{ matrix.platform }} src/python_tests_runner/ - - - name: Perf tests - if: ${{ (matrix.python == '3.8') && (matrix.platform == 'x64') }} - run: | - pip install --force --no-deps --target src/perf_tests/baseline/ pythonnet==2.5.2 - dotnet test --configuration Release --runtime any-${{ matrix.platform }} --logger "console;verbosity=detailed" src/perf_tests/ - - # TODO: Run mono tests on Windows? + run: dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/ diff --git a/.github/workflows/nuget-preview.yml b/.github/workflows/nuget-preview.yml index d652f4b1e..6de97d50e 100644 --- a/.github/workflows/nuget-preview.yml +++ b/.github/workflows/nuget-preview.yml @@ -21,15 +21,15 @@ jobs: echo "DATE_VER=$(date "+%Y-%m-%d")" >> $GITHUB_ENV - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v6 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v5 with: dotnet-version: '6.0.x' - name: Set up Python 3.8 - uses: actions/setup-python@v2 + uses: actions/setup-python@v6 with: python-version: 3.8 architecture: x64 diff --git a/AUTHORS.md b/AUTHORS.md index 92f1a4a97..7ea639059 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -38,6 +38,7 @@ - Dmitriy Se ([@dmitriyse](https://github.com/dmitriyse)) - Félix Bourbonnais ([@BadSingleton](https://github.com/BadSingleton)) - Florian Treurniet ([@ftreurni](https://github.com/ftreurni)) +- Frank Witscher ([@Frawak](https://github.com/Frawak)) - He-chien Tsai ([@t3476](https://github.com/t3476)) - Inna Wiesel ([@inna-w](https://github.com/inna-w)) - Ivan Cronyn ([@cronan](https://github.com/cronan)) @@ -58,6 +59,7 @@ - Peter Kese ([@pkese](https://github.com/pkese)) - Raphael Nestler ([@rnestler](https://github.com/rnestler)) - Rickard Holmberg ([@rickardraysearch](https://github.com/rickardraysearch)) +- Roberto Pastor Muela ([@RobPasMue](https://github.com/RobPasMue)) - Sam Winstanley ([@swinstanley](https://github.com/swinstanley)) - Sean Freitag ([@cowboygneox](https://github.com/cowboygneox)) - Serge Weinstock ([@sweinst](https://github.com/sweinst)) @@ -85,3 +87,5 @@ - ([@alxnull](https://github.com/alxnull)) - ([@gpetrou](https://github.com/gpetrou)) - Ehsan Iran-Nejad ([@eirannejad](https://github.com/eirannejad)) +- ([@legomanww](https://github.com/legomanww)) +- ([@gertdreyer](https://github.com/gertdreyer)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d33ee327..df68fbb39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,14 +5,87 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. -## [Unreleased][] +## Unreleased ### Added +- Support `del obj[...]` for types derived from `IList` and `IDictionary` + ### Changed +### Fixed + +- Fixed crash when trying to `del clrObj[...]` for non-arrays +- ci: properly exclude job (#2542) + +## [3.0.5](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.5) - 2024-12-13 + +### Added + +- Support for Python 3.13 (#2454) + + +## [3.0.4](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.4) - 2024-09-19 + +### Added + +- Added `ToPythonAs()` extension method to allow for explicit conversion + using a specific type. ([#2311][i2311]) +- Added `IComparable` and `IEquatable` implementations to `PyInt`, `PyFloat`, + and `PyString` to compare with primitive .NET types like `long`. + +### Changed + +- Added a `FormatterFactory` member in RuntimeData to create formatters with + parameters. For compatibility, the `FormatterType` member is still present + and has precedence when defining both `FormatterFactory` and `FormatterType` +- Added a post-serialization and a pre-deserialization step callbacks to + extend (de)serialization process +- Added an API to stash serialized data on Python capsules ### Fixed +- Fixed RecursionError for reverse operators on C# operable types from python. See #2240 +- Fixed crash when .NET event has no `AddMethod` +- Fixed probing for assemblies in `sys.path` failing when a path in `sys.path` + has invalid characters. See #2376 +- Fixed possible access violation exception on shutdown. See ([#1977][i1977]) + +## [3.0.3](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.3) - 2023-10-11 + +### Added + +- Support for Python 3.12 + +### Changed + +- Use enum name in `repr` + +## [3.0.2](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.2) - 2023-08-29 + +### Fixed + +- Fixed error occuring when inheriting a class containing a virtual generic method +- Make a second call to `pythonnet.load` a no-op, as it was intended +- Added support for multiple inheritance when inheriting from a class and/or multiple interfaces +- Fixed error occuring when calling `GetBuffer` for anything other than `PyBUF.SIMPLE` +- Bumped `clr_loader` dependency to incorporate patches + +## [3.0.1](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.1) - 2022-11-03 + +### Added + +- Support for Python 3.11 + +### Changed + +- Allow decoders to override conversion of types derived from primitive types + +### Fixed + +- Fixed objects leaking when Python attached event handlers to them even if they were later removed +- Fixed `PyInt` conversion to `BigInteger` and `System.String` produced incorrect result for values between 128 and 255. +- Fixed implementing a generic interface with a Python class + ## [3.0.0](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.0) - 2022-09-29 @@ -805,7 +878,7 @@ This version improves performance on benchmarks significantly compared to 2.3. [semantic versioning]: http://semver.org/ -[unreleased]: ../../compare/v2.3.0...HEAD +[unreleased]: ../../compare/v3.0.1...HEAD [2.3.0]: ../../compare/v2.2.2...v2.3.0 @@ -922,3 +995,5 @@ This version improves performance on benchmarks significantly compared to 2.3. [i238]: https://github.com/pythonnet/pythonnet/issues/238 [i1481]: https://github.com/pythonnet/pythonnet/issues/1481 [i1672]: https://github.com/pythonnet/pythonnet/pull/1672 +[i2311]: https://github.com/pythonnet/pythonnet/issues/2311 +[i1977]: https://github.com/pythonnet/pythonnet/issues/1977 diff --git a/Directory.Build.props b/Directory.Build.props index e45c16f6a..9b6f9555a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,24 +1,25 @@ - Copyright (c) 2006-2022 The Contributors of the Python.NET Project + Copyright (c) 2006-2025 The Contributors of the Python.NET Project pythonnet Python.NET - 10.0 + 12.0 false $([System.IO.File]::ReadAllText("$(MSBuildThisFileDirectory)version.txt").Trim()) $(FullVersion.Split('-', 2)[0]) $(FullVersion.Split('-', 2)[1]) + $(MSBuildThisFileDirectory) - - + + all runtime; build; native; contentfiles; analyzers - + diff --git a/doc/make.bat b/doc/make.bat index 747ffb7b3..dc1312ab0 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -1,35 +1,35 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/doc/requirements.txt b/doc/requirements.txt index 64018840c..8ef3b7159 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -7,3 +7,6 @@ pygments>=2.7 # C# via doxygen breathe git+https://github.com/rogerbarton/sphinx-csharp.git + +# Dependency of pythonnet, needed for autodocs +clr_loader diff --git a/doc/source/dotnet.rst b/doc/source/dotnet.rst index d02a6f0cb..ed2b741a7 100644 --- a/doc/source/dotnet.rst +++ b/doc/source/dotnet.rst @@ -17,7 +17,7 @@ to: - Reference ``Python.Runtime.dll`` (e.g. via a ``PackageReference``) - Call ``PythonEngine.Initialize()`` to initialize Python -- Call ``PythonEngine.ImportModule(name)`` to import a module +- Call ``var mod = PyModule.Import(name)`` to import a module as ``mod`` The module you import can either start working with your managed app environment at the time its imported, or you can explicitly lookup and @@ -42,16 +42,10 @@ application. Before interacting with any of the objects or APIs provided by the ``Python.Runtime`` namespace, calling code must have acquired the Python -global interpreter lock by calling the ``PythonEngine.AcquireLock`` -method. The only exception to this rule is the -``PythonEngine.Initialize`` method, which may be called at startup -without having acquired the GIL. - -When finished using Python APIs, managed code must call a corresponding -``PythonEngine.ReleaseLock`` to release the GIL and allow other threads -to use Python. - -A ``using`` statement may be used to acquire and release the GIL: +global interpreter lock by ``using'' ``Py.GIL()``. The only exception to +this rule is the ``PythonEngine.Initialize`` method, which may be called +at startup without having acquired the GIL. The GIL is released again +by disposing the return value of `Py.GIL()`: .. code:: csharp @@ -59,11 +53,28 @@ A ``using`` statement may be used to acquire and release the GIL: { PythonEngine.Exec("doStuff()"); } + + // or + { + using var _ = Py.GIL() + PythonEngine.Exec("doStuff()"); + } + + // or + var gil = Py.GIL(); + try + { + PythonEngine.Exec("doStuff()"); + } + finally + { + gil.Dispose(); + } -The AcquireLock and ReleaseLock methods are thin wrappers over the -unmanaged ``PyGILState_Ensure`` and ``PyGILState_Release`` functions -from the Python API, and the documentation for those APIs applies to the -managed versions. +The ``Py.GIL()'' object is a thin wrapper over the unmanaged +``PyGILState_Ensure`` (on construction) and ``PyGILState_Release`` (on +disposal) functions from the Python API, and the documentation for those +APIs applies to the managed versions. Passing C# Objects to the Python Engine --------------------------------------- @@ -99,7 +110,7 @@ Code executed from the scope will have access to the variable: using (Py.GIL()) { // create a Python scope - using (PyScope scope = Py.CreateScope()) + using (PyModule scope = Py.CreateScope()) { // convert the Person object to a PyObject PyObject pyPerson = person.ToPython(); diff --git a/pyproject.toml b/pyproject.toml index 91f386fc6..be33a6afb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,30 +1,30 @@ [build-system] -requires = ["setuptools>=61", "wheel"] +requires = ["setuptools>=80"] build-backend = "setuptools.build_meta" [project] name = "pythonnet" description = ".NET and Mono integration for Python" -license = {text = "MIT"} +license = "MIT" readme = "README.rst" dependencies = [ - "clr_loader>=0.2.2,<0.3.0" + "clr_loader>=0.3.1,<0.4.0" ] -requires-python = ">=3.7, <3.11" +requires-python = ">=3.10, <3.15" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", "Programming Language :: C#", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Operating System :: MacOS :: MacOS X", @@ -32,6 +32,14 @@ classifiers = [ dynamic = ["version"] +[dependency-groups] +dev = [ + "pytest >= 6", + "find_libpython >= 0.3", + "numpy >=2 ; python_version >= '3.10'", + "numpy <2 ; python_version < '3.10'", +] + [[project.authors]] name = "The Contributors of the Python.NET Project" email = "pythonnet@python.org" @@ -49,6 +57,7 @@ file = "version.txt" [tool.setuptools.packages.find] include = ["pythonnet*"] +exclude = [".gitignore"] [tool.pytest.ini_options] xfail_strict = true diff --git a/pythonnet.sln b/pythonnet.sln index d1a47892e..9dfeb44b1 100644 --- a/pythonnet.sln +++ b/pythonnet.sln @@ -4,14 +4,10 @@ VisualStudioVersion = 17.0.31912.275 MinimumVisualStudioVersion = 15.0.26124.0 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.Runtime", "src\runtime\Python.Runtime.csproj", "{4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Console", "src\console\Console.csproj", "{E6B01706-00BA-4144-9029-186AC42FBE9A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.EmbeddingTest", "src\embed_tests\Python.EmbeddingTest.csproj", "{819E089B-4770-400E-93C6-4F7A35F0EA12}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.Test", "src\testing\Python.Test.csproj", "{14EF9518-5BB7-4F83-8686-015BD2CC788E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.PerformanceTests", "src\perf_tests\Python.PerformanceTests.csproj", "{4F2EA4A1-7ECA-48B5-8077-7A3C366F9931}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.DomainReloadTests", "tests\domain_tests\Python.DomainReloadTests.csproj", "{F2FB6DA3-318E-4F30-9A1F-932C667E38C5}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F4C6-4EE4-9AEE-315FD79BE2D5}" @@ -22,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F LICENSE = LICENSE README.rst = README.rst version.txt = version.txt + shell.nix = shell.nix EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{D301657F-5EAF-4534-B280-B858D651B2E5}" diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py index 9d7b6c20a..5c1ca108a 100644 --- a/pythonnet/__init__.py +++ b/pythonnet/__init__.py @@ -120,7 +120,9 @@ def load(runtime: Union[clr_loader.Runtime, str, None] = None, **params: str) -> The same parameters as for `set_runtime` can be used. By default, `set_default_runtime` is called if no environment has been set yet and no - parameters are passed.""" + parameters are passed. + + After a successful call, further invocations will return immediately.""" global _LOADED, _LOADER_ASSEMBLY if _LOADED: @@ -142,6 +144,8 @@ def load(runtime: Union[clr_loader.Runtime, str, None] = None, **params: str) -> if func(b"") != 0: raise RuntimeError("Failed to initialize Python.Runtime.dll") + + _LOADED = True import atexit diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 8e911ef5a..000000000 --- a/requirements.txt +++ /dev/null @@ -1,15 +0,0 @@ -# Requirements for both Travis and AppVeyor -pytest -psutil - -# Coverage upload -coverage -codecov - -wheel -pycparser -setuptools -clr-loader - -# Discover libpython -find_libpython \ No newline at end of file diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..fe653deb7 --- /dev/null +++ b/shell.nix @@ -0,0 +1,10 @@ +{ pkgs ? import {}}: +let + fhs = pkgs.buildFHSUserEnv { + name = "my-fhs-environment"; + + targetPkgs = _: [ + pkgs.python3 + ]; + }; +in fhs.env diff --git a/src/console/Console.csproj b/src/console/Console.csproj deleted file mode 100644 index bcbc1292b..000000000 --- a/src/console/Console.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - net472;net6.0 - x64;x86 - Exe - nPython - Python.Runtime - nPython - python-clear.ico - - - - - - Python.Runtime.dll - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - diff --git a/src/console/python-clear.ico b/src/console/python-clear.ico deleted file mode 100644 index b37050cce..000000000 Binary files a/src/console/python-clear.ico and /dev/null differ diff --git a/src/console/python-clear.png b/src/console/python-clear.png deleted file mode 100644 index d67b5b8d4..000000000 Binary files a/src/console/python-clear.png and /dev/null differ diff --git a/src/console/pythonconsole.cs b/src/console/pythonconsole.cs deleted file mode 100644 index bf17848f7..000000000 --- a/src/console/pythonconsole.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using Python.Runtime; - -namespace Python.Runtime -{ - /// - /// Example of Embedding Python inside of a .NET program. - /// - /// - /// It has similar functionality to doing `import clr` from within Python, but this does it - /// the other way around; That is, it loads Python inside of .NET program. - /// See https://github.com/pythonnet/pythonnet/issues/358 for more info. - /// - public sealed class PythonConsole - { -#if NET40 - private static AssemblyLoader assemblyLoader = new AssemblyLoader(); -#endif - private PythonConsole() - { - } - - [STAThread] - public static int Main(string[] args) - { - // Only .NET Framework is capable to safely inject python.runtime.dll into resources. -#if NET40 - // reference the static assemblyLoader to stop it being optimized away - AssemblyLoader a = assemblyLoader; -#endif - string[] cmd = Environment.GetCommandLineArgs(); - PythonEngine.Initialize(); - - int i = Runtime.Py_Main(cmd.Length, cmd); - PythonEngine.Shutdown(); - - return i; - } - -#if NET40 - // Register a callback function to load embedded assemblies. - // (Python.Runtime.dll is included as a resource) - private sealed class AssemblyLoader - { - private Dictionary loadedAssemblies; - - public AssemblyLoader() - { - loadedAssemblies = new Dictionary(); - - AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => - { - string shortName = args.Name.Split(',')[0]; - string resourceName = $"{shortName}.dll"; - - if (loadedAssemblies.ContainsKey(resourceName)) - { - return loadedAssemblies[resourceName]; - } - - // looks for the assembly from the resources and load it - using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) - { - if (stream != null) - { - var assemblyData = new byte[stream.Length]; - stream.Read(assemblyData, 0, assemblyData.Length); - Assembly assembly = Assembly.Load(assemblyData); - loadedAssemblies[resourceName] = assembly; - return assembly; - } - } - return null; - }; - } - } -#endif - } -} diff --git a/src/embed_tests/CallableObject.cs b/src/embed_tests/CallableObject.cs index 8466f5ad8..d450598d2 100644 --- a/src/embed_tests/CallableObject.cs +++ b/src/embed_tests/CallableObject.cs @@ -9,34 +9,37 @@ namespace Python.EmbeddingTest { public class CallableObject { + IPythonBaseTypeProvider BaseTypeProvider; + [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); using var locals = new PyDict(); PythonEngine.Exec(CallViaInheritance.BaseClassSource, locals: locals); - CustomBaseTypeProvider.BaseClass = new PyType(locals[CallViaInheritance.BaseClassName]); - PythonEngine.InteropConfiguration.PythonBaseTypeProviders.Add(new CustomBaseTypeProvider()); + BaseTypeProvider = new CustomBaseTypeProvider(new PyType(locals[CallViaInheritance.BaseClassName])); + PythonEngine.InteropConfiguration.PythonBaseTypeProviders.Add(BaseTypeProvider); } [OneTimeTearDown] public void Dispose() { - PythonEngine.Shutdown(); + PythonEngine.InteropConfiguration.PythonBaseTypeProviders.Remove(BaseTypeProvider); } + [Test] public void CallMethodMakesObjectCallable() { var doubler = new DerivedDoubler(); dynamic applyObjectTo21 = PythonEngine.Eval("lambda o: o(21)"); - Assert.AreEqual(doubler.__call__(21), (int)applyObjectTo21(doubler.ToPython())); + Assert.That((int)applyObjectTo21(doubler.ToPython()), Is.EqualTo(doubler.__call__(21))); } + [Test] public void CallMethodCanBeInheritedFromPython() { var callViaInheritance = new CallViaInheritance(); dynamic applyObjectTo14 = PythonEngine.Eval("lambda o: o(14)"); - Assert.AreEqual(callViaInheritance.Call(14), (int)applyObjectTo14(callViaInheritance.ToPython())); + Assert.That((int)applyObjectTo14(callViaInheritance.ToPython()), Is.EqualTo(callViaInheritance.Call(14))); } [Test] @@ -48,7 +51,7 @@ public void CanOverwriteCall() scope.Exec("orig_call = o.Call"); scope.Exec("o.Call = lambda a: orig_call(a*7)"); int result = scope.Eval("o.Call(5)"); - Assert.AreEqual(105, result); + Assert.That(result, Is.EqualTo(105)); } class Doubler @@ -71,16 +74,14 @@ class {BaseClassName}(MyCallableBase): pass public int Call(int arg) => 3 * arg; } - class CustomBaseTypeProvider : IPythonBaseTypeProvider + class CustomBaseTypeProvider(PyType BaseClass) : IPythonBaseTypeProvider { - internal static PyType BaseClass; - public IEnumerable GetBaseTypes(Type type, IList existingBases) { - Assert.Greater(BaseClass.Refcount, 0); + Assert.That(BaseClass.Refcount, Is.GreaterThan(0)); return type != typeof(CallViaInheritance) ? existingBases - : new[] { BaseClass }; + : [BaseClass]; } } } diff --git a/src/embed_tests/ClassManagerTests.cs b/src/embed_tests/ClassManagerTests.cs index 72025a28b..83bfa7bc2 100644 --- a/src/embed_tests/ClassManagerTests.cs +++ b/src/embed_tests/ClassManagerTests.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class ClassManagerTests { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void NestedClassDerivingFromParent() { diff --git a/src/embed_tests/CodecGroups.cs b/src/embed_tests/CodecGroups.cs index 689e5b24c..22ed0df72 100644 --- a/src/embed_tests/CodecGroups.cs +++ b/src/embed_tests/CodecGroups.cs @@ -20,7 +20,7 @@ public void GetEncodersByType() }; var got = group.GetEncoders(typeof(Uri)).ToArray(); - CollectionAssert.AreEqual(new[]{encoder1, encoder2}, got); + Assert.That(got, Is.EqualTo(new[] { encoder1, encoder2 }).AsCollection); } [Test] @@ -31,9 +31,13 @@ public void CanEncode() new ObjectToEncoderInstanceEncoder(), }; - Assert.IsTrue(group.CanEncode(typeof(Tuple))); - Assert.IsTrue(group.CanEncode(typeof(Uri))); - Assert.IsFalse(group.CanEncode(typeof(string))); + Assert.Multiple(() => + { + Assert.That(group.CanEncode(typeof(Tuple)), Is.True); + Assert.That(group.CanEncode(typeof(Uri)), Is.True); + Assert.That(group.CanEncode(typeof(string)), Is.False); + }); + } [Test] @@ -50,12 +54,12 @@ public void Encodes() var uri = group.TryEncode(new Uri("data:")); var clrObject = (CLRObject)ManagedType.GetManagedObject(uri); - Assert.AreSame(encoder1, clrObject.inst); - Assert.AreNotSame(encoder2, clrObject.inst); + Assert.That(clrObject.inst, Is.SameAs(encoder1)); + Assert.That(clrObject.inst, Is.Not.SameAs(encoder2)); var tuple = group.TryEncode(Tuple.Create(1)); clrObject = (CLRObject)ManagedType.GetManagedObject(tuple); - Assert.AreSame(encoder0, clrObject.inst); + Assert.That(clrObject.inst, Is.SameAs(encoder0)); } [Test] @@ -72,11 +76,11 @@ public void GetDecodersByTypes() }; var decoder = group.GetDecoder(pyfloat, typeof(string)); - Assert.AreSame(decoder2, decoder); + Assert.That(decoder, Is.SameAs(decoder2)); decoder = group.GetDecoder(pystr, typeof(string)); - Assert.IsNull(decoder); + Assert.That(decoder, Is.Null); decoder = group.GetDecoder(pyint, typeof(long)); - Assert.AreSame(decoder1, decoder); + Assert.That(decoder, Is.SameAs(decoder1)); } [Test] public void CanDecode() @@ -91,10 +95,14 @@ public void CanDecode() decoder2, }; - Assert.IsTrue(group.CanDecode(pyint, typeof(long))); - Assert.IsFalse(group.CanDecode(pyint, typeof(int))); - Assert.IsTrue(group.CanDecode(pyfloat, typeof(string))); - Assert.IsFalse(group.CanDecode(pystr, typeof(string))); + Assert.Multiple(() => + { + Assert.That(group.CanDecode(pyint, typeof(long))); + Assert.That(group.CanDecode(pyint, typeof(int)), Is.False); + Assert.That(group.CanDecode(pyfloat, typeof(string))); + Assert.That(group.CanDecode(pystr, typeof(string)), Is.False); + }); + } [Test] @@ -109,24 +117,14 @@ public void Decodes() decoder2, }; - Assert.IsTrue(group.TryDecode(new PyInt(10), out long longResult)); - Assert.AreEqual(42, longResult); - Assert.IsTrue(group.TryDecode(new PyFloat(10), out string strResult)); - Assert.AreSame("atad:", strResult); - - Assert.IsFalse(group.TryDecode(new PyInt(10), out int _)); - } - - [SetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [TearDown] - public void Dispose() - { - PythonEngine.Shutdown(); + Assert.Multiple(() => + { + Assert.That(group.TryDecode(new PyInt(10), out long longResult)); + Assert.That(longResult, Is.EqualTo(42)); + Assert.That(group.TryDecode(new PyFloat(10), out string strResult)); + Assert.That(strResult, Is.SameAs("atad:")); + Assert.That(group.TryDecode(new PyInt(10), out int _), Is.False); + }); } } } diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index be416bc15..5879462f5 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -8,16 +8,10 @@ namespace Python.EmbeddingTest { public class Codecs { - [SetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - [TearDown] - public void Dispose() + public void TearDown() { - PythonEngine.Shutdown(); + PyObjectConversions.Reset(); } [Test] @@ -286,6 +280,7 @@ public void IterableDecoderTest() // regression for https://github.com/pythonnet/pythonnet/issues/1427 [Test] + [Ignore("Broken, the list_encoder object ends up in builtins and fails during GC")] public void PythonRegisteredDecoder_NoStackOverflowOnSystemType() { const string PyCode = @" @@ -356,13 +351,38 @@ from datetime import datetime } [Test] - public void ExceptionDecodedNoInstance() + public void FloatDerivedDecoded() { - PyObjectConversions.RegisterDecoder(new InstancelessExceptionDecoder()); using var scope = Py.CreateScope(); - var error = Assert.Throws(() => PythonEngine.Exec( - $"[].__iter__().__next__()")); - Assert.AreEqual(TestExceptionMessage, error.Message); + scope.Exec(@"class FloatDerived(float): pass"); + using var floatDerived = scope.Eval("FloatDerived"); + var decoder = new DecoderReturningPredefinedValue(floatDerived, 42); + PyObjectConversions.RegisterDecoder(decoder); + using var result = scope.Eval("FloatDerived()"); + object decoded = result.As(); + Assert.AreEqual(42, decoded); + } + + [Test] + public void ExceptionDecodedNoInstance() + { + if (Runtime.PyVersion < new Version(3, 12)) + { + PyObjectConversions.RegisterDecoder(new InstancelessExceptionDecoder()); + using var scope = Py.CreateScope(); + + var error = Assert.Throws(() => + PythonEngine.Exec($"[].__iter__().__next__()") + ); + Assert.AreEqual(TestExceptionMessage, error.Message); + } + else + { + Assert.Ignore( + "This test does not work for Python 3.12, see " + + "https://github.com/python/cpython/issues/101578" + ); + } } public static void AcceptsDateTime(DateTime v) {} diff --git a/src/embed_tests/dynamic.cs b/src/embed_tests/Dynamic.cs similarity index 95% rename from src/embed_tests/dynamic.cs rename to src/embed_tests/Dynamic.cs index 6e3bfc4cb..174167118 100644 --- a/src/embed_tests/dynamic.cs +++ b/src/embed_tests/Dynamic.cs @@ -8,18 +8,6 @@ namespace Python.EmbeddingTest { public class DynamicTest { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - /// /// Set the attribute of a PyObject with a .NET object. /// diff --git a/src/embed_tests/Events.cs b/src/embed_tests/Events.cs new file mode 100644 index 000000000..94a30726b --- /dev/null +++ b/src/embed_tests/Events.cs @@ -0,0 +1,55 @@ +using System; +using System.Diagnostics; +using System.Threading; + +using NUnit.Framework; + +using Python.Runtime; + +namespace Python.EmbeddingTest; + +public class Events +{ + [Test] + public void UsingDoesNotLeak() + { + using var scope = Py.CreateScope(); + scope.Exec(@" +import gc + +from Python.EmbeddingTest import ClassWithEventHandler + +def event_handler(): + pass + +for _ in range(2000): + example = ClassWithEventHandler() + example.LeakEvent += event_handler + example.LeakEvent -= event_handler + del example + +gc.collect() +"); + Runtime.Runtime.TryCollectingGarbage(10); + Assert.AreEqual(0, ClassWithEventHandler.alive); + } +} + +public class ClassWithEventHandler +{ + internal static int alive; + + public event EventHandler LeakEvent; + private Array arr; // dummy array to exacerbate memory leak + + public ClassWithEventHandler() + { + Interlocked.Increment(ref alive); + this.arr = new int[800]; + } + + ~ClassWithEventHandler() + { + Interlocked.Decrement(ref alive); + } +} diff --git a/src/embed_tests/ExtensionTypes.cs b/src/embed_tests/ExtensionTypes.cs index 803845960..3e8ead142 100644 --- a/src/embed_tests/ExtensionTypes.cs +++ b/src/embed_tests/ExtensionTypes.cs @@ -8,18 +8,6 @@ namespace Python.EmbeddingTest; public class ExtensionTypes { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void WeakrefIsNone_AfterBoundMethodIsGone() { @@ -27,6 +15,6 @@ public void WeakrefIsNone_AfterBoundMethodIsGone() var boundMethod = new UriBuilder().ToPython().GetAttr(nameof(UriBuilder.GetHashCode)); var weakref = makeref.Invoke(boundMethod); boundMethod.Dispose(); - Assert.IsTrue(weakref.Invoke().IsNone()); + Assert.That(weakref.Invoke().IsNone(), Is.True); } } diff --git a/src/embed_tests/GlobalTestsSetup.cs b/src/embed_tests/GlobalTestsSetup.cs index dff58b978..4f681dd9f 100644 --- a/src/embed_tests/GlobalTestsSetup.cs +++ b/src/embed_tests/GlobalTestsSetup.cs @@ -13,6 +13,7 @@ public partial class GlobalTestsSetup public void GlobalSetup() { Finalizer.Instance.ErrorHandler += FinalizerErrorHandler; + PythonEngine.Initialize(); } private void FinalizerErrorHandler(object sender, Finalizer.ErrorArgs e) diff --git a/src/embed_tests/Inheritance.cs b/src/embed_tests/Inheritance.cs index ebbc24dc4..1074fa288 100644 --- a/src/embed_tests/Inheritance.cs +++ b/src/embed_tests/Inheritance.cs @@ -9,23 +9,31 @@ namespace Python.EmbeddingTest { public class Inheritance { + ExtraBaseTypeProvider ExtraBaseTypeProvider; + NoEffectBaseTypeProvider NoEffectBaseTypeProvider; + + [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); using var locals = new PyDict(); PythonEngine.Exec(InheritanceTestBaseClassWrapper.ClassSourceCode, locals: locals); - ExtraBaseTypeProvider.ExtraBase = new PyType(locals[InheritanceTestBaseClassWrapper.ClassName]); + + NoEffectBaseTypeProvider = new NoEffectBaseTypeProvider(); + ExtraBaseTypeProvider = new ExtraBaseTypeProvider(new PyType(locals[InheritanceTestBaseClassWrapper.ClassName])); + var baseTypeProviders = PythonEngine.InteropConfiguration.PythonBaseTypeProviders; - baseTypeProviders.Add(new ExtraBaseTypeProvider()); - baseTypeProviders.Add(new NoEffectBaseTypeProvider()); + baseTypeProviders.Add(ExtraBaseTypeProvider); + baseTypeProviders.Add(NoEffectBaseTypeProvider); } [OneTimeTearDown] public void Dispose() { - ExtraBaseTypeProvider.ExtraBase.Dispose(); - PythonEngine.Shutdown(); + var baseTypeProviders = PythonEngine.InteropConfiguration.PythonBaseTypeProviders; + baseTypeProviders.Remove(NoEffectBaseTypeProvider); + baseTypeProviders.Remove(ExtraBaseTypeProvider); + ExtraBaseTypeProvider.Dispose(); } [Test] @@ -33,7 +41,7 @@ public void ExtraBase_PassesInstanceCheck() { var inherited = new Inherited(); bool properlyInherited = PyIsInstance(inherited, ExtraBaseTypeProvider.ExtraBase); - Assert.IsTrue(properlyInherited); + Assert.That(properlyInherited, Is.True); } static dynamic PyIsInstance => PythonEngine.Eval("isinstance"); @@ -44,7 +52,7 @@ public void InheritingWithExtraBase_CreatesNewClass() PyObject a = ExtraBaseTypeProvider.ExtraBase; var inherited = new Inherited(); PyObject inheritedClass = inherited.ToPython().GetAttr("__class__"); - Assert.IsFalse(PythonReferenceComparer.Instance.Equals(a, inheritedClass)); + Assert.That(PythonReferenceComparer.Instance.Equals(a, inheritedClass), Is.False); } [Test] @@ -56,7 +64,7 @@ public void InheritedFromInheritedClassIsSelf() PyObject b = scope.Eval("B"); PyObject bInstance = b.Invoke(); PyObject bInstanceClass = bInstance.GetAttr("__class__"); - Assert.IsTrue(PythonReferenceComparer.Instance.Equals(b, bInstanceClass)); + Assert.That(PythonReferenceComparer.Instance.Equals(b, bInstanceClass), Is.True); } // https://github.com/pythonnet/pythonnet/issues/1420 @@ -76,7 +84,7 @@ public void Grandchild_PassesExtraBaseInstanceCheck() PyObject b = scope.Eval("B"); PyObject bInst = b.Invoke(); bool properlyInherited = PyIsInstance(bInst, ExtraBaseTypeProvider.ExtraBase); - Assert.IsTrue(properlyInherited); + Assert.That(properlyInherited, Is.True); } [Test] @@ -84,7 +92,7 @@ public void CallInheritedClrMethod_WithExtraPythonBase() { var instance = new Inherited().ToPython(); string result = instance.InvokeMethod(nameof(PythonWrapperBase.WrapperBaseMethod)).As(); - Assert.AreEqual(result, nameof(PythonWrapperBase.WrapperBaseMethod)); + Assert.That(nameof(PythonWrapperBase.WrapperBaseMethod), Is.EqualTo(result)); } [Test] @@ -94,7 +102,7 @@ public void CallExtraBaseMethod() using var scope = Py.CreateScope(); scope.Set(nameof(instance), instance); int actual = instance.ToPython().InvokeMethod("callVirt").As(); - Assert.AreEqual(expected: Inherited.OverridenVirtValue, actual); + Assert.That(actual, Is.EqualTo(Inherited.OverridenVirtValue)); } [Test] @@ -105,7 +113,7 @@ public void SetAdHocAttributes_WhenExtraBasePresent() scope.Set(nameof(instance), instance); scope.Exec($"super({nameof(instance)}.__class__, {nameof(instance)}).set_x_to_42()"); int actual = scope.Eval($"{nameof(instance)}.{nameof(Inherited.XProp)}"); - Assert.AreEqual(expected: Inherited.X, actual); + Assert.That(actual, Is.EqualTo(Inherited.X)); } // https://github.com/pythonnet/pythonnet/issues/1476 @@ -115,9 +123,9 @@ public void BaseClearIsCalled() using var scope = Py.CreateScope(); scope.Set("exn", new Exception("42")); var msg = scope.Eval("exn.args[0]"); - Assert.AreEqual(2, msg.Refcount); + Assert.That(msg.Refcount, Is.EqualTo(2)); scope.Set("exn", null); - Assert.AreEqual(1, msg.Refcount); + Assert.That(msg.Refcount, Is.EqualTo(1)); } // https://github.com/pythonnet/pythonnet/issues/1455 @@ -126,18 +134,24 @@ public void PropertyAccessorOverridden() { using var derived = new PropertyAccessorDerived().ToPython(); derived.SetAttr(nameof(PropertyAccessorDerived.VirtualProp), "hi".ToPython()); - Assert.AreEqual("HI", derived.GetAttr(nameof(PropertyAccessorDerived.VirtualProp)).As()); + Assert.That(derived.GetAttr(nameof(PropertyAccessorDerived.VirtualProp)).As(), Is.EqualTo("HI")); } } - class ExtraBaseTypeProvider : IPythonBaseTypeProvider + class ExtraBaseTypeProvider(PyType ExtraBase) : IPythonBaseTypeProvider, IDisposable { - internal static PyType ExtraBase; + public PyType ExtraBase { get; } = ExtraBase; + + public void Dispose() + { + ExtraBase.Dispose(); + } + public IEnumerable GetBaseTypes(Type type, IList existingBases) { if (type == typeof(InheritanceTestBaseClassWrapper)) { - return new[] { PyType.Get(type.BaseType), ExtraBase }; + return [PyType.Get(type.BaseType), ExtraBase]; } return existingBases; } diff --git a/src/embed_tests/Inspect.cs b/src/embed_tests/Inspect.cs index 8ff94e02c..0c4ce43f3 100644 --- a/src/embed_tests/Inspect.cs +++ b/src/embed_tests/Inspect.cs @@ -9,18 +9,6 @@ namespace Python.EmbeddingTest { public class Inspect { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void InstancePropertiesVisibleOnClass() { @@ -28,7 +16,7 @@ public void InstancePropertiesVisibleOnClass() var uriClass = uri.GetPythonType(); var property = uriClass.GetAttr(nameof(Uri.AbsoluteUri)); var pyProp = (PropertyObject)ManagedType.GetManagedObject(property.Reference); - Assert.AreEqual(nameof(Uri.AbsoluteUri), pyProp.info.Value.Name); + Assert.That(pyProp.info.Value.Name, Is.EqualTo(nameof(Uri.AbsoluteUri))); } [Test] diff --git a/src/embed_tests/Modules.cs b/src/embed_tests/Modules.cs index a88ab8552..67fa3d0fc 100644 --- a/src/embed_tests/Modules.cs +++ b/src/embed_tests/Modules.cs @@ -28,18 +28,6 @@ public void Dispose() } } - [OneTimeSetUp] - public void OneTimeSetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void OneTimeTearDown() - { - PythonEngine.Shutdown(); - } - /// /// Eval a Python expression and obtain its return value. /// @@ -50,8 +38,8 @@ public void TestEval() { ps.Set("a", 1); var result = ps.Eval("a + 2"); - Assert.AreEqual(3, result); - } + Assert.That(result, Is.EqualTo(3)); + } } /// @@ -66,7 +54,7 @@ public void TestExec() ps.Set("cc", 10); //declare a local variable ps.Exec("aa = bb + cc + 3"); var result = ps.Get("aa"); - Assert.AreEqual(113, result); + Assert.That(result, Is.EqualTo(113)); } } @@ -83,7 +71,7 @@ public void TestCompileExpression() ps.Set("cc", 10); //declare a local variable PyObject script = PythonEngine.Compile("bb + cc + 3", "", RunFlagType.Eval); var result = ps.Execute(script); - Assert.AreEqual(113, result); + Assert.That(result, Is.EqualTo(113)); } } @@ -102,7 +90,7 @@ public void TestCompileStatements() PyObject script = PythonEngine.Compile("aa = bb + cc + 3", "", RunFlagType.File); ps.Execute(script); var result = ps.Get("aa"); - Assert.AreEqual(113, result); + Assert.That(result, Is.EqualTo(113)); } } @@ -123,7 +111,7 @@ public void TestScopeFunction() dynamic func1 = ps.Get("func1"); func1(); //call the function, it can be called any times var result = ps.Get("bb"); - Assert.AreEqual(100, result); + Assert.That(result, Is.EqualTo(100)); ps.Set("bb", 100); ps.Set("cc", 10); @@ -134,7 +122,7 @@ public void TestScopeFunction() dynamic func2 = ps.Get("func2"); func2(); result = ps.Get("bb"); - Assert.AreEqual(20, result); + Assert.That(result, Is.EqualTo(20)); } } @@ -169,6 +157,62 @@ public void TestScopeClass() } } + /// + /// Create a class in the scope, the class can read variables in the scope. + /// Its methods can write the variables with the help of 'global' keyword. + /// + [Test] + public void TestCreateVirtualPackageStructure() + { + using (Py.GIL()) + { + using var _p1 = PyModule.FromString("test", ""); + // Sub-module + using var _p2 = PyModule.FromString("test.scope", + "class Class1():\n" + + " def __init__(self, value):\n" + + " self.value = value\n" + + " def call(self, arg):\n" + + " return self.value + bb + arg\n" + // use scope variables + " def update(self, arg):\n" + + " global bb\n" + + " bb = self.value + arg\n", // update scope variable + "test" + ); + + dynamic ps2 = Py.Import("test.scope"); + ps2.bb = 100; + + dynamic obj1 = ps2.Class1(20); + var result = obj1.call(10).As(); + Assert.AreEqual(130, result); + + obj1.update(10); + result = ps2.Get("bb"); + Assert.AreEqual(30, result); + } + } + + /// + /// Test setting the file attribute via a FromString parameter + /// + [Test] + public void TestCreateModuleWithFilename() + { + using var _gil = Py.GIL(); + + using var mod = PyModule.FromString("mod", ""); + using var modWithoutName = PyModule.FromString("mod_without_name", "", " "); + using var modNullName = PyModule.FromString("mod_null_name", "", null); + + using var modWithName = PyModule.FromString("mod_with_name", "", "some_filename"); + + Assert.That(mod.Get("__file__"), Is.EqualTo("none")); + Assert.That(modWithoutName.Get("__file__"), Is.EqualTo("none")); + Assert.That(modNullName.Get("__file__"), Is.EqualTo("none")); + Assert.That(modWithName.Get("__file__"), Is.EqualTo("some_filename")); + } + /// /// Import a python module into the session. /// Equivalent to the Python "import" statement. @@ -179,22 +223,22 @@ public void TestImportModule() using (Py.GIL()) { dynamic sys = ps.Import("sys"); - Assert.IsTrue(ps.Contains("sys")); + Assert.That(ps.Contains("sys"), Is.True); ps.Exec("sys.attr1 = 2"); var value1 = ps.Eval("sys.attr1"); var value2 = sys.attr1.As(); - Assert.AreEqual(2, value1); + Assert.That(value1, Is.EqualTo(2)); Assert.AreEqual(2, value2); //import as ps.Import("sys", "sys1"); - Assert.IsTrue(ps.Contains("sys1")); + Assert.That(ps.Contains("sys1"), Is.True); } } /// - /// Create a scope and import variables from a scope, + /// Create a scope and import variables from a scope, /// exec Python statements in the scope then discard it. /// [Test] @@ -210,15 +254,15 @@ public void TestImportScope() scope.Import(ps, "ps"); scope.Exec("aa = ps.bb + ps.cc + 3"); var result = scope.Get("aa"); - Assert.AreEqual(113, result); + Assert.That(result, Is.EqualTo(113)); } - Assert.IsFalse(ps.Contains("aa")); + Assert.That(ps.Contains("aa"), Is.False); } } /// - /// Create a scope and import variables from a scope, + /// Create a scope and import variables from a scope, /// exec Python statements in the scope then discard it. /// [Test] @@ -233,15 +277,15 @@ public void TestImportAllFromScope() { scope.Exec("aa = bb + cc + 3"); var result = scope.Get("aa"); - Assert.AreEqual(113, result); + Assert.That(result, Is.EqualTo(113)); } - Assert.IsFalse(ps.Contains("aa")); + Assert.That(ps.Contains("aa"), Is.False); } } /// - /// Create a scope and import variables from a scope, + /// Create a scope and import variables from a scope, /// call the function imported. /// [Test] @@ -286,25 +330,25 @@ public void TestImportScopeFunction() public void TestVariables() { using (Py.GIL()) - { + { (ps.Variables() as dynamic)["ee"] = new PyInt(200); var a0 = ps.Get("ee"); - Assert.AreEqual(200, a0); + Assert.That(a0, Is.EqualTo(200)); ps.Exec("locals()['ee'] = 210"); var a1 = ps.Get("ee"); - Assert.AreEqual(210, a1); + Assert.That(a1, Is.EqualTo(210)); ps.Exec("globals()['ee'] = 220"); var a2 = ps.Get("ee"); - Assert.AreEqual(220, a2); + Assert.That(a2, Is.EqualTo(220)); using (var item = ps.Variables()) { item["ee"] = new PyInt(230); } var a3 = ps.Get("ee"); - Assert.AreEqual(230, a3); + Assert.That(a3, Is.EqualTo(230)); } } @@ -326,8 +370,8 @@ public void TestThread() _ps.res = 0; _ps.bb = 100; _ps.th_cnt = 0; - //add function to the scope - //can be call many times, more efficient than ast + //add function to the scope + //can be call many times, more efficient than ast ps.Exec( "import threading\n"+ "lock = threading.Lock()\n"+ @@ -364,7 +408,7 @@ public void TestThread() using (Py.GIL()) { var result = ps.Get("res"); - Assert.AreEqual(101 * th_cnt, result); + Assert.That(result, Is.EqualTo(101 * th_cnt)); } } finally @@ -378,7 +422,7 @@ public void TestCreate() { using var scope = Py.CreateScope(); - Assert.IsFalse(PyModule.SysModules.HasKey("testmod")); + Assert.That(PyModule.SysModules.HasKey("testmod"), Is.False); PyModule testmod = new PyModule("testmod"); @@ -392,7 +436,7 @@ public void TestCreate() ); scope.Execute(code); - Assert.IsTrue(scope.TryGet("x", out dynamic x)); + Assert.That(scope.TryGet("x", out dynamic x), Is.True); Assert.AreEqual("True", x.ToString()); } diff --git a/src/embed_tests/NeedsReinit/StopAndRestartEngine.cs b/src/embed_tests/NeedsReinit/StopAndRestartEngine.cs new file mode 100644 index 000000000..9ea4f73c5 --- /dev/null +++ b/src/embed_tests/NeedsReinit/StopAndRestartEngine.cs @@ -0,0 +1,28 @@ +using Python.Runtime; +using NUnit.Framework; + +namespace Python.EmbeddingTest.NeedsReinit; + +public class StopAndRestartEngine +{ + bool WasInitialized = false; + + [OneTimeSetUp] + public void Setup() + { + WasInitialized = PythonEngine.IsInitialized; + if (WasInitialized) + { + PythonEngine.Shutdown(); + } + } + + [OneTimeTearDown] + public void Teardown() + { + if (WasInitialized && !PythonEngine.IsInitialized) + { + PythonEngine.Initialize(); + } + } +} diff --git a/src/embed_tests/TestDomainReload.cs b/src/embed_tests/NeedsReinit/TestDomainReload.cs similarity index 99% rename from src/embed_tests/TestDomainReload.cs rename to src/embed_tests/NeedsReinit/TestDomainReload.cs index a0f9b63eb..a8d2cd3d8 100644 --- a/src/embed_tests/TestDomainReload.cs +++ b/src/embed_tests/NeedsReinit/TestDomainReload.cs @@ -15,10 +15,13 @@ // Unfortunately this means no continuous integration testing for this case. // #if NETFRAMEWORK -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest.NeedsReinit { - class TestDomainReload + [Category("NeedsReinit")] + class TestDomainReload : StopAndRestartEngine { + + abstract class CrossCaller : MarshalByRefObject { public abstract ValueType Execute(ValueType arg); diff --git a/src/embed_tests/pyinitialize.cs b/src/embed_tests/NeedsReinit/TestPyInitialize.cs similarity index 95% rename from src/embed_tests/pyinitialize.cs rename to src/embed_tests/NeedsReinit/TestPyInitialize.cs index 25dafb686..1ef4127b8 100644 --- a/src/embed_tests/pyinitialize.cs +++ b/src/embed_tests/NeedsReinit/TestPyInitialize.cs @@ -2,9 +2,10 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest.NeedsReinit { - public class PyInitializeTest + [Category("NeedsReinit")] + public class TestPyInitialize : StopAndRestartEngine { /// /// Tests issue with multiple simple Initialize/Shutdowns. @@ -42,8 +43,8 @@ public static void LoadSpecificArgs() { using var v0 = argv[0]; using var v1 = argv[1]; - Assert.AreEqual(args[0], v0.ToString()); - Assert.AreEqual(args[1], v1.ToString()); + Assert.That(v0.ToString(), Is.EqualTo(args[0])); + Assert.That(v1.ToString(), Is.EqualTo(args[1])); } } } diff --git a/src/embed_tests/NeedsReinit/TestPythonEngineProperties.cs b/src/embed_tests/NeedsReinit/TestPythonEngineProperties.cs new file mode 100644 index 000000000..8eb9e975d --- /dev/null +++ b/src/embed_tests/NeedsReinit/TestPythonEngineProperties.cs @@ -0,0 +1,133 @@ +using System; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest.NeedsReinit +{ + [Category("NeedsReinit")] + public class TestPythonEngineProperties : StopAndRestartEngine + { + [Test] + public void SetPythonHome() + { + PythonEngine.Initialize(); + var pythonHomeBackup = PythonEngine.PythonHome; + PythonEngine.Shutdown(); + + if (pythonHomeBackup == "") + Assert.Inconclusive("Can't reset PythonHome to empty string, skipping"); + + var pythonHome = "/dummypath/"; + + PythonEngine.PythonHome = pythonHome; + PythonEngine.Initialize(); + + Assert.AreEqual(pythonHome, PythonEngine.PythonHome); + PythonEngine.Shutdown(); + + // Restoring valid pythonhome. + PythonEngine.PythonHome = pythonHomeBackup; + } + + [Test] + public void SetPythonHomeTwice() + { + PythonEngine.Initialize(); + var pythonHomeBackup = PythonEngine.PythonHome; + PythonEngine.Shutdown(); + + if (pythonHomeBackup == "") + Assert.Inconclusive("Can't reset PythonHome to empty string, skipping"); + + var pythonHome = "/dummypath/"; + + PythonEngine.PythonHome = "/dummypath2/"; + PythonEngine.PythonHome = pythonHome; + PythonEngine.Initialize(); + + Assert.AreEqual(pythonHome, PythonEngine.PythonHome); + PythonEngine.Shutdown(); + + PythonEngine.PythonHome = pythonHomeBackup; + } + + [Test] + [Ignore("Currently buggy in Python")] + public void SetPythonHomeEmptyString() + { + PythonEngine.Initialize(); + + var backup = PythonEngine.PythonHome; + if (backup == "") + { + PythonEngine.Shutdown(); + Assert.Inconclusive("Can't reset PythonHome to empty string, skipping"); + } + PythonEngine.PythonHome = ""; + + Assert.AreEqual("", PythonEngine.PythonHome); + + PythonEngine.PythonHome = backup; + PythonEngine.Shutdown(); + } + + [Test] + public void SetProgramName() + { + if (PythonEngine.IsInitialized) + { + PythonEngine.Shutdown(); + } + + var programNameBackup = PythonEngine.ProgramName; + + var programName = "FooBar"; + + PythonEngine.ProgramName = programName; + PythonEngine.Initialize(); + + Assert.AreEqual(programName, PythonEngine.ProgramName); + PythonEngine.Shutdown(); + + PythonEngine.ProgramName = programNameBackup; + } + + [Test] + public void SetPythonPath() + { + PythonEngine.Initialize(); + + const string moduleName = "pytest"; + bool importShouldSucceed; + try + { + Py.Import(moduleName); + importShouldSucceed = true; + } + catch + { + importShouldSucceed = false; + } + + string[] paths = Py.Import("sys").GetAttr("path").As(); + string path = string.Join(System.IO.Path.PathSeparator.ToString(), paths); + + // path should not be set to PythonEngine.PythonPath here. + // PythonEngine.PythonPath gets the default module search path, not the full search path. + // The list sys.path is initialized with this value on interpreter startup; + // it can be (and usually is) modified later to change the search path for loading modules. + // See https://docs.python.org/3/c-api/init.html#c.Py_GetPath + // After PythonPath is set, then PythonEngine.PythonPath will correctly return the full search path. + + PythonEngine.Shutdown(); + + PythonEngine.PythonPath = path; + PythonEngine.Initialize(); + + Assert.AreEqual(path, PythonEngine.PythonPath); + if (importShouldSucceed) Py.Import(moduleName); + + PythonEngine.Shutdown(); + } + } +} diff --git a/src/embed_tests/TestRuntime.cs b/src/embed_tests/NeedsReinit/TestRuntime.cs similarity index 94% rename from src/embed_tests/TestRuntime.cs rename to src/embed_tests/NeedsReinit/TestRuntime.cs index 77696fd96..193bf57d3 100644 --- a/src/embed_tests/TestRuntime.cs +++ b/src/embed_tests/NeedsReinit/TestRuntime.cs @@ -3,20 +3,11 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest.NeedsReinit { - public class TestRuntime + [Ignore("Tests for low-level Runtime functions, crashing currently")] + public class TestRuntime : StopAndRestartEngine { - [OneTimeSetUp] - public void SetUp() - { - // We needs to ensure that no any engines are running. - if (PythonEngine.IsInitialized) - { - PythonEngine.Shutdown(); - } - } - [Test] public static void Py_IsInitializedValue() { diff --git a/src/embed_tests/NumPyTests.cs b/src/embed_tests/NumPyTests.cs index e102ddb99..6f4a85716 100644 --- a/src/embed_tests/NumPyTests.cs +++ b/src/embed_tests/NumPyTests.cs @@ -13,14 +13,13 @@ public class NumPyTests [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); TupleCodec.Register(); } [OneTimeTearDown] public void Dispose() { - PythonEngine.Shutdown(); + PyObjectConversions.Reset(); } [Test] @@ -32,7 +31,7 @@ public void TestReadme() StringAssert.StartsWith("-0.95892", sin(5).ToString()); double c = (double)(np.cos(5) + sin(5)); - Assert.AreEqual(-0.675262, c, 0.01); + Assert.That(c, Is.EqualTo(-0.675262).Within(0.01)); dynamic a = np.array(new List { 1, 2, 3 }); Assert.AreEqual("float64", a.dtype.ToString()); diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 4993994d3..15258fc83 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -1,7 +1,7 @@ - net472;net6.0 + net472;net10.0 ..\pythonnet.snk true @@ -14,19 +14,28 @@ + + + + + $(DefineConstants);$(ConfiguredConstants) - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - 1.0.0 + 1.* all runtime; build; native; contentfiles; analyzers diff --git a/src/embed_tests/References.cs b/src/embed_tests/References.cs index c416c5ebe..af9e74336 100644 --- a/src/embed_tests/References.cs +++ b/src/embed_tests/References.cs @@ -5,18 +5,6 @@ namespace Python.EmbeddingTest public class References { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void MoveToPyObject_SetsNull() { @@ -24,10 +12,10 @@ public void MoveToPyObject_SetsNull() NewReference reference = Runtime.PyDict_Items(dict.Reference); try { - Assert.IsFalse(reference.IsNull()); + Assert.That(reference.IsNull(), Is.False); using (reference.MoveToPyObject()) - Assert.IsTrue(reference.IsNull()); + Assert.That(reference.IsNull(), Is.True); } finally { diff --git a/src/embed_tests/StateSerialization/MethodSerialization.cs b/src/embed_tests/StateSerialization/MethodSerialization.cs index 80b7a08ee..d565c1e7a 100644 --- a/src/embed_tests/StateSerialization/MethodSerialization.cs +++ b/src/embed_tests/StateSerialization/MethodSerialization.cs @@ -20,7 +20,7 @@ public void GenericRoundtrip() } [Test] - public void ConstrctorRoundtrip() + public void ConstructorRoundtrip() { var ctor = typeof(MethodTestHost).GetConstructor(new[] { typeof(int) }); var maybeConstructor = new MaybeMethodBase(ctor); @@ -33,6 +33,10 @@ static T SerializationRoundtrip(T item) { using var buf = new MemoryStream(); var formatter = RuntimeData.CreateFormatter(); + if (typeof(NoopFormatter).IsAssignableFrom(formatter.GetType())) + { + Assert.Inconclusive("NoopFormatter in use, cannot perform serialization test."); + } formatter.Serialize(buf, item); buf.Position = 0; return (T)formatter.Deserialize(buf); diff --git a/src/embed_tests/TestCallbacks.cs b/src/embed_tests/TestCallbacks.cs index 88b84d0c3..7e9583364 100644 --- a/src/embed_tests/TestCallbacks.cs +++ b/src/embed_tests/TestCallbacks.cs @@ -5,16 +5,6 @@ namespace Python.EmbeddingTest { public class TestCallbacks { - [OneTimeSetUp] - public void SetUp() { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() { - PythonEngine.Shutdown(); - } - [Test] public void TestNoOverloadException() { int passed = 0; @@ -23,7 +13,7 @@ public void TestNoOverloadException() { using dynamic callWith42 = PythonEngine.Eval("lambda f: f([42])"); using var pyFunc = aFunctionThatCallsIntoPython.ToPython(); var error = Assert.Throws(() => callWith42(pyFunc)); - Assert.AreEqual("TypeError", error.Type.Name); + Assert.That(error.Type.Name, Is.EqualTo("TypeError")); string expectedArgTypes = "()"; StringAssert.EndsWith(expectedArgTypes, error.Message); error.Traceback.Dispose(); diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 0686d528b..3feced8d0 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -23,18 +23,6 @@ public class TestConverter typeof(ulong) }; - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestConvertSingleToManaged( [Values(float.PositiveInfinity, float.NegativeInfinity, float.MinValue, float.MaxValue, float.NaN, @@ -45,8 +33,8 @@ public void TestConvertSingleToManaged( object convertedValue; var converted = Converter.ToManaged(pyFloat, typeof(float), out convertedValue, false); - Assert.IsTrue(converted); - Assert.IsTrue(((float) convertedValue).Equals(testValue)); + Assert.That(converted, Is.True); + Assert.That(((float)convertedValue).Equals(testValue), Is.True); } [Test] @@ -59,8 +47,8 @@ public void TestConvertDoubleToManaged( object convertedValue; var converted = Converter.ToManaged(pyFloat, typeof(double), out convertedValue, false); - Assert.IsTrue(converted); - Assert.IsTrue(((double) convertedValue).Equals(testValue)); + Assert.That(converted, Is.True); + Assert.That(((double)convertedValue).Equals(testValue), Is.True); } [Test] @@ -79,10 +67,10 @@ public void CovertTypeError() try { bool res = Converter.ToManaged(s, type, out value, true); - Assert.IsFalse(res); + Assert.That(res, Is.False); var bo = Exceptions.ExceptionMatches(Exceptions.TypeError); - Assert.IsTrue(Exceptions.ExceptionMatches(Exceptions.TypeError) - || Exceptions.ExceptionMatches(Exceptions.ValueError)); + Assert.That(Exceptions.ExceptionMatches(Exceptions.TypeError) + || Exceptions.ExceptionMatches(Exceptions.ValueError), Is.True); } finally { @@ -104,8 +92,8 @@ public void ConvertOverflow() foreach (var type in _numTypes) { bool res = Converter.ToManaged(largeNum.BorrowOrThrow(), type, out value, true); - Assert.IsFalse(res); - Assert.IsTrue(Exceptions.ExceptionMatches(Exceptions.OverflowError)); + Assert.That(res, Is.False); + Assert.That(Exceptions.ExceptionMatches(Exceptions.OverflowError), Is.True); Exceptions.Clear(); } } @@ -129,7 +117,7 @@ public void ToNullable() const int Const = 42; var i = new PyInt(Const); var ni = i.As(); - Assert.AreEqual(Const, ni); + Assert.That(ni, Is.EqualTo(Const)); } [Test] @@ -138,9 +126,9 @@ public void BigIntExplicit() BigInteger val = 42; var i = new PyInt(val); var ni = i.As(); - Assert.AreEqual(val, ni); + Assert.That(ni, Is.EqualTo(val)); var nullable = i.As(); - Assert.AreEqual(val, nullable); + Assert.That(nullable, Is.EqualTo(val)); } [Test] @@ -148,7 +136,7 @@ public void PyIntImplicit() { var i = new PyInt(1); var ni = (PyObject)i.As(); - Assert.IsTrue(PythonReferenceComparer.Instance.Equals(i, ni)); + Assert.That(PythonReferenceComparer.Instance.Equals(i, ni), Is.True); } [Test] @@ -158,7 +146,7 @@ public void ToPyList() list.Append("hello".ToPython()); list.Append("world".ToPython()); var back = list.ToPython().As(); - Assert.AreEqual(list.Length(), back.Length()); + Assert.That(back.Length(), Is.EqualTo(list.Length())); } [Test] @@ -182,7 +170,16 @@ public void RawPyObjectProxy() const string handlePropertyName = nameof(PyObject.Handle); #pragma warning restore CS0612 // Type or member is obsolete var proxiedHandle = pyObjectProxy.GetAttr(handlePropertyName).As(); - Assert.AreEqual(pyObject.DangerousGetAddressOrNull(), proxiedHandle); + Assert.That(proxiedHandle, Is.EqualTo(pyObject.DangerousGetAddressOrNull())); + } + + [Test] + public void GenericToPython() + { + int i = 42; + var pyObject = i.ToPythonAs(); + var type = pyObject.GetPythonType(); + Assert.That(type.Name, Is.EqualTo(nameof(IConvertible))); } // regression for https://github.com/pythonnet/pythonnet/issues/451 @@ -198,7 +195,7 @@ class PyGetListImpl(test.GetListImpl): var pyImpl = scope.Get("PyGetListImpl"); dynamic inst = pyImpl.Invoke(); List result = inst.GetList(); - CollectionAssert.AreEqual(new[] { "testing" }, result); + Assert.That(result, Is.EqualTo(new[] { "testing" }).AsCollection); } } diff --git a/src/embed_tests/TestCustomMarshal.cs b/src/embed_tests/TestCustomMarshal.cs index 312863d0c..3bcb6b2e6 100644 --- a/src/embed_tests/TestCustomMarshal.cs +++ b/src/embed_tests/TestCustomMarshal.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestCustomMarshal { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public static void GetManagedStringTwice() { @@ -27,9 +15,9 @@ public static void GetManagedStringTwice() string s1 = Runtime.Runtime.GetManagedString(op.BorrowOrThrow()); string s2 = Runtime.Runtime.GetManagedString(op.Borrow()); - Assert.AreEqual(1, Runtime.Runtime.Refcount32(op.Borrow())); - Assert.AreEqual(expected, s1); - Assert.AreEqual(expected, s2); + Assert.That(Runtime.Runtime.Refcount32(op.Borrow()), Is.EqualTo(1)); + Assert.That(s1, Is.EqualTo(expected)); + Assert.That(s2, Is.EqualTo(expected)); } } } diff --git a/src/embed_tests/TestFinalizer.cs b/src/embed_tests/TestFinalizer.cs index b748a2244..89dcf137e 100644 --- a/src/embed_tests/TestFinalizer.cs +++ b/src/embed_tests/TestFinalizer.cs @@ -17,7 +17,6 @@ public class TestFinalizer public void SetUp() { _oldThreshold = Finalizer.Instance.Threshold; - PythonEngine.Initialize(); Exceptions.Clear(); } @@ -25,7 +24,6 @@ public void SetUp() public void TearDown() { Finalizer.Instance.Threshold = _oldThreshold; - PythonEngine.Shutdown(); } private static void FullGCCollect() @@ -38,7 +36,7 @@ private static void FullGCCollect() [Obsolete("GC tests are not guaranteed")] public void CollectBasicObject() { - Assert.IsTrue(Finalizer.Instance.Enable); + Assert.That(Finalizer.Instance.Enable, Is.True); Finalizer.Instance.Threshold = 1; bool called = false; @@ -49,7 +47,7 @@ public void CollectBasicObject() called = true; }; - Assert.IsFalse(called, "The event handler was called before it was installed"); + Assert.That(called, Is.False, "The event handler was called before it was installed"); Finalizer.Instance.BeforeCollect += handler; IntPtr pyObj = MakeAGarbage(out var shortWeak, out var longWeak); @@ -60,10 +58,10 @@ public void CollectBasicObject() "The referenced object is alive although it should have been collected", shortWeak ); - Assert.IsTrue( + Assert.That( longWeak.IsAlive, - "The reference object is not alive although it should still be", - longWeak + Is.True, + $"The reference object is not alive although it should still be" ); { @@ -83,20 +81,22 @@ public void CollectBasicObject() { Finalizer.Instance.BeforeCollect -= handler; } - Assert.IsTrue(called, "The event handler was not called during finalization"); + Assert.That(called, Is.True, "The event handler was not called during finalization"); Assert.GreaterOrEqual(objectCount, 1); } [Test] + [Ignore("Requires explicit shutdown")] [Obsolete("GC tests are not guaranteed")] public void CollectOnShutdown() { IntPtr op = MakeAGarbage(out var shortWeak, out var longWeak); FullGCCollect(); - Assert.IsFalse(shortWeak.IsAlive); + Assert.That(shortWeak.IsAlive, Is.False); List garbage = Finalizer.Instance.GetCollectedObjects(); Assert.IsNotEmpty(garbage, "The garbage object should be collected"); - Assert.IsTrue(garbage.Contains(op), + Assert.That(garbage.Contains(op), + Is.True, "Garbage should contains the collected object"); PythonEngine.Shutdown(); @@ -133,7 +133,7 @@ private static IntPtr MakeAGarbage(out WeakReference shortWeak, out WeakReferenc handle = obj.Handle; }); garbageGen.Start(); - Assert.IsTrue(garbageGen.Join(TimeSpan.FromSeconds(5)), "Garbage creation timed out"); + Assert.That(garbageGen.Join(TimeSpan.FromSeconds(5)), Is.True, "Garbage creation timed out"); shortWeak = @short; longWeak = @long; return handle; @@ -209,8 +209,8 @@ public void ValidateRefCount() Finalizer.IncorrectRefCntHandler handler = (s, e) => { called = true; - Assert.AreEqual(ptr, e.Handle); - Assert.AreEqual(2, e.ImpactedObjects.Count); + Assert.That(e.Handle, Is.EqualTo(ptr)); + Assert.That(e.ImpactedObjects.Count, Is.EqualTo(2)); // Fix for this test, don't do this on general environment #pragma warning disable CS0618 // Type or member is obsolete Runtime.Runtime.XIncref(e.Reference); @@ -223,7 +223,7 @@ public void ValidateRefCount() ptr = CreateStringGarbage(); FullGCCollect(); Assert.Throws(() => Finalizer.Instance.Collect()); - Assert.IsTrue(called); + Assert.That(called, Is.True); } finally { diff --git a/src/embed_tests/TestGILState.cs b/src/embed_tests/TestGILState.cs index bf6f02dc6..ba2ab500f 100644 --- a/src/embed_tests/TestGILState.cs +++ b/src/embed_tests/TestGILState.cs @@ -17,17 +17,5 @@ public void CanDisposeMultipleTimes() gilState.Dispose(); } } - - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } } } diff --git a/src/embed_tests/TestInstanceWrapping.cs b/src/embed_tests/TestInstanceWrapping.cs index 0a441c823..c6996fd87 100644 --- a/src/embed_tests/TestInstanceWrapping.cs +++ b/src/embed_tests/TestInstanceWrapping.cs @@ -7,18 +7,6 @@ namespace Python.EmbeddingTest { public class TestInstanceWrapping { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - // regression test for https://github.com/pythonnet/pythonnet/issues/811 [Test] public void OverloadResolution_UnknownToObject() @@ -30,7 +18,7 @@ public void OverloadResolution_UnknownToObject() dynamic callWithSelf = PythonEngine.Eval("lambda o: o.ObjOrClass(object())"); callWithSelf(o); - Assert.AreEqual(Overloaded.Object, overloaded.Value); + Assert.That(overloaded.Value, Is.EqualTo(Overloaded.Object)); } } @@ -41,7 +29,7 @@ public void WeakRefIsNone_AfterObjectIsGone() var ub = new UriBuilder().ToPython(); using var weakref = makeref.Invoke(ub); ub.Dispose(); - Assert.IsTrue(weakref.Invoke().IsNone()); + Assert.That(weakref.Invoke().IsNone(), Is.True); } class Base {} diff --git a/src/embed_tests/TestInterrupt.cs b/src/embed_tests/TestInterrupt.cs index e6546adb2..d48f7c73b 100644 --- a/src/embed_tests/TestInterrupt.cs +++ b/src/embed_tests/TestInterrupt.cs @@ -15,7 +15,6 @@ public class TestInterrupt [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); // workaround for assert tlock.locked() warning threading = Py.Import("threading"); } @@ -24,7 +23,6 @@ public void SetUp() public void Dispose() { threading.Dispose(); - PythonEngine.Shutdown(); } [Test] @@ -50,9 +48,9 @@ public void PythonThreadIDStable() } PythonEngine.EndAllowThreads(threadState); - Assert.IsTrue(asyncCall.Wait(TimeSpan.FromSeconds(5)), "Async thread has not finished in time"); + Assert.That(asyncCall.Wait(TimeSpan.FromSeconds(5)), Is.True, "Async thread has not finished in time"); - Assert.AreEqual(pythonThreadID, pythonThreadID2); + Assert.That(pythonThreadID2, Is.EqualTo(pythonThreadID)); Assert.NotZero(pythonThreadID); } @@ -86,13 +84,13 @@ import time PythonEngine.EndAllowThreads(threadState); int interruptReturnValue = PythonEngine.Interrupt((ulong)Interlocked.Read(ref pythonThreadID)); - Assert.AreEqual(1, interruptReturnValue); + Assert.That(interruptReturnValue, Is.EqualTo(1)); threadState = PythonEngine.BeginAllowThreads(); - Assert.IsTrue(asyncCall.Wait(TimeSpan.FromSeconds(5)), "Async thread was not interrupted in time"); + Assert.That(asyncCall.Wait(TimeSpan.FromSeconds(5)), Is.True, "Async thread was not interrupted in time"); PythonEngine.EndAllowThreads(threadState); - Assert.AreEqual(0, asyncCall.Result); + Assert.That(asyncCall.Result, Is.EqualTo(0)); } } } diff --git a/src/embed_tests/TestNamedArguments.cs b/src/embed_tests/TestNamedArguments.cs index c86302038..16d7d5b8f 100644 --- a/src/embed_tests/TestNamedArguments.cs +++ b/src/embed_tests/TestNamedArguments.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestNamedArguments { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - /// /// Test named arguments support through Py.kw method /// @@ -27,7 +15,7 @@ public void TestKeywordArgs() dynamic a = CreateTestClass(); var result = (int)a.Test3(2, Py.kw("a4", 8)); - Assert.AreEqual(12, result); + Assert.That(result, Is.EqualTo(12)); } @@ -40,7 +28,7 @@ public void TestNamedArgs() dynamic a = CreateTestClass(); var result = (int)a.Test3(2, a4: 8); - Assert.AreEqual(12, result); + Assert.That(result, Is.EqualTo(12)); } diff --git a/src/embed_tests/TestNativeTypeOffset.cs b/src/embed_tests/TestNativeTypeOffset.cs index d692c24e6..61b6903c5 100644 --- a/src/embed_tests/TestNativeTypeOffset.cs +++ b/src/embed_tests/TestNativeTypeOffset.cs @@ -13,18 +13,6 @@ namespace Python.EmbeddingTest { public class TestNativeTypeOffset { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - /// /// Tests that installation has generated code for NativeTypeOffset and that it can be loaded. /// diff --git a/src/embed_tests/TestOperator.cs b/src/embed_tests/TestOperator.cs index a5713274a..ab71ed9b3 100644 --- a/src/embed_tests/TestOperator.cs +++ b/src/embed_tests/TestOperator.cs @@ -14,13 +14,132 @@ public class TestOperator [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); + OwnIntCodec.Setup(); } [OneTimeTearDown] public void Dispose() { - PythonEngine.Shutdown(); + PyObjectConversions.Reset(); + } + + // Mock Integer class to test math ops on non-native dotnet types + public readonly struct OwnInt + { + private readonly int _value; + + public int Num => _value; + + public OwnInt() + { + _value = 0; + } + + public OwnInt(int value) + { + _value = value; + } + + public override int GetHashCode() + { + return unchecked(65535 + _value.GetHashCode()); + } + + public override bool Equals(object obj) + { + return obj is OwnInt @object && + Num == @object.Num; + } + + public static OwnInt operator -(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value - p2._value); + } + + public static OwnInt operator +(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value + p2._value); + } + + public static OwnInt operator *(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value * p2._value); + } + + public static OwnInt operator /(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value / p2._value); + } + + public static OwnInt operator %(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value % p2._value); + } + + public static OwnInt operator ^(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value ^ p2._value); + } + + public static bool operator <(OwnInt p1, OwnInt p2) + { + return p1._value < p2._value; + } + + public static bool operator >(OwnInt p1, OwnInt p2) + { + return p1._value > p2._value; + } + + public static bool operator ==(OwnInt p1, OwnInt p2) + { + return p1._value == p2._value; + } + + public static bool operator !=(OwnInt p1, OwnInt p2) + { + return p1._value != p2._value; + } + + public static OwnInt operator |(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value | p2._value); + } + + public static OwnInt operator &(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value & p2._value); + } + + public static bool operator <=(OwnInt p1, OwnInt p2) + { + return p1._value <= p2._value; + } + + public static bool operator >=(OwnInt p1, OwnInt p2) + { + return p1._value >= p2._value; + } + } + + // Codec for mock class above. + public class OwnIntCodec : IPyObjectDecoder + { + public static void Setup() + { + PyObjectConversions.RegisterDecoder(new OwnIntCodec()); + } + + public bool CanDecode(PyType objectType, Type targetType) + { + return objectType.Name == "int" && targetType == typeof(OwnInt); + } + + public bool TryDecode(PyObject pyObj, out T value) + { + value = (T)(object)new OwnInt(pyObj.As()); + return true; + } } public class OperableObject @@ -524,6 +643,121 @@ public void ShiftOperatorOverloads() c = a >> b.Num assert c.Num == a.Num >> b.Num +"); + } + + [Test] + public void ReverseOperatorWithCodec() + { + string name = string.Format("{0}.{1}", + typeof(OwnInt).DeclaringType.Name, + typeof(OwnInt).Name); + string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; + + PythonEngine.Exec($@" +from {module} import * +cls = {name} +a = 2 +b = cls(10) + +c = a + b +assert c.Num == a + b.Num + +c = a - b +assert c.Num == a - b.Num + +c = a * b +assert c.Num == a * b.Num + +c = a / b +assert c.Num == a // b.Num + +c = a % b +assert c.Num == a % b.Num + +c = a & b +assert c.Num == a & b.Num + +c = a | b +assert c.Num == a | b.Num + +c = a ^ b +assert c.Num == a ^ b.Num + +c = a == b +assert c == (a == b.Num) + +c = a != b +assert c == (a != b.Num) + +c = a <= b +assert c == (a <= b.Num) + +c = a >= b +assert c == (a >= b.Num) + +c = a < b +assert c == (a < b.Num) + +c = a > b +assert c == (a > b.Num) +"); + } + + [Test] + public void ForwardOperatorWithCodec() + { + string name = string.Format("{0}.{1}", + typeof(OwnInt).DeclaringType.Name, + typeof(OwnInt).Name); + string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; + + PythonEngine.Exec($@" +from {module} import * +cls = {name} +a = cls(2) +b = 10 +c = a + b +assert c.Num == a.Num + b + +c = a - b +assert c.Num == a.Num - b + +c = a * b +assert c.Num == a.Num * b + +c = a / b +assert c.Num == a.Num // b + +c = a % b +assert c.Num == a.Num % b + +c = a & b +assert c.Num == a.Num & b + +c = a | b +assert c.Num == a.Num | b + +c = a ^ b +assert c.Num == a.Num ^ b + +c = a == b +assert c == (a.Num == b) + +c = a != b +assert c == (a.Num != b) + +c = a <= b +assert c == (a.Num <= b) + +c = a >= b +assert c == (a.Num >= b) + +c = a < b +assert c == (a.Num < b) + +c = a > b +assert c == (a.Num > b) "); } } diff --git a/src/embed_tests/TestPyBuffer.cs b/src/embed_tests/TestPyBuffer.cs index a1bcc161d..89ddf9370 100644 --- a/src/embed_tests/TestPyBuffer.cs +++ b/src/embed_tests/TestPyBuffer.cs @@ -11,14 +11,13 @@ class TestPyBuffer [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); TupleCodec.Register(); } [OneTimeTearDown] public void Dispose() { - PythonEngine.Shutdown(); + PyObjectConversions.Reset(); } [Test] @@ -38,7 +37,7 @@ public void TestBufferWrite() } string result = pythonArray.InvokeMethod("decode", "utf-8".ToPython()).As(); - Assert.IsTrue(result == bufferTestString2); + Assert.That(result == bufferTestString2, Is.True); } [Test] @@ -58,7 +57,7 @@ public void TestBufferRead() } string result = new UTF8Encoding().GetString(managedArray); - Assert.IsTrue(result == " " + bufferTestString.Substring(1)); + Assert.That(result == " " + bufferTestString.Substring(1), Is.True); } [Test] @@ -67,8 +66,8 @@ public void ArrayHasBuffer() var array = new[,] {{1, 2}, {3,4}}; var memoryView = PythonEngine.Eval("memoryview"); var mem = memoryView.Invoke(array.ToPython()); - Assert.AreEqual(1, mem[(0, 0).ToPython()].As()); - Assert.AreEqual(array[1,0], mem[(1, 0).ToPython()].As()); + Assert.That(mem[(0, 0).ToPython()].As(), Is.EqualTo(1)); + Assert.That(mem[(1, 0).ToPython()].As(), Is.EqualTo(array[1, 0])); } [Test] @@ -77,14 +76,14 @@ public void RefCount() using var _ = Py.GIL(); using var arr = ByteArrayFromAsciiString("hello world! !$%&/()=?"); - Assert.AreEqual(1, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(1)); using (PyBuffer buf = arr.GetBuffer()) { - Assert.AreEqual(2, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(2)); } - Assert.AreEqual(1, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(1)); } [Test] @@ -99,7 +98,7 @@ public void Finalization() using var _ = Py.GIL(); using var arr = ByteArrayFromAsciiString("hello world! !$%&/()=?"); - Assert.AreEqual(1, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(1)); MakeBufAndLeak(arr); @@ -107,7 +106,27 @@ public void Finalization() GC.WaitForPendingFinalizers(); Finalizer.Instance.Collect(); - Assert.AreEqual(1, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(1)); + } + + [Test] + public void MultidimensionalNumPyArray() + { + var ndarray = np.arange(24).reshape(1,2,3,4).T; + PyObject ndim = ndarray.ndim; + PyObject shape = ndarray.shape; + PyObject strides = ndarray.strides; + PyObject contiguous = ndarray.flags["C_CONTIGUOUS"]; + + using PyBuffer buf = ndarray.GetBuffer(PyBUF.STRIDED); + + Assert.Multiple(() => + { + Assert.That(buf.Dimensions, Is.EqualTo(ndim.As())); + Assert.That(buf.Shape, Is.EqualTo(shape.As())); + Assert.That(buf.Strides, Is.EqualTo(strides.As())); + Assert.That(buf.IsContiguous(BufferOrderStyle.C), Is.EqualTo(contiguous.As())); + }); } [MethodImpl(MethodImplOptions.NoInlining)] @@ -121,5 +140,21 @@ static PyObject ByteArrayFromAsciiString(string str) using var scope = Py.CreateScope(); return Runtime.Runtime.PyByteArray_FromStringAndSize(str).MoveToPyObject(); } + + dynamic np + { + get + { + try + { + return Py.Import("numpy"); + } + catch (PythonException) + { + Assert.Inconclusive("Numpy or dependency not installed"); + return null; + } + } + } } } diff --git a/src/embed_tests/TestPyFloat.cs b/src/embed_tests/TestPyFloat.cs index 36531cb6a..c6111f180 100644 --- a/src/embed_tests/TestPyFloat.cs +++ b/src/embed_tests/TestPyFloat.cs @@ -9,18 +9,6 @@ namespace Python.EmbeddingTest /// public class TestPyFloat { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void FloatCtor() { @@ -126,5 +114,32 @@ public void AsFloatBad() StringAssert.StartsWith("could not convert string to float", ex.Message); Assert.IsNull(a); } + + [Test] + public void CompareTo() + { + var v = new PyFloat(42); + + Assert.That(v.CompareTo(42f), Is.EqualTo(0)); + Assert.That(v.CompareTo(42d), Is.EqualTo(0)); + + Assert.That(v.CompareTo(41f), Is.EqualTo(1)); + Assert.That(v.CompareTo(41d), Is.EqualTo(1)); + + Assert.That(v.CompareTo(43f), Is.EqualTo(-1)); + Assert.That(v.CompareTo(43d), Is.EqualTo(-1)); + } + + [Test] + public void Equals() + { + var v = new PyFloat(42); + + Assert.That(v.Equals(42f), Is.True); + Assert.That(v.Equals(42d), Is.True); + + Assert.That(v.Equals(41f), Is.False); + Assert.That(v.Equals(41d), Is.False); + } } } diff --git a/src/embed_tests/TestPyInt.cs b/src/embed_tests/TestPyInt.cs index 822fe0715..36319cf1a 100644 --- a/src/embed_tests/TestPyInt.cs +++ b/src/embed_tests/TestPyInt.cs @@ -10,18 +10,6 @@ namespace Python.EmbeddingTest { public class TestPyInt { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestCtorInt() { @@ -191,16 +179,93 @@ public void ToBigInteger() { 0, 1, 2, 0x10, + 0x79, + 0x80, + 0x81, + 0xFF, 0x123, + 0x8000, 0x1234, + 0x8001, + 0x4000, + 0xFF, }; simpleValues = simpleValues.Concat(simpleValues.Select(v => -v)).ToArray(); - foreach (var val in simpleValues) - { - var pyInt = new PyInt(val); - Assert.AreEqual((BigInteger)val, pyInt.ToBigInteger()); - } + var expected = simpleValues.Select(v => new BigInteger(v)).ToArray(); + var actual = simpleValues.Select(v => new PyInt(v).ToBigInteger()).ToArray(); + + CollectionAssert.AreEqual(expected, actual); + } + + [Test] + public void CompareTo() + { + var v = new PyInt(42); + + #region Signed + Assert.AreEqual(0, v.CompareTo(42L)); + Assert.AreEqual(0, v.CompareTo(42)); + Assert.AreEqual(0, v.CompareTo((short)42)); + Assert.AreEqual(0, v.CompareTo((sbyte)42)); + + Assert.AreEqual(1, v.CompareTo(41L)); + Assert.AreEqual(1, v.CompareTo(41)); + Assert.AreEqual(1, v.CompareTo((short)41)); + Assert.AreEqual(1, v.CompareTo((sbyte)41)); + + Assert.AreEqual(-1, v.CompareTo(43L)); + Assert.AreEqual(-1, v.CompareTo(43)); + Assert.AreEqual(-1, v.CompareTo((short)43)); + Assert.AreEqual(-1, v.CompareTo((sbyte)43)); + #endregion Signed + + #region Unsigned + Assert.AreEqual(0, v.CompareTo(42UL)); + Assert.AreEqual(0, v.CompareTo(42U)); + Assert.AreEqual(0, v.CompareTo((ushort)42)); + Assert.AreEqual(0, v.CompareTo((byte)42)); + + Assert.AreEqual(1, v.CompareTo(41UL)); + Assert.AreEqual(1, v.CompareTo(41U)); + Assert.AreEqual(1, v.CompareTo((ushort)41)); + Assert.AreEqual(1, v.CompareTo((byte)41)); + + Assert.AreEqual(-1, v.CompareTo(43UL)); + Assert.AreEqual(-1, v.CompareTo(43U)); + Assert.AreEqual(-1, v.CompareTo((ushort)43)); + Assert.AreEqual(-1, v.CompareTo((byte)43)); + #endregion Unsigned + } + + [Test] + public void Equals() + { + var v = new PyInt(42); + + #region Signed + Assert.True(v.Equals(42L)); + Assert.True(v.Equals(42)); + Assert.True(v.Equals((short)42)); + Assert.True(v.Equals((sbyte)42)); + + Assert.False(v.Equals(41L)); + Assert.False(v.Equals(41)); + Assert.False(v.Equals((short)41)); + Assert.False(v.Equals((sbyte)41)); + #endregion Signed + + #region Unsigned + Assert.True(v.Equals(42UL)); + Assert.True(v.Equals(42U)); + Assert.True(v.Equals((ushort)42)); + Assert.True(v.Equals((byte)42)); + + Assert.False(v.Equals(41UL)); + Assert.False(v.Equals(41U)); + Assert.False(v.Equals((ushort)41)); + Assert.False(v.Equals((byte)41)); + #endregion Unsigned } [Test] diff --git a/src/embed_tests/TestPyIter.cs b/src/embed_tests/TestPyIter.cs index 7428da979..5da660242 100644 --- a/src/embed_tests/TestPyIter.cs +++ b/src/embed_tests/TestPyIter.cs @@ -9,18 +9,6 @@ namespace Python.EmbeddingTest { class TestPyIter { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void KeepOldObjects() { diff --git a/src/embed_tests/TestPyList.cs b/src/embed_tests/TestPyList.cs index eee129f2d..a380f0b2d 100644 --- a/src/embed_tests/TestPyList.cs +++ b/src/embed_tests/TestPyList.cs @@ -7,18 +7,6 @@ namespace Python.EmbeddingTest { public class TestPyList { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestStringIsListType() { diff --git a/src/embed_tests/TestPyNumber.cs b/src/embed_tests/TestPyNumber.cs index 0261c15c1..d8e275521 100644 --- a/src/embed_tests/TestPyNumber.cs +++ b/src/embed_tests/TestPyNumber.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPyNumber { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void IsNumberTypeTrue() { diff --git a/src/embed_tests/TestPyObject.cs b/src/embed_tests/TestPyObject.cs index 2f27eba1b..f762b94e9 100644 --- a/src/embed_tests/TestPyObject.cs +++ b/src/embed_tests/TestPyObject.cs @@ -8,18 +8,6 @@ namespace Python.EmbeddingTest { public class TestPyObject { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestGetDynamicMemberNames() { diff --git a/src/embed_tests/TestPySequence.cs b/src/embed_tests/TestPySequence.cs index dc35a2633..339ea1e83 100644 --- a/src/embed_tests/TestPySequence.cs +++ b/src/embed_tests/TestPySequence.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPySequence { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestIsSequenceTrue() { diff --git a/src/embed_tests/TestPyString.cs b/src/embed_tests/TestPyString.cs index b12e08c23..a1fdd6079 100644 --- a/src/embed_tests/TestPyString.cs +++ b/src/embed_tests/TestPyString.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPyString { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestStringCtor() { @@ -112,5 +100,24 @@ public void TestUnicodeSurrogate() Assert.AreEqual(4, actual.Length()); Assert.AreEqual(expected, actual.ToString()); } + + [Test] + public void CompareTo() + { + var a = new PyString("foo"); + + Assert.AreEqual(0, a.CompareTo("foo")); + Assert.AreEqual("foo".CompareTo("bar"), a.CompareTo("bar")); + Assert.AreEqual("foo".CompareTo("foz"), a.CompareTo("foz")); + } + + [Test] + public void Equals() + { + var a = new PyString("foo"); + + Assert.True(a.Equals("foo")); + Assert.False(a.Equals("bar")); + } } } diff --git a/src/embed_tests/TestPyTuple.cs b/src/embed_tests/TestPyTuple.cs index 5d76116aa..3a3fbf2a0 100644 --- a/src/embed_tests/TestPyTuple.cs +++ b/src/embed_tests/TestPyTuple.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPyTuple { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - /// /// Test IsTupleType without having to Initialize a tuple. /// PyTuple constructor use IsTupleType. This decouples the tests. diff --git a/src/embed_tests/TestPyType.cs b/src/embed_tests/TestPyType.cs index 34645747d..c29032a8a 100644 --- a/src/embed_tests/TestPyType.cs +++ b/src/embed_tests/TestPyType.cs @@ -10,25 +10,14 @@ namespace Python.EmbeddingTest { public class TestPyType { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void CanCreateHeapType() { const string name = "nÁmæ"; const string docStr = "dÁcæ"; - using var doc = new StrPtr(docStr, Encoding.UTF8); + using var doc = new StrPtr(docStr); + var spec = new TypeSpec( name: name, basicSize: Util.ReadInt32(Runtime.Runtime.PyBaseObjectType, TypeOffset.tp_basicsize), diff --git a/src/embed_tests/TestPyWith.cs b/src/embed_tests/TestPyWith.cs index d1c9aac28..fbce811da 100644 --- a/src/embed_tests/TestPyWith.cs +++ b/src/embed_tests/TestPyWith.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPyWith { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - /// /// Test that exception is raised in context manager that ignores it. /// diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs index ca9164a1d..485931cfb 100644 --- a/src/embed_tests/TestPythonEngineProperties.cs +++ b/src/embed_tests/TestPythonEngineProperties.cs @@ -70,149 +70,31 @@ public static void GetVersionDoesntCrash() [Test] public static void GetPythonPathDefault() { - PythonEngine.Initialize(); string s = PythonEngine.PythonPath; StringAssert.Contains("python", s.ToLower()); - PythonEngine.Shutdown(); } [Test] public static void GetProgramNameDefault() { - PythonEngine.Initialize(); string s = PythonEngine.ProgramName; Assert.NotNull(s); - PythonEngine.Shutdown(); } /// /// Test default behavior of PYTHONHOME. If ENVVAR is set it will /// return the same value. If not, returns EmptyString. /// - /// - /// AppVeyor.yml has been update to tests with ENVVAR set. - /// [Test] public static void GetPythonHomeDefault() { string envPythonHome = Environment.GetEnvironmentVariable("PYTHONHOME") ?? ""; - PythonEngine.Initialize(); string enginePythonHome = PythonEngine.PythonHome; Assert.AreEqual(envPythonHome, enginePythonHome); - PythonEngine.Shutdown(); - } - - [Test] - public void SetPythonHome() - { - // We needs to ensure that engine was started and shutdown at least once before setting dummy home. - // Otherwise engine will not run with dummy path with random problem. - if (!PythonEngine.IsInitialized) - { - PythonEngine.Initialize(); - } - - PythonEngine.Shutdown(); - - var pythonHomeBackup = PythonEngine.PythonHome; - - var pythonHome = "/dummypath/"; - - PythonEngine.PythonHome = pythonHome; - PythonEngine.Initialize(); - - PythonEngine.Shutdown(); - - // Restoring valid pythonhome. - PythonEngine.PythonHome = pythonHomeBackup; - } - - [Test] - public void SetPythonHomeTwice() - { - // We needs to ensure that engine was started and shutdown at least once before setting dummy home. - // Otherwise engine will not run with dummy path with random problem. - if (!PythonEngine.IsInitialized) - { - PythonEngine.Initialize(); - } - PythonEngine.Shutdown(); - - var pythonHomeBackup = PythonEngine.PythonHome; - - var pythonHome = "/dummypath/"; - - PythonEngine.PythonHome = "/dummypath2/"; - PythonEngine.PythonHome = pythonHome; - PythonEngine.Initialize(); - - Assert.AreEqual(pythonHome, PythonEngine.PythonHome); - PythonEngine.Shutdown(); - - PythonEngine.PythonHome = pythonHomeBackup; - } - - [Test] - public void SetProgramName() - { - if (PythonEngine.IsInitialized) - { - PythonEngine.Shutdown(); - } - - var programNameBackup = PythonEngine.ProgramName; - - var programName = "FooBar"; - - PythonEngine.ProgramName = programName; - PythonEngine.Initialize(); - - Assert.AreEqual(programName, PythonEngine.ProgramName); - PythonEngine.Shutdown(); - - PythonEngine.ProgramName = programNameBackup; - } - - [Test] - public void SetPythonPath() - { - PythonEngine.Initialize(); - - const string moduleName = "pytest"; - bool importShouldSucceed; - try - { - Py.Import(moduleName); - importShouldSucceed = true; - } - catch - { - importShouldSucceed = false; - } - - string[] paths = Py.Import("sys").GetAttr("path").As(); - string path = string.Join(System.IO.Path.PathSeparator.ToString(), paths); - - // path should not be set to PythonEngine.PythonPath here. - // PythonEngine.PythonPath gets the default module search path, not the full search path. - // The list sys.path is initialized with this value on interpreter startup; - // it can be (and usually is) modified later to change the search path for loading modules. - // See https://docs.python.org/3/c-api/init.html#c.Py_GetPath - // After PythonPath is set, then PythonEngine.PythonPath will correctly return the full search path. - - PythonEngine.Shutdown(); - - PythonEngine.PythonPath = path; - PythonEngine.Initialize(); - - Assert.AreEqual(path, PythonEngine.PythonPath); - if (importShouldSucceed) Py.Import(moduleName); - - PythonEngine.Shutdown(); } } } diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index a248b6a1f..91a412749 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPythonException { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestMessage() { diff --git a/src/embed_tests/pyimport.cs b/src/embed_tests/pyimport.cs index b828d5315..c774af345 100644 --- a/src/embed_tests/pyimport.cs +++ b/src/embed_tests/pyimport.cs @@ -21,28 +21,29 @@ namespace Python.EmbeddingTest /// public class PyImportTest { + string TestPath; + [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); - /* Append the tests directory to sys.path * using reflection to circumvent the private * modifiers placed on most Runtime methods. */ - string testPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "fixtures"); - TestContext.Out.WriteLine(testPath); + TestPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "fixtures"); + TestContext.Out.WriteLine(TestPath); - using var str = Runtime.Runtime.PyString_FromString(testPath); - Assert.IsFalse(str.IsNull()); + using var str = Runtime.Runtime.PyString_FromString(TestPath); + Assert.That(str.IsNull(), Is.False); BorrowedReference path = Runtime.Runtime.PySys_GetObject("path"); - Assert.IsFalse(path.IsNull); + Assert.That(path.IsNull, Is.False); Runtime.Runtime.PyList_Append(path, str.Borrow()); } [OneTimeTearDown] public void Dispose() { - PythonEngine.Shutdown(); + using var _ = Py.GIL(); + Py.Import("sys").GetAttr("path").InvokeMethod("remove", new PyString(TestPath)); } /// @@ -89,7 +90,7 @@ public void BadAssembly() path = @"C:\Windows\System32\kernel32.dll"; } - Assert.IsTrue(File.Exists(path), $"Test DLL {path} does not exist!"); + Assert.That(File.Exists(path), Is.True, $"Test DLL {path} does not exist!"); string code = $@" import clr diff --git a/src/embed_tests/pyrunstring.cs b/src/embed_tests/pyrunstring.cs index 57c133c00..fb1302800 100644 --- a/src/embed_tests/pyrunstring.cs +++ b/src/embed_tests/pyrunstring.cs @@ -6,26 +6,14 @@ namespace Python.EmbeddingTest { public class RunStringTest { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestRunSimpleString() { int aa = PythonEngine.RunSimpleString("import sys"); - Assert.AreEqual(0, aa); + Assert.That(aa, Is.EqualTo(0)); int bb = PythonEngine.RunSimpleString("import 1234"); - Assert.AreEqual(-1, bb); + Assert.That(bb, Is.EqualTo(-1)); } [Test] @@ -39,7 +27,7 @@ public void TestEval() object b = PythonEngine.Eval("sys.attr1 + a + 1", null, locals) .AsManagedObject(typeof(int)); - Assert.AreEqual(111, b); + Assert.That(b, Is.EqualTo(111)); } [Test] @@ -53,7 +41,7 @@ public void TestExec() PythonEngine.Exec("c = sys.attr1 + a + 1", null, locals); object c = locals.GetItem("c").AsManagedObject(typeof(int)); - Assert.AreEqual(111, c); + Assert.That(c, Is.EqualTo(111)); } [Test] diff --git a/src/perf_tests/BaselineComparisonBenchmarkBase.cs b/src/perf_tests/BaselineComparisonBenchmarkBase.cs deleted file mode 100644 index 06adcbc67..000000000 --- a/src/perf_tests/BaselineComparisonBenchmarkBase.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; - -using Python.Runtime; - -namespace Python.PerformanceTests -{ - public class BaselineComparisonBenchmarkBase - { - public BaselineComparisonBenchmarkBase() - { - Console.WriteLine($"CWD: {Environment.CurrentDirectory}"); - Console.WriteLine($"Using Python.Runtime from {typeof(PythonEngine).Assembly.Location} {typeof(PythonEngine).Assembly.GetName()}"); - - try { - PythonEngine.Initialize(); - Console.WriteLine("Python Initialized"); - Trace.Assert(PythonEngine.BeginAllowThreads() != IntPtr.Zero); - Console.WriteLine("Threading enabled"); - } - catch (Exception e) { - Console.WriteLine(e); - throw; - } - } - - static BaselineComparisonBenchmarkBase() - { - SetupRuntimeResolve(); - } - - public static void SetupRuntimeResolve() - { - string pythonRuntimeDll = Environment.GetEnvironmentVariable(BaselineComparisonConfig.EnvironmentVariableName); - if (string.IsNullOrEmpty(pythonRuntimeDll)) - { - throw new ArgumentException( - "Required environment variable is missing", - BaselineComparisonConfig.EnvironmentVariableName); - } - - Console.WriteLine("Preloading " + pythonRuntimeDll); - Assembly.LoadFrom(pythonRuntimeDll); - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { - if (assembly.FullName.StartsWith("Python.Runtime")) - Console.WriteLine(assembly.Location); - foreach(var dependency in assembly.GetReferencedAssemblies()) - if (dependency.FullName.Contains("Python.Runtime")) { - Console.WriteLine($"{assembly} -> {dependency}"); - } - } - - AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; - } - - static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) { - if (!args.Name.StartsWith("Python.Runtime")) - return null; - - var preloaded = AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(a => a.GetName().Name == "Python.Runtime"); - if (preloaded != null) return preloaded; - - string pythonRuntimeDll = Environment.GetEnvironmentVariable(BaselineComparisonConfig.EnvironmentVariableName); - if (string.IsNullOrEmpty(pythonRuntimeDll)) - return null; - - return Assembly.LoadFrom(pythonRuntimeDll); - } - } -} diff --git a/src/perf_tests/BaselineComparisonConfig.cs b/src/perf_tests/BaselineComparisonConfig.cs deleted file mode 100644 index 3f6766554..000000000 --- a/src/perf_tests/BaselineComparisonConfig.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; - -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Jobs; - -using Perfolizer.Horology; - -namespace Python.PerformanceTests -{ - public class BaselineComparisonConfig : ManualConfig - { - public const string EnvironmentVariableName = "PythonRuntimeDLL"; - - public BaselineComparisonConfig() - { - this.Options |= ConfigOptions.DisableOptimizationsValidator; - - string deploymentRoot = BenchmarkTests.DeploymentRoot; - - var baseJob = Job.Default - .WithLaunchCount(1) - .WithWarmupCount(3) - .WithMaxIterationCount(100) - .WithIterationTime(TimeInterval.FromMilliseconds(100)); - this.Add(baseJob - .WithId("baseline") - .WithEnvironmentVariable(EnvironmentVariableName, - Path.Combine(deploymentRoot, "baseline", "Python.Runtime.dll")) - .WithBaseline(true)); - this.Add(baseJob - .WithId("new") - .WithEnvironmentVariable(EnvironmentVariableName, - Path.Combine(deploymentRoot, "new", "Python.Runtime.dll"))); - } - - static BaselineComparisonConfig() { - AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; - } - - static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) { - Console.WriteLine(args.Name); - if (!args.Name.StartsWith("Python.Runtime")) - return null; - string pythonRuntimeDll = Environment.GetEnvironmentVariable(EnvironmentVariableName); - if (string.IsNullOrEmpty(pythonRuntimeDll)) - pythonRuntimeDll = Path.Combine(BenchmarkTests.DeploymentRoot, "baseline", "Python.Runtime.dll"); - return Assembly.LoadFrom(pythonRuntimeDll); - } - } -} diff --git a/src/perf_tests/BenchmarkTests.cs b/src/perf_tests/BenchmarkTests.cs deleted file mode 100644 index 9e033d11f..000000000 --- a/src/perf_tests/BenchmarkTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Reflection; - -using BenchmarkDotNet.Reports; -using BenchmarkDotNet.Running; -using NUnit.Framework; - -namespace Python.PerformanceTests -{ - public class BenchmarkTests - { - Summary summary; - - [OneTimeSetUp] - public void SetUp() - { - Environment.CurrentDirectory = Path.Combine(DeploymentRoot, "new"); - this.summary = BenchmarkRunner.Run(); - Assert.IsNotEmpty(this.summary.Reports); - Assert.IsTrue( - condition: this.summary.Reports.All(r => r.Success), - message: "BenchmarkDotNet failed to execute or collect results of performance tests. See logs above."); - } - - [Test] - public void ReadInt64Property() - { - double optimisticPerfRatio = GetOptimisticPerfRatio(this.summary.Reports); - AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 1.35); - } - - [Test] - public void WriteInt64Property() - { - double optimisticPerfRatio = GetOptimisticPerfRatio(this.summary.Reports); - AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 1.25); - } - - static double GetOptimisticPerfRatio( - IReadOnlyList reports, - [CallerMemberName] string methodName = null) - { - reports = reports.Where(r => r.BenchmarkCase.Descriptor.WorkloadMethod.Name == methodName).ToArray(); - if (reports.Count == 0) - throw new ArgumentException( - message: $"No reports found for {methodName}. " - + "You have to match test method name to benchmark method name or " - + "pass benchmark method name explicitly", - paramName: nameof(methodName)); - - var baseline = reports.Single(r => r.BenchmarkCase.Job.ResolvedId == "baseline").ResultStatistics; - var @new = reports.Single(r => r.BenchmarkCase.Job.ResolvedId != "baseline").ResultStatistics; - - double newTimeOptimistic = @new.Mean - (@new.StandardDeviation + baseline.StandardDeviation) * 0.5; - - return newTimeOptimistic / baseline.Mean; - } - - public static string DeploymentRoot => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - - public static void AssertPerformanceIsBetterOrSame( - double actual, double target, - double wiggleRoom = 1.1, [CallerMemberName] string testName = null) { - double threshold = target * wiggleRoom; - Assert.LessOrEqual(actual, threshold, - $"{testName}: {actual:F3} > {threshold:F3} (target: {target:F3})" - + ": perf result is higher than the failure threshold."); - } - } -} diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj deleted file mode 100644 index bde07ecab..000000000 --- a/src/perf_tests/Python.PerformanceTests.csproj +++ /dev/null @@ -1,48 +0,0 @@ - - - - net472 - false - x64 - x64 - - - - - - PreserveNewest - - - - - - false - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - diff --git a/src/perf_tests/PythonCallingNetBenchmark.cs b/src/perf_tests/PythonCallingNetBenchmark.cs deleted file mode 100644 index d7edd4583..000000000 --- a/src/perf_tests/PythonCallingNetBenchmark.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text; - -using BenchmarkDotNet.Attributes; -using Python.Runtime; - -namespace Python.PerformanceTests -{ - [Config(typeof(BaselineComparisonConfig))] - public class PythonCallingNetBenchmark: BaselineComparisonBenchmarkBase - { - [Benchmark] - public void ReadInt64Property() - { - using (Py.GIL()) - { - var locals = new PyDict(); - locals.SetItem("a", new NetObject().ToPython()); - Exec($@" -s = 0 -for i in range(50000): - s += a.{nameof(NetObject.LongProperty)} -", locals: locals); - } - } - - [Benchmark] - public void WriteInt64Property() { - using (Py.GIL()) { - var locals = new PyDict(); - locals.SetItem("a", new NetObject().ToPython()); - Exec($@" -s = 0 -for i in range(50000): - a.{nameof(NetObject.LongProperty)} += i -", locals: locals); - } - } - - static void Exec(string code, PyDict locals) - { - MethodInfo exec = typeof(PythonEngine).GetMethod(nameof(PythonEngine.Exec)); - object localsArg = typeof(PyObject).Assembly.GetName().Version.Major >= 3 - ? locals : locals.Handle; - exec.Invoke(null, new[] - { - code, localsArg, null - }); - } - } - - class NetObject - { - public long LongProperty { get; set; } = 42; - } -} diff --git a/src/perf_tests/baseline/.gitkeep b/src/perf_tests/baseline/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/python_tests_runner/Python.PythonTestsRunner.csproj b/src/python_tests_runner/Python.PythonTestsRunner.csproj index 63981c424..9f782bd2b 100644 --- a/src/python_tests_runner/Python.PythonTestsRunner.csproj +++ b/src/python_tests_runner/Python.PythonTestsRunner.csproj @@ -1,7 +1,7 @@ - net472;net6.0 + net472;net10.0 @@ -10,17 +10,25 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - 1.0.0 + 1.* all runtime; build; native; contentfiles; analyzers + + + + diff --git a/src/python_tests_runner/PythonTestRunner.cs b/src/python_tests_runner/PythonTestRunner.cs index 05298997b..3df22ec2e 100644 --- a/src/python_tests_runner/PythonTestRunner.cs +++ b/src/python_tests_runner/PythonTestRunner.cs @@ -8,22 +8,29 @@ using NUnit.Framework; using Python.Runtime; -using Python.Test; namespace Python.PythonTestsRunner { public class PythonTestRunner { + string OriginalDirectory; + [OneTimeSetUp] public void SetUp() { PythonEngine.Initialize(); + OriginalDirectory = Environment.CurrentDirectory; + + var codeDir = File.ReadAllText("tests_location.txt").Trim(); + TestContext.Progress.WriteLine($"Changing working directory to {codeDir}"); + Environment.CurrentDirectory = codeDir; } [OneTimeTearDown] public void Dispose() { PythonEngine.Shutdown(); + Environment.CurrentDirectory = OriginalDirectory; } /// @@ -35,6 +42,7 @@ static IEnumerable PythonTestCases() // Add the test that you want to debug here. yield return new[] { "test_indexer", "test_boolean_indexer" }; yield return new[] { "test_delegate", "test_bool_delegate" }; + yield return new[] { "test_subclass", "test_implement_interface_and_class" }; } /// @@ -45,39 +53,15 @@ static IEnumerable PythonTestCases() [TestCaseSource(nameof(PythonTestCases))] public void RunPythonTest(string testFile, string testName) { - // Find the tests directory - string folder = typeof(PythonTestRunner).Assembly.Location; - while (Path.GetFileName(folder) != "src") + using dynamic pytest = Py.Import("pytest"); + + using var args = new PyList(); + args.Append(new PyString($"{testFile}.py::{testName}")); + int res = pytest.main(args); + if (res != 0) { - folder = Path.GetDirectoryName(folder); + Assert.Fail($"Python test {testFile}.{testName} failed"); } - folder = Path.Combine(folder, "..", "tests"); - string path = Path.Combine(folder, testFile + ".py"); - if (!File.Exists(path)) throw new FileNotFoundException("Cannot find test file", path); - - // We could use 'import' below, but importlib gives more helpful error messages than 'import' - // https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly - // Because the Python tests sometimes have relative imports, the module name must be inside the tests package - PythonEngine.Exec($@" -import sys -import os -sys.path.append(os.path.dirname(r'{folder}')) -sys.path.append(os.path.join(r'{folder}', 'fixtures')) -import clr -clr.AddReference('Python.Test') -import tests -module_name = 'tests.{testFile}' -file_path = r'{path}' -import importlib.util -spec = importlib.util.spec_from_file_location(module_name, file_path) -module = importlib.util.module_from_spec(spec) -sys.modules[module_name] = module -try: - spec.loader.exec_module(module) -except ImportError as error: - raise ImportError(str(error) + ' when sys.path=' + os.pathsep.join(sys.path)) -module.{testName}() -"); } } } diff --git a/src/runtime/AssemblyManager.cs b/src/runtime/AssemblyManager.cs index a8bbd1f6c..dcc5aa2f0 100644 --- a/src/runtime/AssemblyManager.cs +++ b/src/runtime/AssemblyManager.cs @@ -123,6 +123,11 @@ private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args) internal static AssemblyName? TryParseAssemblyName(string name) { + // workaround for https://github.com/dotnet/runtime/issues/123951 + if (name.IndexOfAny(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }) >= 0) + { + return null; + } try { return new AssemblyName(name); @@ -200,6 +205,13 @@ static IEnumerable FindAssemblyCandidates(string name) } else { + int invalidCharIndex = head.IndexOfAny(Path.GetInvalidPathChars()); + if (invalidCharIndex >= 0) + { + using var importWarning = Runtime.PyObject_GetAttrString(Exceptions.exceptions_module, "ImportWarning"); + Exceptions.warn($"Path entry '{head}' has invalid char at position {invalidCharIndex}", importWarning.BorrowOrThrow()); + continue; + } path = Path.Combine(head, name); } diff --git a/src/runtime/ClassManager.cs b/src/runtime/ClassManager.cs index 79ab20e82..b884bfa92 100644 --- a/src/runtime/ClassManager.cs +++ b/src/runtime/ClassManager.cs @@ -213,6 +213,7 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p ClassInfo info = GetClassInfo(type, impl); impl.indexer = info.indexer; + impl.del = info.del; impl.richcompare.Clear(); @@ -290,11 +291,13 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p internal static bool ShouldBindMethod(MethodBase mb) { + if (mb is null) throw new ArgumentNullException(nameof(mb)); return (mb.IsPublic || mb.IsFamily || mb.IsFamilyOrAssembly); } internal static bool ShouldBindField(FieldInfo fi) { + if (fi is null) throw new ArgumentNullException(nameof(fi)); return (fi.IsPublic || fi.IsFamily || fi.IsFamilyOrAssembly); } @@ -326,7 +329,7 @@ internal static bool ShouldBindProperty(PropertyInfo pi) internal static bool ShouldBindEvent(EventInfo ei) { - return ShouldBindMethod(ei.GetAddMethod(true)); + return ei.GetAddMethod(true) is { } add && ShouldBindMethod(add); } private static ClassInfo GetClassInfo(Type type, ClassBase impl) @@ -536,6 +539,21 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl) ob = new MethodObject(type, name, mlist); ci.members[name] = ob.AllocObject(); + if (name == nameof(IDictionary.Remove) + && mlist.Any(m => m.DeclaringType?.GetInterfaces() + .Any(i => i.TryGetGenericDefinition() == typeof(IDictionary<,>)) is true)) + { + ci.del = new(); + ci.del.AddRange(mlist.Where(m => !m.IsStatic)); + } + else if (name == nameof(IList.RemoveAt) + && mlist.Any(m => m.DeclaringType?.GetInterfaces() + .Any(i => i.TryGetGenericDefinition() == typeof(IList<>)) is true)) + { + ci.del = new(); + ci.del.AddRange(mlist.Where(m => !m.IsStatic)); + } + if (mlist.Any(OperatorMethod.IsOperatorMethod)) { string pyName = OperatorMethod.GetPyMethodName(name); @@ -546,7 +564,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl) ci.members[pyName] = new MethodObject(type, name, forwardMethods).AllocObject(); // Only methods where only the right operand is the declaring type. if (reverseMethods.Length > 0) - ci.members[pyNameReverse] = new MethodObject(type, name, reverseMethods).AllocObject(); + ci.members[pyNameReverse] = new MethodObject(type, name, reverseMethods, argsReversed: true).AllocObject(); } } @@ -579,6 +597,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl) private class ClassInfo { public Indexer? indexer; + public MethodBinder? del; public readonly Dictionary members = new(); internal ClassInfo() diff --git a/src/runtime/Codecs/IterableDecoder.cs b/src/runtime/Codecs/IterableDecoder.cs index bcc2eca01..b864850d5 100644 --- a/src/runtime/Codecs/IterableDecoder.cs +++ b/src/runtime/Codecs/IterableDecoder.cs @@ -1,55 +1,55 @@ -using System; -using System.Collections.Generic; - -namespace Python.Runtime.Codecs -{ - public class IterableDecoder : IPyObjectDecoder - { - internal static bool IsIterable(Type targetType) - { - //if it is a plain IEnumerable, we can decode it using sequence protocol. - if (targetType == typeof(System.Collections.IEnumerable)) - return true; - - if (!targetType.IsGenericType) - return false; - - return targetType.GetGenericTypeDefinition() == typeof(IEnumerable<>); - } - - internal static bool IsIterable(PyType objectType) - { - return objectType.HasAttr("__iter__"); - } - - public bool CanDecode(PyType objectType, Type targetType) - { - return IsIterable(objectType) && IsIterable(targetType); - } - - public bool TryDecode(PyObject pyObj, out T value) - { - //first see if T is a plan IEnumerable - if (typeof(T) == typeof(System.Collections.IEnumerable)) - { - object enumerable = new CollectionWrappers.IterableWrapper(pyObj); - value = (T)enumerable; - return true; - } - - var elementType = typeof(T).GetGenericArguments()[0]; - var collectionType = typeof(CollectionWrappers.IterableWrapper<>).MakeGenericType(elementType); - - var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); - value = (T)instance; - return true; - } - - public static IterableDecoder Instance { get; } = new IterableDecoder(); - - public static void Register() - { - PyObjectConversions.RegisterDecoder(Instance); - } - } -} +using System; +using System.Collections.Generic; + +namespace Python.Runtime.Codecs +{ + public class IterableDecoder : IPyObjectDecoder + { + internal static bool IsIterable(Type targetType) + { + //if it is a plain IEnumerable, we can decode it using sequence protocol. + if (targetType == typeof(System.Collections.IEnumerable)) + return true; + + if (!targetType.IsGenericType) + return false; + + return targetType.GetGenericTypeDefinition() == typeof(IEnumerable<>); + } + + internal static bool IsIterable(PyType objectType) + { + return objectType.HasAttr("__iter__"); + } + + public bool CanDecode(PyType objectType, Type targetType) + { + return IsIterable(objectType) && IsIterable(targetType); + } + + public bool TryDecode(PyObject pyObj, out T value) + { + //first see if T is a plan IEnumerable + if (typeof(T) == typeof(System.Collections.IEnumerable)) + { + object enumerable = new CollectionWrappers.IterableWrapper(pyObj); + value = (T)enumerable; + return true; + } + + var elementType = typeof(T).GetGenericArguments()[0]; + var collectionType = typeof(CollectionWrappers.IterableWrapper<>).MakeGenericType(elementType); + + var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); + value = (T)instance; + return true; + } + + public static IterableDecoder Instance { get; } = new IterableDecoder(); + + public static void Register() + { + PyObjectConversions.RegisterDecoder(Instance); + } + } +} diff --git a/src/runtime/Codecs/ListDecoder.cs b/src/runtime/Codecs/ListDecoder.cs index 70ff33aaa..5da82851f 100644 --- a/src/runtime/Codecs/ListDecoder.cs +++ b/src/runtime/Codecs/ListDecoder.cs @@ -1,50 +1,50 @@ -using System; -using System.Collections.Generic; - -namespace Python.Runtime.Codecs -{ - public class ListDecoder : IPyObjectDecoder - { - private static bool IsList(Type targetType) - { - if (!targetType.IsGenericType) - return false; - - return targetType.GetGenericTypeDefinition() == typeof(IList<>); - } - - private static bool IsList(PyType objectType) - { - //TODO accept any python object that implements the sequence and list protocols - //must implement sequence protocol to fully implement list protocol - //if (!SequenceDecoder.IsSequence(objectType)) return false; - - //returns wheter the type is a list. - return PythonReferenceComparer.Instance.Equals(objectType, Runtime.PyListType); - } - - public bool CanDecode(PyType objectType, Type targetType) - { - return IsList(objectType) && IsList(targetType); - } - - public bool TryDecode(PyObject pyObj, out T value) - { - if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); - - var elementType = typeof(T).GetGenericArguments()[0]; - Type collectionType = typeof(CollectionWrappers.ListWrapper<>).MakeGenericType(elementType); - - var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); - value = (T)instance; - return true; - } - - public static ListDecoder Instance { get; } = new ListDecoder(); - - public static void Register() - { - PyObjectConversions.RegisterDecoder(Instance); - } - } -} +using System; +using System.Collections.Generic; + +namespace Python.Runtime.Codecs +{ + public class ListDecoder : IPyObjectDecoder + { + private static bool IsList(Type targetType) + { + if (!targetType.IsGenericType) + return false; + + return targetType.GetGenericTypeDefinition() == typeof(IList<>); + } + + private static bool IsList(PyType objectType) + { + //TODO accept any python object that implements the sequence and list protocols + //must implement sequence protocol to fully implement list protocol + //if (!SequenceDecoder.IsSequence(objectType)) return false; + + //returns wheter the type is a list. + return PythonReferenceComparer.Instance.Equals(objectType, Runtime.PyListType); + } + + public bool CanDecode(PyType objectType, Type targetType) + { + return IsList(objectType) && IsList(targetType); + } + + public bool TryDecode(PyObject pyObj, out T value) + { + if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); + + var elementType = typeof(T).GetGenericArguments()[0]; + Type collectionType = typeof(CollectionWrappers.ListWrapper<>).MakeGenericType(elementType); + + var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); + value = (T)instance; + return true; + } + + public static ListDecoder Instance { get; } = new ListDecoder(); + + public static void Register() + { + PyObjectConversions.RegisterDecoder(Instance); + } + } +} diff --git a/src/runtime/Codecs/SequenceDecoder.cs b/src/runtime/Codecs/SequenceDecoder.cs index a539297cd..c5ded4958 100644 --- a/src/runtime/Codecs/SequenceDecoder.cs +++ b/src/runtime/Codecs/SequenceDecoder.cs @@ -1,52 +1,52 @@ -using System; -using System.Collections.Generic; - -namespace Python.Runtime.Codecs -{ - public class SequenceDecoder : IPyObjectDecoder - { - internal static bool IsSequence(Type targetType) - { - if (!targetType.IsGenericType) - return false; - - return targetType.GetGenericTypeDefinition() == typeof(ICollection<>); - } - - internal static bool IsSequence(PyType objectType) - { - //must implement iterable protocol to fully implement sequence protocol - if (!IterableDecoder.IsIterable(objectType)) return false; - +using System; +using System.Collections.Generic; + +namespace Python.Runtime.Codecs +{ + public class SequenceDecoder : IPyObjectDecoder + { + internal static bool IsSequence(Type targetType) + { + if (!targetType.IsGenericType) + return false; + + return targetType.GetGenericTypeDefinition() == typeof(ICollection<>); + } + + internal static bool IsSequence(PyType objectType) + { + //must implement iterable protocol to fully implement sequence protocol + if (!IterableDecoder.IsIterable(objectType)) return false; + //returns wheter it implements the sequence protocol //according to python doc this needs to exclude dict subclasses //but I don't know how to look for that given the objectType //rather than the instance. - return objectType.HasAttr("__getitem__"); - } - - public bool CanDecode(PyType objectType, Type targetType) - { - return IsSequence(objectType) && IsSequence(targetType); - } - - public bool TryDecode(PyObject pyObj, out T value) - { - if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); - - var elementType = typeof(T).GetGenericArguments()[0]; - Type collectionType = typeof(CollectionWrappers.SequenceWrapper<>).MakeGenericType(elementType); - - var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); - value = (T)instance; - return true; - } - - public static SequenceDecoder Instance { get; } = new SequenceDecoder(); - - public static void Register() - { - PyObjectConversions.RegisterDecoder(Instance); - } - } -} + return objectType.HasAttr("__getitem__"); + } + + public bool CanDecode(PyType objectType, Type targetType) + { + return IsSequence(objectType) && IsSequence(targetType); + } + + public bool TryDecode(PyObject pyObj, out T value) + { + if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); + + var elementType = typeof(T).GetGenericArguments()[0]; + Type collectionType = typeof(CollectionWrappers.SequenceWrapper<>).MakeGenericType(elementType); + + var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); + value = (T)instance; + return true; + } + + public static SequenceDecoder Instance { get; } = new SequenceDecoder(); + + public static void Register() + { + PyObjectConversions.RegisterDecoder(Instance); + } + } +} diff --git a/src/runtime/CollectionWrappers/IterableWrapper.cs b/src/runtime/CollectionWrappers/IterableWrapper.cs index d30e584d3..849e3997b 100644 --- a/src/runtime/CollectionWrappers/IterableWrapper.cs +++ b/src/runtime/CollectionWrappers/IterableWrapper.cs @@ -24,18 +24,22 @@ public IEnumerator GetEnumerator() { iterObject = PyIter.GetIter(pyObject); } - - using var _ = iterObject; - while (true) + try { - using var GIL = Py.GIL(); - - if (!iterObject.MoveNext()) + while (true) { - iterObject.Dispose(); - break; + using var _ = Py.GIL(); + if (!iterObject.MoveNext()) + { + break; + } + yield return iterObject.Current.As()!; } - yield return iterObject.Current.As()!; + } + finally + { + using var _ = Py.GIL(); + iterObject.Dispose(); } } } diff --git a/src/runtime/CollectionWrappers/ListWrapper.cs b/src/runtime/CollectionWrappers/ListWrapper.cs index 41ccb8fae..91c156c32 100644 --- a/src/runtime/CollectionWrappers/ListWrapper.cs +++ b/src/runtime/CollectionWrappers/ListWrapper.cs @@ -14,12 +14,14 @@ public T this[int index] { get { + using var _ = Py.GIL(); var item = Runtime.PyList_GetItem(pyObject, index); var pyItem = new PyObject(item); return pyItem.As()!; } set { + using var _ = Py.GIL(); var pyItem = value.ToPython(); var result = Runtime.PyList_SetItem(pyObject, index, new NewReference(pyItem).Steal()); if (result == -1) @@ -37,6 +39,7 @@ public void Insert(int index, T item) if (IsReadOnly) throw new InvalidOperationException("Collection is read-only"); + using var _ = Py.GIL(); var pyItem = item.ToPython(); int result = Runtime.PyList_Insert(pyObject, index, pyItem); diff --git a/src/runtime/CollectionWrappers/SequenceWrapper.cs b/src/runtime/CollectionWrappers/SequenceWrapper.cs index fcc5c23f4..feb0e515d 100644 --- a/src/runtime/CollectionWrappers/SequenceWrapper.cs +++ b/src/runtime/CollectionWrappers/SequenceWrapper.cs @@ -14,10 +14,14 @@ public int Count { get { - var size = Runtime.PySequence_Size(pyObject.Reference); - if (size == -1) + nint size = -1; { - Runtime.CheckExceptionOccurred(); + using var _ = Py.GIL(); + size = Runtime.PySequence_Size(pyObject.Reference); + if (size == -1) + { + Runtime.CheckExceptionOccurred(); + } } return checked((int)size); @@ -38,6 +42,7 @@ public void Clear() { if (IsReadOnly) throw new NotImplementedException(); + using var _ = Py.GIL(); int result = Runtime.PySequence_DelSlice(pyObject, 0, Count); if (result == -1) { @@ -77,12 +82,16 @@ protected bool removeAt(int index) if (index >= Count || index < 0) return false; - int result = Runtime.PySequence_DelItem(pyObject, index); - if (result == 0) - return true; + { + using var _ = Py.GIL(); + int result = Runtime.PySequence_DelItem(pyObject, index); + + if (result == 0) + return true; - Runtime.CheckExceptionOccurred(); + Runtime.CheckExceptionOccurred(); + } return false; } diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index 3c46e9034..50b33e60e 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -133,7 +133,8 @@ internal static NewReference ToPython(object? value, Type type) if (EncodableByUser(type, value)) { var encoded = PyObjectConversions.TryEncode(value, type); - if (encoded != null) { + if (encoded != null) + { return new NewReference(encoded); } } @@ -334,7 +335,7 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType, if (obType.IsGenericType && obType.GetGenericTypeDefinition() == typeof(Nullable<>)) { - if( value == Runtime.PyNone ) + if (value == Runtime.PyNone) { result = null; return true; @@ -361,28 +362,44 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType, // conversions (Python string -> managed string). if (obType == objectType) { - if (Runtime.PyString_Check(value)) + if (Runtime.PyString_CheckExact(value)) { return ToPrimitive(value, stringType, out result, setError); } - if (Runtime.PyBool_Check(value)) + if (Runtime.PyBool_CheckExact(value)) { return ToPrimitive(value, boolType, out result, setError); } - if (Runtime.PyFloat_Check(value)) + if (Runtime.PyFloat_CheckExact(value)) { return ToPrimitive(value, doubleType, out result, setError); } - // give custom codecs a chance to take over conversion of ints and sequences + // give custom codecs a chance to take over conversion + // of ints, sequences, and types derived from primitives BorrowedReference pyType = Runtime.PyObject_TYPE(value); if (PyObjectConversions.TryDecode(value, pyType, obType, out result)) { return true; } + if (Runtime.PyString_Check(value)) + { + return ToPrimitive(value, stringType, out result, setError); + } + + if (Runtime.PyBool_Check(value)) + { + return ToPrimitive(value, boolType, out result, setError); + } + + if (Runtime.PyFloat_Check(value)) + { + return ToPrimitive(value, doubleType, out result, setError); + } + if (Runtime.PyInt_Check(value)) { result = new PyInt(value); @@ -670,10 +687,8 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec { if (Runtime.PyUnicode_GetLength(value) == 1) { - IntPtr unicodePtr = Runtime.PyUnicode_AsUnicode(value); - Char[] buff = new Char[1]; - Marshal.Copy(unicodePtr, buff, 0, 1); - result = buff[0]; + int chr = Runtime.PyUnicode_ReadChar(value, 0); + result = (Char)chr; return true; } goto type_error; @@ -966,5 +981,11 @@ public static PyObject ToPython(this object? o) if (o is null) return Runtime.None; return Converter.ToPython(o, o.GetType()).MoveToPyObject(); } + + public static PyObject ToPythonAs(this T? o) + { + if (o is null) return Runtime.None; + return Converter.ToPython(o, typeof(T)).MoveToPyObject(); + } } } diff --git a/src/runtime/Exceptions.cs b/src/runtime/Exceptions.cs index da095e030..85e56eace 100644 --- a/src/runtime/Exceptions.cs +++ b/src/runtime/Exceptions.cs @@ -270,7 +270,7 @@ public static void warn(string message, BorrowedReference exception, int stackle } using var warn = Runtime.PyObject_GetAttrString(warnings_module.obj, "warn"); - Exceptions.ErrorCheck(warn.Borrow()); + warn.BorrowOrThrow(); using var argsTemp = Runtime.PyTuple_New(3); BorrowedReference args = argsTemp.BorrowOrThrow(); @@ -283,7 +283,7 @@ public static void warn(string message, BorrowedReference exception, int stackle Runtime.PyTuple_SetItem(args, 2, level.StealOrThrow()); using var result = Runtime.PyObject_CallObject(warn.Borrow(), args); - Exceptions.ErrorCheck(result.Borrow()); + result.BorrowOrThrow(); } public static void warn(string message, BorrowedReference exception) diff --git a/src/runtime/Finalizer.cs b/src/runtime/Finalizer.cs index f4b465ecb..5b5ecfcfc 100644 --- a/src/runtime/Finalizer.cs +++ b/src/runtime/Finalizer.cs @@ -191,7 +191,7 @@ internal static void Shutdown() Instance.started = false; } - internal nint DisposeAll() + internal nint DisposeAll(bool disposeObj = true, bool disposeDerived = true, bool disposeBuffer = true) { if (_objQueue.IsEmpty && _derivedQueue.IsEmpty && _bufferQueue.IsEmpty) return 0; @@ -216,7 +216,7 @@ internal nint DisposeAll() try { - while (!_objQueue.IsEmpty) + if (disposeObj) while (!_objQueue.IsEmpty) { if (!_objQueue.TryDequeue(out var obj)) continue; @@ -240,7 +240,7 @@ internal nint DisposeAll() } } - while (!_derivedQueue.IsEmpty) + if (disposeDerived) while (!_derivedQueue.IsEmpty) { if (!_derivedQueue.TryDequeue(out var derived)) continue; @@ -258,7 +258,7 @@ internal nint DisposeAll() collected++; } - while (!_bufferQueue.IsEmpty) + if (disposeBuffer) while (!_bufferQueue.IsEmpty) { if (!_bufferQueue.TryDequeue(out var buffer)) continue; @@ -364,6 +364,8 @@ struct PendingFinalization { public IntPtr PyObj; public BorrowedReference Ref => new(PyObj); + public ManagedType? Managed => ManagedType.GetManagedObject(Ref); + public nint RefCount => Runtime.Refcount(Ref); public int RuntimeRun; #if TRACE_ALLOC public string StackTrace; diff --git a/src/runtime/InternalPythonnetException.cs b/src/runtime/InternalPythonnetException.cs new file mode 100644 index 000000000..d0ea1bece --- /dev/null +++ b/src/runtime/InternalPythonnetException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Python.Runtime; + +public class InternalPythonnetException : Exception +{ + public InternalPythonnetException(string message, Exception innerException) + : base(message, innerException) { } +} diff --git a/src/runtime/Loader.cs b/src/runtime/Loader.cs index 516b9ab9c..c0e964abc 100644 --- a/src/runtime/Loader.cs +++ b/src/runtime/Loader.cs @@ -12,7 +12,7 @@ public unsafe static int Initialize(IntPtr data, int size) { try { - var dllPath = Encoding.UTF8.GetString((byte*)data.ToPointer(), size); + var dllPath = Encodings.UTF8.GetString((byte*)data.ToPointer(), size); if (!string.IsNullOrEmpty(dllPath)) { @@ -33,7 +33,7 @@ public unsafe static int Initialize(IntPtr data, int size) ); return 1; } - + return 0; } @@ -41,7 +41,7 @@ public unsafe static int Shutdown(IntPtr data, int size) { try { - var command = Encoding.UTF8.GetString((byte*)data.ToPointer(), size); + var command = Encodings.UTF8.GetString((byte*)data.ToPointer(), size); if (command == "full_shutdown") { diff --git a/src/runtime/MethodBinder.cs b/src/runtime/MethodBinder.cs index 07ed4fe22..af75a34b4 100644 --- a/src/runtime/MethodBinder.cs +++ b/src/runtime/MethodBinder.cs @@ -28,9 +28,12 @@ internal class MethodBinder [NonSerialized] public bool init = false; + public const bool DefaultAllowThreads = true; public bool allow_threads = DefaultAllowThreads; + public bool argsReversed = false; + internal MethodBinder() { list = new List(); @@ -51,6 +54,11 @@ internal void AddMethod(MethodBase m) list.Add(m); } + internal void AddRange(IEnumerable methods) + { + list.AddRange(methods.Select(m => new MaybeMethodBase(m))); + } + /// /// Given a sequence of MethodInfo and a sequence of types, return the /// MethodInfo that matches the signature represented by those types. @@ -363,10 +371,10 @@ public MismatchedMethod(Exception exception, MethodBase mb) _methods = GetMethods(); } - return Bind(inst, args, kwargDict, _methods, matchGenerics: true); + return Bind(inst, args, kwargDict, _methods, matchGenerics: true, argsReversed); } - static Binding? Bind(BorrowedReference inst, BorrowedReference args, Dictionary kwargDict, MethodBase[] methods, bool matchGenerics) + private static Binding? Bind(BorrowedReference inst, BorrowedReference args, Dictionary kwargDict, MethodBase[] methods, bool matchGenerics, bool argsReversed = false) { var pynargs = (int)Runtime.PyTuple_Size(args); var isGeneric = false; @@ -386,7 +394,7 @@ public MismatchedMethod(Exception exception, MethodBase mb) // Binary operator methods will have 2 CLR args but only one Python arg // (unary operators will have 1 less each), since Python operator methods are bound. isOperator = isOperator && pynargs == pi.Length - 1; - bool isReverse = isOperator && OperatorMethod.IsReverse((MethodInfo)mi); // Only cast if isOperator. + bool isReverse = isOperator && argsReversed; // Only cast if isOperator. if (isReverse && OperatorMethod.IsComparisonOp((MethodInfo)mi)) continue; // Comparison operators in Python have no reverse mode. if (!MatchesArgumentCount(pynargs, pi, kwargDict, out bool paramsArray, out ArrayList? defaultArgList, out int kwargsMatched, out int defaultsNeeded) && !isOperator) @@ -394,12 +402,14 @@ public MismatchedMethod(Exception exception, MethodBase mb) continue; } // Preprocessing pi to remove either the first or second argument. - if (isOperator && !isReverse) { + if (isOperator && !isReverse) + { // The first Python arg is the right operand, while the bound instance is the left. // We need to skip the first (left operand) CLR argument. pi = pi.Skip(1).ToArray(); } - else if (isOperator && isReverse) { + else if (isOperator && isReverse) + { // The first Python arg is the left operand. // We need to take the first CLR argument. pi = pi.Take(1).ToArray(); diff --git a/src/runtime/Native/CustomMarshaler.cs b/src/runtime/Native/CustomMarshaler.cs index 62c027150..299af3a33 100644 --- a/src/runtime/Native/CustomMarshaler.cs +++ b/src/runtime/Native/CustomMarshaler.cs @@ -42,7 +42,7 @@ public int GetNativeDataSize() internal class UcsMarshaler : MarshalerBase { internal static readonly int _UCS = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 2 : 4; - internal static readonly Encoding PyEncoding = _UCS == 2 ? Encoding.Unicode : Encoding.UTF32; + internal static readonly Encoding PyEncoding = _UCS == 2 ? Encodings.UTF16 : Encodings.UTF32; private static readonly MarshalerBase Instance = new UcsMarshaler(); public override IntPtr MarshalManagedToNative(object managedObj) diff --git a/src/runtime/Native/NativeTypeSpec.cs b/src/runtime/Native/NativeTypeSpec.cs index 8b84df536..90e07afd7 100644 --- a/src/runtime/Native/NativeTypeSpec.cs +++ b/src/runtime/Native/NativeTypeSpec.cs @@ -17,7 +17,7 @@ public NativeTypeSpec(TypeSpec spec) { if (spec is null) throw new ArgumentNullException(nameof(spec)); - this.Name = new StrPtr(spec.Name, Encoding.UTF8); + this.Name = new StrPtr(spec.Name); this.BasicSize = spec.BasicSize; this.ItemSize = spec.ItemSize; this.Flags = (int)spec.Flags; diff --git a/src/runtime/Native/PyIdentifier_.cs b/src/runtime/Native/PyIdentifier_.cs index 1ea2b704c..8d693c09f 100644 --- a/src/runtime/Native/PyIdentifier_.cs +++ b/src/runtime/Native/PyIdentifier_.cs @@ -13,8 +13,6 @@ static class PyIdentifier public static BorrowedReference __doc__ => new(f__doc__); static IntPtr f__class__; public static BorrowedReference __class__ => new(f__class__); - static IntPtr f__clear_reentry_guard__; - public static BorrowedReference __clear_reentry_guard__ => new(f__clear_reentry_guard__); static IntPtr f__module__; public static BorrowedReference __module__ => new(f__module__); static IntPtr f__file__; @@ -24,6 +22,8 @@ static class PyIdentifier static IntPtr f__self__; public static BorrowedReference __self__ => new(f__self__); static IntPtr f__annotations__; + public static BorrowedReference __dictoffset__ => new(f__dictoffset__); + static IntPtr f__dictoffset__; public static BorrowedReference __annotations__ => new(f__annotations__); static IntPtr f__init__; public static BorrowedReference __init__ => new(f__init__); @@ -51,12 +51,12 @@ static partial class InternString "__dict__", "__doc__", "__class__", - "__clear_reentry_guard__", "__module__", "__file__", "__slots__", "__self__", "__annotations__", + "__dictoffset__", "__init__", "__repr__", "__import__", diff --git a/src/runtime/Native/PyIdentifier_.tt b/src/runtime/Native/PyIdentifier_.tt index 9350cde43..72dc1d70e 100644 --- a/src/runtime/Native/PyIdentifier_.tt +++ b/src/runtime/Native/PyIdentifier_.tt @@ -7,12 +7,12 @@ "__dict__", "__doc__", "__class__", - "__clear_reentry_guard__", "__module__", "__file__", "__slots__", "__self__", "__annotations__", + "__dictoffset__", "__init__", "__repr__", diff --git a/src/runtime/Native/StrPtr.cs b/src/runtime/Native/StrPtr.cs index 4f73be9b5..c9f4db660 100644 --- a/src/runtime/Native/StrPtr.cs +++ b/src/runtime/Native/StrPtr.cs @@ -10,6 +10,8 @@ struct StrPtr : IDisposable public IntPtr RawPointer { get; set; } unsafe byte* Bytes => (byte*)this.RawPointer; + public unsafe StrPtr(string value) : this(value, Encodings.UTF8) {} + public unsafe StrPtr(string value, Encoding encoding) { if (value is null) throw new ArgumentNullException(nameof(value)); diff --git a/src/runtime/Native/TypeOffset.cs b/src/runtime/Native/TypeOffset.cs index 803b86bfa..282107432 100644 --- a/src/runtime/Native/TypeOffset.cs +++ b/src/runtime/Native/TypeOffset.cs @@ -77,6 +77,8 @@ static partial class TypeOffset internal static int tp_setattro { get; private set; } internal static int tp_str { get; private set; } internal static int tp_traverse { get; private set; } + // Special case: Only available in Python 3.14 onwards, set to -1 by default + internal static int ht_token { get; private set; } = -1; internal static void Use(ITypeOffsets offsets, int extraHeadOffset) { @@ -89,6 +91,19 @@ internal static void Use(ITypeOffsets offsets, int extraHeadOffset) slotNames.Add(offsetProperty.Name); var sourceProperty = typeof(ITypeOffsets).GetProperty(offsetProperty.Name); + if (sourceProperty == null) + { + if ((int)offsetProperty.GetValue(null) == -1) + { + // Skip, this is an optional offset value + continue; + } + else + { + throw new Exception($"No offset defined for necessary slot {offsetProperty.Name}"); + } + } + int value = (int)sourceProperty.GetValue(offsets, null); value += extraHeadOffset; offsetProperty.SetValue(obj: null, value: value, index: null); diff --git a/src/runtime/Native/TypeOffset311.cs b/src/runtime/Native/TypeOffset311.cs new file mode 100644 index 000000000..de5afacb9 --- /dev/null +++ b/src/runtime/Native/TypeOffset311.cs @@ -0,0 +1,141 @@ + +// Auto-generated by geninterop.py. +// DO NOT MODIFY BY HAND. + +// Python 3.11: ABI flags: '' + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +using Python.Runtime.Native; + +namespace Python.Runtime +{ + [SuppressMessage("Style", "IDE1006:Naming Styles", + Justification = "Following CPython", + Scope = "type")] + + [StructLayout(LayoutKind.Sequential)] + internal class TypeOffset311 : GeneratedTypeOffsets, ITypeOffsets + { + public TypeOffset311() { } + // Auto-generated from PyHeapTypeObject in Python.h + public int ob_refcnt { get; private set; } + public int ob_type { get; private set; } + public int ob_size { get; private set; } + public int tp_name { get; private set; } + public int tp_basicsize { get; private set; } + public int tp_itemsize { get; private set; } + public int tp_dealloc { get; private set; } + public int tp_vectorcall_offset { get; private set; } + public int tp_getattr { get; private set; } + public int tp_setattr { get; private set; } + public int tp_as_async { get; private set; } + public int tp_repr { get; private set; } + public int tp_as_number { get; private set; } + public int tp_as_sequence { get; private set; } + public int tp_as_mapping { get; private set; } + public int tp_hash { get; private set; } + public int tp_call { get; private set; } + public int tp_str { get; private set; } + public int tp_getattro { get; private set; } + public int tp_setattro { get; private set; } + public int tp_as_buffer { get; private set; } + public int tp_flags { get; private set; } + public int tp_doc { get; private set; } + public int tp_traverse { get; private set; } + public int tp_clear { get; private set; } + public int tp_richcompare { get; private set; } + public int tp_weaklistoffset { get; private set; } + public int tp_iter { get; private set; } + public int tp_iternext { get; private set; } + public int tp_methods { get; private set; } + public int tp_members { get; private set; } + public int tp_getset { get; private set; } + public int tp_base { get; private set; } + public int tp_dict { get; private set; } + public int tp_descr_get { get; private set; } + public int tp_descr_set { get; private set; } + public int tp_dictoffset { get; private set; } + public int tp_init { get; private set; } + public int tp_alloc { get; private set; } + public int tp_new { get; private set; } + public int tp_free { get; private set; } + public int tp_is_gc { get; private set; } + public int tp_bases { get; private set; } + public int tp_mro { get; private set; } + public int tp_cache { get; private set; } + public int tp_subclasses { get; private set; } + public int tp_weaklist { get; private set; } + public int tp_del { get; private set; } + public int tp_version_tag { get; private set; } + public int tp_finalize { get; private set; } + public int tp_vectorcall { get; private set; } + public int am_await { get; private set; } + public int am_aiter { get; private set; } + public int am_anext { get; private set; } + public int am_send { get; private set; } + public int nb_add { get; private set; } + public int nb_subtract { get; private set; } + public int nb_multiply { get; private set; } + public int nb_remainder { get; private set; } + public int nb_divmod { get; private set; } + public int nb_power { get; private set; } + public int nb_negative { get; private set; } + public int nb_positive { get; private set; } + public int nb_absolute { get; private set; } + public int nb_bool { get; private set; } + public int nb_invert { get; private set; } + public int nb_lshift { get; private set; } + public int nb_rshift { get; private set; } + public int nb_and { get; private set; } + public int nb_xor { get; private set; } + public int nb_or { get; private set; } + public int nb_int { get; private set; } + public int nb_reserved { get; private set; } + public int nb_float { get; private set; } + public int nb_inplace_add { get; private set; } + public int nb_inplace_subtract { get; private set; } + public int nb_inplace_multiply { get; private set; } + public int nb_inplace_remainder { get; private set; } + public int nb_inplace_power { get; private set; } + public int nb_inplace_lshift { get; private set; } + public int nb_inplace_rshift { get; private set; } + public int nb_inplace_and { get; private set; } + public int nb_inplace_xor { get; private set; } + public int nb_inplace_or { get; private set; } + public int nb_floor_divide { get; private set; } + public int nb_true_divide { get; private set; } + public int nb_inplace_floor_divide { get; private set; } + public int nb_inplace_true_divide { get; private set; } + public int nb_index { get; private set; } + public int nb_matrix_multiply { get; private set; } + public int nb_inplace_matrix_multiply { get; private set; } + public int mp_length { get; private set; } + public int mp_subscript { get; private set; } + public int mp_ass_subscript { get; private set; } + public int sq_length { get; private set; } + public int sq_concat { get; private set; } + public int sq_repeat { get; private set; } + public int sq_item { get; private set; } + public int was_sq_slice { get; private set; } + public int sq_ass_item { get; private set; } + public int was_sq_ass_slice { get; private set; } + public int sq_contains { get; private set; } + public int sq_inplace_concat { get; private set; } + public int sq_inplace_repeat { get; private set; } + public int bf_getbuffer { get; private set; } + public int bf_releasebuffer { get; private set; } + public int name { get; private set; } + public int ht_slots { get; private set; } + public int qualname { get; private set; } + public int ht_cached_keys { get; private set; } + public int ht_module { get; private set; } + public int _ht_tpname { get; private set; } + public int spec_cache_getitem { get; private set; } + } +} diff --git a/src/runtime/Native/TypeOffset312.cs b/src/runtime/Native/TypeOffset312.cs new file mode 100644 index 000000000..8ba30e816 --- /dev/null +++ b/src/runtime/Native/TypeOffset312.cs @@ -0,0 +1,144 @@ + +// Auto-generated by geninterop.py. +// DO NOT MODIFY BY HAND. + +// Python 3.12: ABI flags: '' + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +using Python.Runtime.Native; + +namespace Python.Runtime +{ + [SuppressMessage("Style", "IDE1006:Naming Styles", + Justification = "Following CPython", + Scope = "type")] + + [StructLayout(LayoutKind.Sequential)] + internal class TypeOffset312 : GeneratedTypeOffsets, ITypeOffsets + { + public TypeOffset312() { } + // Auto-generated from PyHeapTypeObject in Python.h + public int ob_refcnt { get; private set; } + public int ob_type { get; private set; } + public int ob_size { get; private set; } + public int tp_name { get; private set; } + public int tp_basicsize { get; private set; } + public int tp_itemsize { get; private set; } + public int tp_dealloc { get; private set; } + public int tp_vectorcall_offset { get; private set; } + public int tp_getattr { get; private set; } + public int tp_setattr { get; private set; } + public int tp_as_async { get; private set; } + public int tp_repr { get; private set; } + public int tp_as_number { get; private set; } + public int tp_as_sequence { get; private set; } + public int tp_as_mapping { get; private set; } + public int tp_hash { get; private set; } + public int tp_call { get; private set; } + public int tp_str { get; private set; } + public int tp_getattro { get; private set; } + public int tp_setattro { get; private set; } + public int tp_as_buffer { get; private set; } + public int tp_flags { get; private set; } + public int tp_doc { get; private set; } + public int tp_traverse { get; private set; } + public int tp_clear { get; private set; } + public int tp_richcompare { get; private set; } + public int tp_weaklistoffset { get; private set; } + public int tp_iter { get; private set; } + public int tp_iternext { get; private set; } + public int tp_methods { get; private set; } + public int tp_members { get; private set; } + public int tp_getset { get; private set; } + public int tp_base { get; private set; } + public int tp_dict { get; private set; } + public int tp_descr_get { get; private set; } + public int tp_descr_set { get; private set; } + public int tp_dictoffset { get; private set; } + public int tp_init { get; private set; } + public int tp_alloc { get; private set; } + public int tp_new { get; private set; } + public int tp_free { get; private set; } + public int tp_is_gc { get; private set; } + public int tp_bases { get; private set; } + public int tp_mro { get; private set; } + public int tp_cache { get; private set; } + public int tp_subclasses { get; private set; } + public int tp_weaklist { get; private set; } + public int tp_del { get; private set; } + public int tp_version_tag { get; private set; } + public int tp_finalize { get; private set; } + public int tp_vectorcall { get; private set; } + public int tp_watched { get; private set; } + public int am_await { get; private set; } + public int am_aiter { get; private set; } + public int am_anext { get; private set; } + public int am_send { get; private set; } + public int nb_add { get; private set; } + public int nb_subtract { get; private set; } + public int nb_multiply { get; private set; } + public int nb_remainder { get; private set; } + public int nb_divmod { get; private set; } + public int nb_power { get; private set; } + public int nb_negative { get; private set; } + public int nb_positive { get; private set; } + public int nb_absolute { get; private set; } + public int nb_bool { get; private set; } + public int nb_invert { get; private set; } + public int nb_lshift { get; private set; } + public int nb_rshift { get; private set; } + public int nb_and { get; private set; } + public int nb_xor { get; private set; } + public int nb_or { get; private set; } + public int nb_int { get; private set; } + public int nb_reserved { get; private set; } + public int nb_float { get; private set; } + public int nb_inplace_add { get; private set; } + public int nb_inplace_subtract { get; private set; } + public int nb_inplace_multiply { get; private set; } + public int nb_inplace_remainder { get; private set; } + public int nb_inplace_power { get; private set; } + public int nb_inplace_lshift { get; private set; } + public int nb_inplace_rshift { get; private set; } + public int nb_inplace_and { get; private set; } + public int nb_inplace_xor { get; private set; } + public int nb_inplace_or { get; private set; } + public int nb_floor_divide { get; private set; } + public int nb_true_divide { get; private set; } + public int nb_inplace_floor_divide { get; private set; } + public int nb_inplace_true_divide { get; private set; } + public int nb_index { get; private set; } + public int nb_matrix_multiply { get; private set; } + public int nb_inplace_matrix_multiply { get; private set; } + public int mp_length { get; private set; } + public int mp_subscript { get; private set; } + public int mp_ass_subscript { get; private set; } + public int sq_length { get; private set; } + public int sq_concat { get; private set; } + public int sq_repeat { get; private set; } + public int sq_item { get; private set; } + public int was_sq_slice { get; private set; } + public int sq_ass_item { get; private set; } + public int was_sq_ass_slice { get; private set; } + public int sq_contains { get; private set; } + public int sq_inplace_concat { get; private set; } + public int sq_inplace_repeat { get; private set; } + public int bf_getbuffer { get; private set; } + public int bf_releasebuffer { get; private set; } + public int name { get; private set; } + public int ht_slots { get; private set; } + public int qualname { get; private set; } + public int ht_cached_keys { get; private set; } + public int ht_module { get; private set; } + public int _ht_tpname { get; private set; } + public int spec_cache_getitem { get; private set; } + public int getitem_version { get; private set; } + } +} + diff --git a/src/runtime/Native/TypeOffset313.cs b/src/runtime/Native/TypeOffset313.cs new file mode 100644 index 000000000..4c2e71295 --- /dev/null +++ b/src/runtime/Native/TypeOffset313.cs @@ -0,0 +1,152 @@ + +// Auto-generated by geninterop.py. +// DO NOT MODIFY BY HAND. + +// Python 3.13: ABI flags: '' + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +using Python.Runtime.Native; + +namespace Python.Runtime +{ + [SuppressMessage("Style", "IDE1006:Naming Styles", + Justification = "Following CPython", + Scope = "type")] + + [StructLayout(LayoutKind.Sequential)] + internal class TypeOffset313 : GeneratedTypeOffsets, ITypeOffsets + { + public TypeOffset313() { } + // Auto-generated from PyHeapTypeObject in Python.h + public int ob_refcnt { get; private set; } + public int ob_type { get; private set; } + public int ob_size { get; private set; } + public int tp_name { get; private set; } + public int tp_basicsize { get; private set; } + public int tp_itemsize { get; private set; } + public int tp_dealloc { get; private set; } + public int tp_vectorcall_offset { get; private set; } + public int tp_getattr { get; private set; } + public int tp_setattr { get; private set; } + public int tp_as_async { get; private set; } + public int tp_repr { get; private set; } + public int tp_as_number { get; private set; } + public int tp_as_sequence { get; private set; } + public int tp_as_mapping { get; private set; } + public int tp_hash { get; private set; } + public int tp_call { get; private set; } + public int tp_str { get; private set; } + public int tp_getattro { get; private set; } + public int tp_setattro { get; private set; } + public int tp_as_buffer { get; private set; } + public int tp_flags { get; private set; } + public int tp_doc { get; private set; } + public int tp_traverse { get; private set; } + public int tp_clear { get; private set; } + public int tp_richcompare { get; private set; } + public int tp_weaklistoffset { get; private set; } + public int tp_iter { get; private set; } + public int tp_iternext { get; private set; } + public int tp_methods { get; private set; } + public int tp_members { get; private set; } + public int tp_getset { get; private set; } + public int tp_base { get; private set; } + public int tp_dict { get; private set; } + public int tp_descr_get { get; private set; } + public int tp_descr_set { get; private set; } + public int tp_dictoffset { get; private set; } + public int tp_init { get; private set; } + public int tp_alloc { get; private set; } + public int tp_new { get; private set; } + public int tp_free { get; private set; } + public int tp_is_gc { get; private set; } + public int tp_bases { get; private set; } + public int tp_mro { get; private set; } + public int tp_cache { get; private set; } + public int tp_subclasses { get; private set; } + public int tp_weaklist { get; private set; } + public int tp_del { get; private set; } + public int tp_version_tag { get; private set; } + public int tp_finalize { get; private set; } + public int tp_vectorcall { get; private set; } + // This is an error in our generator: + // + // The fields below are actually not pointers (like we incorrectly + // assume for all other fields) but instead a char (1 byte) and a short + // (2 bytes). By dropping one of the fields, we still get the correct + // overall size of the struct. + public int tp_watched { get; private set; } + // public int tp_versions_used { get; private set; } + public int am_await { get; private set; } + public int am_aiter { get; private set; } + public int am_anext { get; private set; } + public int am_send { get; private set; } + public int nb_add { get; private set; } + public int nb_subtract { get; private set; } + public int nb_multiply { get; private set; } + public int nb_remainder { get; private set; } + public int nb_divmod { get; private set; } + public int nb_power { get; private set; } + public int nb_negative { get; private set; } + public int nb_positive { get; private set; } + public int nb_absolute { get; private set; } + public int nb_bool { get; private set; } + public int nb_invert { get; private set; } + public int nb_lshift { get; private set; } + public int nb_rshift { get; private set; } + public int nb_and { get; private set; } + public int nb_xor { get; private set; } + public int nb_or { get; private set; } + public int nb_int { get; private set; } + public int nb_reserved { get; private set; } + public int nb_float { get; private set; } + public int nb_inplace_add { get; private set; } + public int nb_inplace_subtract { get; private set; } + public int nb_inplace_multiply { get; private set; } + public int nb_inplace_remainder { get; private set; } + public int nb_inplace_power { get; private set; } + public int nb_inplace_lshift { get; private set; } + public int nb_inplace_rshift { get; private set; } + public int nb_inplace_and { get; private set; } + public int nb_inplace_xor { get; private set; } + public int nb_inplace_or { get; private set; } + public int nb_floor_divide { get; private set; } + public int nb_true_divide { get; private set; } + public int nb_inplace_floor_divide { get; private set; } + public int nb_inplace_true_divide { get; private set; } + public int nb_index { get; private set; } + public int nb_matrix_multiply { get; private set; } + public int nb_inplace_matrix_multiply { get; private set; } + public int mp_length { get; private set; } + public int mp_subscript { get; private set; } + public int mp_ass_subscript { get; private set; } + public int sq_length { get; private set; } + public int sq_concat { get; private set; } + public int sq_repeat { get; private set; } + public int sq_item { get; private set; } + public int was_sq_slice { get; private set; } + public int sq_ass_item { get; private set; } + public int was_sq_ass_slice { get; private set; } + public int sq_contains { get; private set; } + public int sq_inplace_concat { get; private set; } + public int sq_inplace_repeat { get; private set; } + public int bf_getbuffer { get; private set; } + public int bf_releasebuffer { get; private set; } + public int name { get; private set; } + public int ht_slots { get; private set; } + public int qualname { get; private set; } + public int ht_cached_keys { get; private set; } + public int ht_module { get; private set; } + public int _ht_tpname { get; private set; } + public int spec_cache_getitem { get; private set; } + public int getitem_version { get; private set; } + public int init { get; private set; } + } +} + diff --git a/src/runtime/Native/TypeOffset314.cs b/src/runtime/Native/TypeOffset314.cs new file mode 100644 index 000000000..28101ba12 --- /dev/null +++ b/src/runtime/Native/TypeOffset314.cs @@ -0,0 +1,153 @@ + +// Auto-generated by geninterop.py. +// DO NOT MODIFY BY HAND. + +// Python 3.14: ABI flags: '' + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +using Python.Runtime.Native; + +namespace Python.Runtime +{ + [SuppressMessage("Style", "IDE1006:Naming Styles", + Justification = "Following CPython", + Scope = "type")] + + [StructLayout(LayoutKind.Sequential)] + internal class TypeOffset314 : GeneratedTypeOffsets, ITypeOffsets + { + public TypeOffset314() { } + // Auto-generated from PyHeapTypeObject in Python.h + public int ob_refcnt_full { get; private set; } + public int ob_type { get; private set; } + public int ob_size { get; private set; } + public int tp_name { get; private set; } + public int tp_basicsize { get; private set; } + public int tp_itemsize { get; private set; } + public int tp_dealloc { get; private set; } + public int tp_vectorcall_offset { get; private set; } + public int tp_getattr { get; private set; } + public int tp_setattr { get; private set; } + public int tp_as_async { get; private set; } + public int tp_repr { get; private set; } + public int tp_as_number { get; private set; } + public int tp_as_sequence { get; private set; } + public int tp_as_mapping { get; private set; } + public int tp_hash { get; private set; } + public int tp_call { get; private set; } + public int tp_str { get; private set; } + public int tp_getattro { get; private set; } + public int tp_setattro { get; private set; } + public int tp_as_buffer { get; private set; } + public int tp_flags { get; private set; } + public int tp_doc { get; private set; } + public int tp_traverse { get; private set; } + public int tp_clear { get; private set; } + public int tp_richcompare { get; private set; } + public int tp_weaklistoffset { get; private set; } + public int tp_iter { get; private set; } + public int tp_iternext { get; private set; } + public int tp_methods { get; private set; } + public int tp_members { get; private set; } + public int tp_getset { get; private set; } + public int tp_base { get; private set; } + public int tp_dict { get; private set; } + public int tp_descr_get { get; private set; } + public int tp_descr_set { get; private set; } + public int tp_dictoffset { get; private set; } + public int tp_init { get; private set; } + public int tp_alloc { get; private set; } + public int tp_new { get; private set; } + public int tp_free { get; private set; } + public int tp_is_gc { get; private set; } + public int tp_bases { get; private set; } + public int tp_mro { get; private set; } + public int tp_cache { get; private set; } + public int tp_subclasses { get; private set; } + public int tp_weaklist { get; private set; } + public int tp_del { get; private set; } + public int tp_version_tag { get; private set; } + public int tp_finalize { get; private set; } + public int tp_vectorcall { get; private set; } + // This is an error in our generator: + // + // The fields below are actually not pointers (like we incorrectly + // assume for all other fields) but instead a char (1 byte) and a short + // (2 bytes). By dropping one of the fields, we still get the correct + // overall size of the struct. + public int tp_watched { get; private set; } + // public int tp_versions_used { get; private set; } + public int am_await { get; private set; } + public int am_aiter { get; private set; } + public int am_anext { get; private set; } + public int am_send { get; private set; } + public int nb_add { get; private set; } + public int nb_subtract { get; private set; } + public int nb_multiply { get; private set; } + public int nb_remainder { get; private set; } + public int nb_divmod { get; private set; } + public int nb_power { get; private set; } + public int nb_negative { get; private set; } + public int nb_positive { get; private set; } + public int nb_absolute { get; private set; } + public int nb_bool { get; private set; } + public int nb_invert { get; private set; } + public int nb_lshift { get; private set; } + public int nb_rshift { get; private set; } + public int nb_and { get; private set; } + public int nb_xor { get; private set; } + public int nb_or { get; private set; } + public int nb_int { get; private set; } + public int nb_reserved { get; private set; } + public int nb_float { get; private set; } + public int nb_inplace_add { get; private set; } + public int nb_inplace_subtract { get; private set; } + public int nb_inplace_multiply { get; private set; } + public int nb_inplace_remainder { get; private set; } + public int nb_inplace_power { get; private set; } + public int nb_inplace_lshift { get; private set; } + public int nb_inplace_rshift { get; private set; } + public int nb_inplace_and { get; private set; } + public int nb_inplace_xor { get; private set; } + public int nb_inplace_or { get; private set; } + public int nb_floor_divide { get; private set; } + public int nb_true_divide { get; private set; } + public int nb_inplace_floor_divide { get; private set; } + public int nb_inplace_true_divide { get; private set; } + public int nb_index { get; private set; } + public int nb_matrix_multiply { get; private set; } + public int nb_inplace_matrix_multiply { get; private set; } + public int mp_length { get; private set; } + public int mp_subscript { get; private set; } + public int mp_ass_subscript { get; private set; } + public int sq_length { get; private set; } + public int sq_concat { get; private set; } + public int sq_repeat { get; private set; } + public int sq_item { get; private set; } + public int was_sq_slice { get; private set; } + public int sq_ass_item { get; private set; } + public int was_sq_ass_slice { get; private set; } + public int sq_contains { get; private set; } + public int sq_inplace_concat { get; private set; } + public int sq_inplace_repeat { get; private set; } + public int bf_getbuffer { get; private set; } + public int bf_releasebuffer { get; private set; } + public int name { get; private set; } + public int ht_slots { get; private set; } + public int qualname { get; private set; } + public int ht_cached_keys { get; private set; } + public int ht_module { get; private set; } + public int _ht_tpname { get; private set; } + public int ht_token { get; private set; } + public int spec_cache_getitem { get; private set; } + public int getitem_version { get; private set; } + public int init { get; private set; } + } +} + diff --git a/src/runtime/Native/TypeOffset37.cs b/src/runtime/Native/TypeOffset37.cs deleted file mode 100644 index 951cb1068..000000000 --- a/src/runtime/Native/TypeOffset37.cs +++ /dev/null @@ -1,136 +0,0 @@ - -// Auto-generated by geninterop.py. -// DO NOT MODIFY BY HAND. - -// Python 3.7: ABI flags: '' - -// ReSharper disable InconsistentNaming -// ReSharper disable IdentifierTypo - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -using Python.Runtime.Native; - -namespace Python.Runtime -{ - [SuppressMessage("Style", "IDE1006:Naming Styles", - Justification = "Following CPython", - Scope = "type")] - - [StructLayout(LayoutKind.Sequential)] - internal class TypeOffset37 : GeneratedTypeOffsets, ITypeOffsets - { - public TypeOffset37() { } - // Auto-generated from PyHeapTypeObject in Python.h - public int ob_refcnt { get; private set; } - public int ob_type { get; private set; } - public int ob_size { get; private set; } - public int tp_name { get; private set; } - public int tp_basicsize { get; private set; } - public int tp_itemsize { get; private set; } - public int tp_dealloc { get; private set; } - public int tp_print { get; private set; } - public int tp_getattr { get; private set; } - public int tp_setattr { get; private set; } - public int tp_as_async { get; private set; } - public int tp_repr { get; private set; } - public int tp_as_number { get; private set; } - public int tp_as_sequence { get; private set; } - public int tp_as_mapping { get; private set; } - public int tp_hash { get; private set; } - public int tp_call { get; private set; } - public int tp_str { get; private set; } - public int tp_getattro { get; private set; } - public int tp_setattro { get; private set; } - public int tp_as_buffer { get; private set; } - public int tp_flags { get; private set; } - public int tp_doc { get; private set; } - public int tp_traverse { get; private set; } - public int tp_clear { get; private set; } - public int tp_richcompare { get; private set; } - public int tp_weaklistoffset { get; private set; } - public int tp_iter { get; private set; } - public int tp_iternext { get; private set; } - public int tp_methods { get; private set; } - public int tp_members { get; private set; } - public int tp_getset { get; private set; } - public int tp_base { get; private set; } - public int tp_dict { get; private set; } - public int tp_descr_get { get; private set; } - public int tp_descr_set { get; private set; } - public int tp_dictoffset { get; private set; } - public int tp_init { get; private set; } - public int tp_alloc { get; private set; } - public int tp_new { get; private set; } - public int tp_free { get; private set; } - public int tp_is_gc { get; private set; } - public int tp_bases { get; private set; } - public int tp_mro { get; private set; } - public int tp_cache { get; private set; } - public int tp_subclasses { get; private set; } - public int tp_weaklist { get; private set; } - public int tp_del { get; private set; } - public int tp_version_tag { get; private set; } - public int tp_finalize { get; private set; } - public int am_await { get; private set; } - public int am_aiter { get; private set; } - public int am_anext { get; private set; } - public int nb_add { get; private set; } - public int nb_subtract { get; private set; } - public int nb_multiply { get; private set; } - public int nb_remainder { get; private set; } - public int nb_divmod { get; private set; } - public int nb_power { get; private set; } - public int nb_negative { get; private set; } - public int nb_positive { get; private set; } - public int nb_absolute { get; private set; } - public int nb_bool { get; private set; } - public int nb_invert { get; private set; } - public int nb_lshift { get; private set; } - public int nb_rshift { get; private set; } - public int nb_and { get; private set; } - public int nb_xor { get; private set; } - public int nb_or { get; private set; } - public int nb_int { get; private set; } - public int nb_reserved { get; private set; } - public int nb_float { get; private set; } - public int nb_inplace_add { get; private set; } - public int nb_inplace_subtract { get; private set; } - public int nb_inplace_multiply { get; private set; } - public int nb_inplace_remainder { get; private set; } - public int nb_inplace_power { get; private set; } - public int nb_inplace_lshift { get; private set; } - public int nb_inplace_rshift { get; private set; } - public int nb_inplace_and { get; private set; } - public int nb_inplace_xor { get; private set; } - public int nb_inplace_or { get; private set; } - public int nb_floor_divide { get; private set; } - public int nb_true_divide { get; private set; } - public int nb_inplace_floor_divide { get; private set; } - public int nb_inplace_true_divide { get; private set; } - public int nb_index { get; private set; } - public int nb_matrix_multiply { get; private set; } - public int nb_inplace_matrix_multiply { get; private set; } - public int mp_length { get; private set; } - public int mp_subscript { get; private set; } - public int mp_ass_subscript { get; private set; } - public int sq_length { get; private set; } - public int sq_concat { get; private set; } - public int sq_repeat { get; private set; } - public int sq_item { get; private set; } - public int was_sq_slice { get; private set; } - public int sq_ass_item { get; private set; } - public int was_sq_ass_slice { get; private set; } - public int sq_contains { get; private set; } - public int sq_inplace_concat { get; private set; } - public int sq_inplace_repeat { get; private set; } - public int bf_getbuffer { get; private set; } - public int bf_releasebuffer { get; private set; } - public int name { get; private set; } - public int ht_slots { get; private set; } - public int qualname { get; private set; } - public int ht_cached_keys { get; private set; } - } -} diff --git a/src/runtime/Native/TypeOffset38.cs b/src/runtime/Native/TypeOffset38.cs deleted file mode 100644 index 67a40eabd..000000000 --- a/src/runtime/Native/TypeOffset38.cs +++ /dev/null @@ -1,138 +0,0 @@ - -// Auto-generated by geninterop.py. -// DO NOT MODIFY BY HAND. - -// Python 3.8: ABI flags: '' - -// ReSharper disable InconsistentNaming -// ReSharper disable IdentifierTypo - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -using Python.Runtime.Native; - -namespace Python.Runtime -{ - [SuppressMessage("Style", "IDE1006:Naming Styles", - Justification = "Following CPython", - Scope = "type")] - - [StructLayout(LayoutKind.Sequential)] - internal class TypeOffset38 : GeneratedTypeOffsets, ITypeOffsets - { - public TypeOffset38() { } - // Auto-generated from PyHeapTypeObject in Python.h - public int ob_refcnt { get; private set; } - public int ob_type { get; private set; } - public int ob_size { get; private set; } - public int tp_name { get; private set; } - public int tp_basicsize { get; private set; } - public int tp_itemsize { get; private set; } - public int tp_dealloc { get; private set; } - public int tp_vectorcall_offset { get; private set; } - public int tp_getattr { get; private set; } - public int tp_setattr { get; private set; } - public int tp_as_async { get; private set; } - public int tp_repr { get; private set; } - public int tp_as_number { get; private set; } - public int tp_as_sequence { get; private set; } - public int tp_as_mapping { get; private set; } - public int tp_hash { get; private set; } - public int tp_call { get; private set; } - public int tp_str { get; private set; } - public int tp_getattro { get; private set; } - public int tp_setattro { get; private set; } - public int tp_as_buffer { get; private set; } - public int tp_flags { get; private set; } - public int tp_doc { get; private set; } - public int tp_traverse { get; private set; } - public int tp_clear { get; private set; } - public int tp_richcompare { get; private set; } - public int tp_weaklistoffset { get; private set; } - public int tp_iter { get; private set; } - public int tp_iternext { get; private set; } - public int tp_methods { get; private set; } - public int tp_members { get; private set; } - public int tp_getset { get; private set; } - public int tp_base { get; private set; } - public int tp_dict { get; private set; } - public int tp_descr_get { get; private set; } - public int tp_descr_set { get; private set; } - public int tp_dictoffset { get; private set; } - public int tp_init { get; private set; } - public int tp_alloc { get; private set; } - public int tp_new { get; private set; } - public int tp_free { get; private set; } - public int tp_is_gc { get; private set; } - public int tp_bases { get; private set; } - public int tp_mro { get; private set; } - public int tp_cache { get; private set; } - public int tp_subclasses { get; private set; } - public int tp_weaklist { get; private set; } - public int tp_del { get; private set; } - public int tp_version_tag { get; private set; } - public int tp_finalize { get; private set; } - public int tp_vectorcall { get; private set; } - public int tp_print { get; private set; } - public int am_await { get; private set; } - public int am_aiter { get; private set; } - public int am_anext { get; private set; } - public int nb_add { get; private set; } - public int nb_subtract { get; private set; } - public int nb_multiply { get; private set; } - public int nb_remainder { get; private set; } - public int nb_divmod { get; private set; } - public int nb_power { get; private set; } - public int nb_negative { get; private set; } - public int nb_positive { get; private set; } - public int nb_absolute { get; private set; } - public int nb_bool { get; private set; } - public int nb_invert { get; private set; } - public int nb_lshift { get; private set; } - public int nb_rshift { get; private set; } - public int nb_and { get; private set; } - public int nb_xor { get; private set; } - public int nb_or { get; private set; } - public int nb_int { get; private set; } - public int nb_reserved { get; private set; } - public int nb_float { get; private set; } - public int nb_inplace_add { get; private set; } - public int nb_inplace_subtract { get; private set; } - public int nb_inplace_multiply { get; private set; } - public int nb_inplace_remainder { get; private set; } - public int nb_inplace_power { get; private set; } - public int nb_inplace_lshift { get; private set; } - public int nb_inplace_rshift { get; private set; } - public int nb_inplace_and { get; private set; } - public int nb_inplace_xor { get; private set; } - public int nb_inplace_or { get; private set; } - public int nb_floor_divide { get; private set; } - public int nb_true_divide { get; private set; } - public int nb_inplace_floor_divide { get; private set; } - public int nb_inplace_true_divide { get; private set; } - public int nb_index { get; private set; } - public int nb_matrix_multiply { get; private set; } - public int nb_inplace_matrix_multiply { get; private set; } - public int mp_length { get; private set; } - public int mp_subscript { get; private set; } - public int mp_ass_subscript { get; private set; } - public int sq_length { get; private set; } - public int sq_concat { get; private set; } - public int sq_repeat { get; private set; } - public int sq_item { get; private set; } - public int was_sq_slice { get; private set; } - public int sq_ass_item { get; private set; } - public int was_sq_ass_slice { get; private set; } - public int sq_contains { get; private set; } - public int sq_inplace_concat { get; private set; } - public int sq_inplace_repeat { get; private set; } - public int bf_getbuffer { get; private set; } - public int bf_releasebuffer { get; private set; } - public int name { get; private set; } - public int ht_slots { get; private set; } - public int qualname { get; private set; } - public int ht_cached_keys { get; private set; } - } -} diff --git a/src/runtime/Native/TypeOffset39.cs b/src/runtime/Native/TypeOffset39.cs deleted file mode 100644 index cf3acc984..000000000 --- a/src/runtime/Native/TypeOffset39.cs +++ /dev/null @@ -1,138 +0,0 @@ - -// Auto-generated by geninterop.py. -// DO NOT MODIFY BY HAND. - -// Python 3.9: ABI flags: '' - -// ReSharper disable InconsistentNaming -// ReSharper disable IdentifierTypo - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -using Python.Runtime.Native; - -namespace Python.Runtime -{ - [SuppressMessage("Style", "IDE1006:Naming Styles", - Justification = "Following CPython", - Scope = "type")] - - [StructLayout(LayoutKind.Sequential)] - internal class TypeOffset39 : GeneratedTypeOffsets, ITypeOffsets - { - public TypeOffset39() { } - // Auto-generated from PyHeapTypeObject in Python.h - public int ob_refcnt { get; private set; } - public int ob_type { get; private set; } - public int ob_size { get; private set; } - public int tp_name { get; private set; } - public int tp_basicsize { get; private set; } - public int tp_itemsize { get; private set; } - public int tp_dealloc { get; private set; } - public int tp_vectorcall_offset { get; private set; } - public int tp_getattr { get; private set; } - public int tp_setattr { get; private set; } - public int tp_as_async { get; private set; } - public int tp_repr { get; private set; } - public int tp_as_number { get; private set; } - public int tp_as_sequence { get; private set; } - public int tp_as_mapping { get; private set; } - public int tp_hash { get; private set; } - public int tp_call { get; private set; } - public int tp_str { get; private set; } - public int tp_getattro { get; private set; } - public int tp_setattro { get; private set; } - public int tp_as_buffer { get; private set; } - public int tp_flags { get; private set; } - public int tp_doc { get; private set; } - public int tp_traverse { get; private set; } - public int tp_clear { get; private set; } - public int tp_richcompare { get; private set; } - public int tp_weaklistoffset { get; private set; } - public int tp_iter { get; private set; } - public int tp_iternext { get; private set; } - public int tp_methods { get; private set; } - public int tp_members { get; private set; } - public int tp_getset { get; private set; } - public int tp_base { get; private set; } - public int tp_dict { get; private set; } - public int tp_descr_get { get; private set; } - public int tp_descr_set { get; private set; } - public int tp_dictoffset { get; private set; } - public int tp_init { get; private set; } - public int tp_alloc { get; private set; } - public int tp_new { get; private set; } - public int tp_free { get; private set; } - public int tp_is_gc { get; private set; } - public int tp_bases { get; private set; } - public int tp_mro { get; private set; } - public int tp_cache { get; private set; } - public int tp_subclasses { get; private set; } - public int tp_weaklist { get; private set; } - public int tp_del { get; private set; } - public int tp_version_tag { get; private set; } - public int tp_finalize { get; private set; } - public int tp_vectorcall { get; private set; } - public int am_await { get; private set; } - public int am_aiter { get; private set; } - public int am_anext { get; private set; } - public int nb_add { get; private set; } - public int nb_subtract { get; private set; } - public int nb_multiply { get; private set; } - public int nb_remainder { get; private set; } - public int nb_divmod { get; private set; } - public int nb_power { get; private set; } - public int nb_negative { get; private set; } - public int nb_positive { get; private set; } - public int nb_absolute { get; private set; } - public int nb_bool { get; private set; } - public int nb_invert { get; private set; } - public int nb_lshift { get; private set; } - public int nb_rshift { get; private set; } - public int nb_and { get; private set; } - public int nb_xor { get; private set; } - public int nb_or { get; private set; } - public int nb_int { get; private set; } - public int nb_reserved { get; private set; } - public int nb_float { get; private set; } - public int nb_inplace_add { get; private set; } - public int nb_inplace_subtract { get; private set; } - public int nb_inplace_multiply { get; private set; } - public int nb_inplace_remainder { get; private set; } - public int nb_inplace_power { get; private set; } - public int nb_inplace_lshift { get; private set; } - public int nb_inplace_rshift { get; private set; } - public int nb_inplace_and { get; private set; } - public int nb_inplace_xor { get; private set; } - public int nb_inplace_or { get; private set; } - public int nb_floor_divide { get; private set; } - public int nb_true_divide { get; private set; } - public int nb_inplace_floor_divide { get; private set; } - public int nb_inplace_true_divide { get; private set; } - public int nb_index { get; private set; } - public int nb_matrix_multiply { get; private set; } - public int nb_inplace_matrix_multiply { get; private set; } - public int mp_length { get; private set; } - public int mp_subscript { get; private set; } - public int mp_ass_subscript { get; private set; } - public int sq_length { get; private set; } - public int sq_concat { get; private set; } - public int sq_repeat { get; private set; } - public int sq_item { get; private set; } - public int was_sq_slice { get; private set; } - public int sq_ass_item { get; private set; } - public int was_sq_ass_slice { get; private set; } - public int sq_contains { get; private set; } - public int sq_inplace_concat { get; private set; } - public int sq_inplace_repeat { get; private set; } - public int bf_getbuffer { get; private set; } - public int bf_releasebuffer { get; private set; } - public int name { get; private set; } - public int ht_slots { get; private set; } - public int qualname { get; private set; } - public int ht_cached_keys { get; private set; } - public int ht_module { get; private set; } - } -} diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 5072f23cd..291da366b 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -2,7 +2,7 @@ netstandard2.0 AnyCPU - 10.0 + 10 Python.Runtime Python.Runtime enable @@ -12,8 +12,6 @@ https://github.com/pythonnet/pythonnet git python interop dynamic dlr Mono pinvoke - python-clear.png - https://raw.githubusercontent.com/pythonnet/pythonnet/master/src/console/python-clear.ico https://pythonnet.github.io/ README.md true @@ -48,7 +46,6 @@ - @@ -63,7 +60,7 @@ - - + + diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index e5879ae67..13855adef 100644 --- a/src/runtime/PythonEngine.cs +++ b/src/runtime/PythonEngine.cs @@ -47,6 +47,14 @@ public static bool IsInitialized get { return initialized; } } + private static void EnsureInitialized() + { + if (!IsInitialized) + throw new InvalidOperationException( + "Python must be initialized for this operation" + ); + } + /// Set to true to enable GIL debugging assistance. public static bool DebugGIL { get; set; } = false; @@ -96,6 +104,7 @@ public static string PythonHome { get { + EnsureInitialized(); IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetPythonHome()); return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? ""; } @@ -103,10 +112,8 @@ public static string PythonHome { // this value is null in the beginning Marshal.FreeHGlobal(_pythonHome); - _pythonHome = Runtime.TryUsingDll( - () => UcsMarshaler.Py3UnicodePy2StringtoPtr(value) - ); - Runtime.Py_SetPythonHome(_pythonHome); + _pythonHome = UcsMarshaler.Py3UnicodePy2StringtoPtr(value); + Runtime.TryUsingDll(() => Runtime.Py_SetPythonHome(_pythonHome)); } } @@ -128,7 +135,7 @@ public static string PythonPath } public static Version MinSupportedVersion => new(3, 7); - public static Version MaxSupportedVersion => new(3, 10, int.MaxValue, int.MaxValue); + public static Version MaxSupportedVersion => new(3, 14, int.MaxValue, int.MaxValue); public static bool IsSupportedVersion(Version version) => version >= MinSupportedVersion && version <= MaxSupportedVersion; public static string Version diff --git a/src/runtime/PythonTypes/PyBuffer.cs b/src/runtime/PythonTypes/PyBuffer.cs index de0e7122f..120582494 100644 --- a/src/runtime/PythonTypes/PyBuffer.cs +++ b/src/runtime/PythonTypes/PyBuffer.cs @@ -11,7 +11,7 @@ public sealed class PyBuffer : IDisposable private PyObject _exporter; private Py_buffer _view; - unsafe internal PyBuffer(PyObject exporter, PyBUF flags) + internal PyBuffer(PyObject exporter, PyBUF flags) { _view = new Py_buffer(); @@ -25,17 +25,17 @@ unsafe internal PyBuffer(PyObject exporter, PyBUF flags) var intPtrBuf = new IntPtr[_view.ndim]; if (_view.shape != IntPtr.Zero) { - Marshal.Copy(_view.shape, intPtrBuf, 0, (int)_view.len * sizeof(IntPtr)); + Marshal.Copy(_view.shape, intPtrBuf, 0, _view.ndim); Shape = intPtrBuf.Select(x => (long)x).ToArray(); } if (_view.strides != IntPtr.Zero) { - Marshal.Copy(_view.strides, intPtrBuf, 0, (int)_view.len * sizeof(IntPtr)); + Marshal.Copy(_view.strides, intPtrBuf, 0, _view.ndim); Strides = intPtrBuf.Select(x => (long)x).ToArray(); } if (_view.suboffsets != IntPtr.Zero) { - Marshal.Copy(_view.suboffsets, intPtrBuf, 0, (int)_view.len * sizeof(IntPtr)); + Marshal.Copy(_view.suboffsets, intPtrBuf, 0, _view.ndim); SubOffsets = intPtrBuf.Select(x => (long)x).ToArray(); } } diff --git a/src/runtime/PythonTypes/PyFloat.IComparable.cs b/src/runtime/PythonTypes/PyFloat.IComparable.cs new file mode 100644 index 000000000..c12fc283a --- /dev/null +++ b/src/runtime/PythonTypes/PyFloat.IComparable.cs @@ -0,0 +1,34 @@ +using System; + +namespace Python.Runtime; + +partial class PyFloat : IComparable, IComparable + , IEquatable, IEquatable + , IComparable, IEquatable +{ + public override bool Equals(object o) + { + using var _ = Py.GIL(); + return o switch + { + double f64 => this.Equals(f64), + float f32 => this.Equals(f32), + _ => base.Equals(o), + }; + } + + public int CompareTo(double other) => this.ToDouble().CompareTo(other); + + public int CompareTo(float other) => this.ToDouble().CompareTo(other); + + public bool Equals(double other) => this.ToDouble().Equals(other); + + public bool Equals(float other) => this.ToDouble().Equals(other); + + public int CompareTo(PyFloat? other) + { + return other is null ? 1 : this.CompareTo(other.BorrowNullable()); + } + + public bool Equals(PyFloat? other) => base.Equals(other); +} diff --git a/src/runtime/PythonTypes/PyFloat.cs b/src/runtime/PythonTypes/PyFloat.cs index c09ec93ba..50621d5c2 100644 --- a/src/runtime/PythonTypes/PyFloat.cs +++ b/src/runtime/PythonTypes/PyFloat.cs @@ -8,7 +8,7 @@ namespace Python.Runtime /// PY3: https://docs.python.org/3/c-api/float.html /// for details. /// - public class PyFloat : PyNumber + public partial class PyFloat : PyNumber { internal PyFloat(in StolenReference ptr) : base(ptr) { @@ -100,6 +100,8 @@ public static PyFloat AsFloat(PyObject value) return new PyFloat(op.Steal()); } + public double ToDouble() => Runtime.PyFloat_AsDouble(obj); + public override TypeCode GetTypeCode() => TypeCode.Double; } } diff --git a/src/runtime/PythonTypes/PyInt.IComparable.cs b/src/runtime/PythonTypes/PyInt.IComparable.cs new file mode 100644 index 000000000..a96f02e10 --- /dev/null +++ b/src/runtime/PythonTypes/PyInt.IComparable.cs @@ -0,0 +1,136 @@ +using System; + +namespace Python.Runtime; + +partial class PyInt : IComparable, IComparable, IComparable, IComparable + , IComparable, IComparable, IComparable, IComparable + , IEquatable, IEquatable, IEquatable, IEquatable + , IEquatable, IEquatable, IEquatable, IEquatable + , IComparable, IEquatable +{ + public override bool Equals(object o) + { + using var _ = Py.GIL(); + return o switch + { + long i64 => this.Equals(i64), + int i32 => this.Equals(i32), + short i16 => this.Equals(i16), + sbyte i8 => this.Equals(i8), + + ulong u64 => this.Equals(u64), + uint u32 => this.Equals(u32), + ushort u16 => this.Equals(u16), + byte u8 => this.Equals(u8), + + _ => base.Equals(o), + }; + } + + #region Signed + public int CompareTo(long other) + { + using var pyOther = Runtime.PyInt_FromInt64(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public int CompareTo(int other) + { + using var pyOther = Runtime.PyInt_FromInt32(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public int CompareTo(short other) + { + using var pyOther = Runtime.PyInt_FromInt32(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public int CompareTo(sbyte other) + { + using var pyOther = Runtime.PyInt_FromInt32(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public bool Equals(long other) + { + using var pyOther = Runtime.PyInt_FromInt64(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + + public bool Equals(int other) + { + using var pyOther = Runtime.PyInt_FromInt32(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + + public bool Equals(short other) + { + using var pyOther = Runtime.PyInt_FromInt32(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + + public bool Equals(sbyte other) + { + using var pyOther = Runtime.PyInt_FromInt32(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + #endregion Signed + + #region Unsigned + public int CompareTo(ulong other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public int CompareTo(uint other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public int CompareTo(ushort other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public int CompareTo(byte other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public bool Equals(ulong other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + + public bool Equals(uint other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + + public bool Equals(ushort other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + + public bool Equals(byte other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + #endregion Unsigned + + public int CompareTo(PyInt? other) + { + return other is null ? 1 : this.CompareTo(other.BorrowNullable()); + } + + public bool Equals(PyInt? other) => base.Equals(other); +} diff --git a/src/runtime/PythonTypes/PyInt.cs b/src/runtime/PythonTypes/PyInt.cs index 6b3dbf210..0d00f5a13 100644 --- a/src/runtime/PythonTypes/PyInt.cs +++ b/src/runtime/PythonTypes/PyInt.cs @@ -9,7 +9,7 @@ namespace Python.Runtime /// Represents a Python integer object. /// See the documentation at https://docs.python.org/3/c-api/long.html /// - public class PyInt : PyNumber, IFormattable + public partial class PyInt : PyNumber, IFormattable { internal PyInt(in StolenReference ptr) : base(ptr) { @@ -212,7 +212,7 @@ public BigInteger ToBigInteger() offset++; neg = true; } - byte[] littleEndianBytes = new byte[(hex.Length - offset + 1) / 2]; + byte[] littleEndianBytes = new byte[(hex.Length - offset + 1) / 2 + 1]; for (; offset < hex.Length; offset++) { int littleEndianHexIndex = hex.Length - 1 - offset; diff --git a/src/runtime/PythonTypes/PyModule.cs b/src/runtime/PythonTypes/PyModule.cs index 4549678ed..243f77ecc 100644 --- a/src/runtime/PythonTypes/PyModule.cs +++ b/src/runtime/PythonTypes/PyModule.cs @@ -82,7 +82,18 @@ public PyModule Reload() public static PyModule FromString(string name, string code) { - using NewReference c = Runtime.Py_CompileString(code, "none", (int)RunFlagType.File); + return FromString(name, code, ""); + } + + public static PyModule FromString(string name, string code, string file) + { + // Force valid value + if (string.IsNullOrWhiteSpace(file)) + { + file = "none"; + } + + using NewReference c = Runtime.Py_CompileString(code, file, (int)RunFlagType.File); NewReference m = Runtime.PyImport_ExecCodeModule(name, c.BorrowOrThrow()); return new PyModule(m.StealOrThrow()); } diff --git a/src/runtime/PythonTypes/PyObject.cs b/src/runtime/PythonTypes/PyObject.cs index ce86753eb..cf0c2a03f 100644 --- a/src/runtime/PythonTypes/PyObject.cs +++ b/src/runtime/PythonTypes/PyObject.cs @@ -1051,9 +1051,20 @@ public PyList Dir() return Runtime.GetManagedString(strval.BorrowOrThrow()); } - string? DebuggerDisplay => DebugUtil.HaveInterpreterLock() - ? this.ToString() - : $"pyobj at 0x{this.rawPtr:X} (get Py.GIL to see more info)"; + ManagedType? InternalManagedObject => ManagedType.GetManagedObject(this.Reference); + + string? DebuggerDisplay + { + get + { + if (DebugUtil.HaveInterpreterLock()) + return this.ToString(); + var obj = this.InternalManagedObject; + return obj is { } + ? obj.ToString() + : $"pyobj at 0x{this.rawPtr:X} (get Py.GIL to see more info)"; + } + } /// @@ -1125,6 +1136,23 @@ public long Refcount } } + internal int CompareTo(BorrowedReference other) + { + int greater = Runtime.PyObject_RichCompareBool(this.Reference, other, Runtime.Py_GT); + Debug.Assert(greater != -1); + if (greater > 0) + return 1; + int less = Runtime.PyObject_RichCompareBool(this.Reference, other, Runtime.Py_LT); + Debug.Assert(less != -1); + return less > 0 ? -1 : 0; + } + + internal bool Equals(BorrowedReference other) + { + int equal = Runtime.PyObject_RichCompareBool(this.Reference, other, Runtime.Py_EQ); + Debug.Assert(equal != -1); + return equal > 0; + } public override bool TryGetMember(GetMemberBinder binder, out object? result) { @@ -1314,7 +1342,7 @@ private bool TryCompare(PyObject arg, int op, out object @out) } return true; } - + public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result) { using var _ = Py.GIL(); diff --git a/src/runtime/PythonTypes/PyString.cs b/src/runtime/PythonTypes/PyString.cs index d54397fcf..6fed25c3e 100644 --- a/src/runtime/PythonTypes/PyString.cs +++ b/src/runtime/PythonTypes/PyString.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Runtime.Serialization; namespace Python.Runtime @@ -13,7 +14,7 @@ namespace Python.Runtime /// 2011-01-29: ...Then why does the string constructor call PyUnicode_FromUnicode()??? /// [Serializable] - public class PyString : PySequence + public class PyString : PySequence, IComparable, IEquatable { internal PyString(in StolenReference reference) : base(reference) { } internal PyString(BorrowedReference reference) : base(reference) { } @@ -61,5 +62,23 @@ public static bool IsStringType(PyObject value) } public override TypeCode GetTypeCode() => TypeCode.String; + + internal string ToStringUnderGIL() + { + string? result = Runtime.GetManagedString(this.Reference); + Debug.Assert(result is not null); + return result!; + } + + public bool Equals(string? other) + => this.ToStringUnderGIL().Equals(other, StringComparison.CurrentCulture); + public int CompareTo(string? other) + => string.Compare(this.ToStringUnderGIL(), other, StringComparison.CurrentCulture); + + public override string ToString() + { + using var _ = Py.GIL(); + return this.ToStringUnderGIL(); + } } } diff --git a/src/runtime/PythonTypes/PyType.cs b/src/runtime/PythonTypes/PyType.cs index 260800592..28bda5d3e 100644 --- a/src/runtime/PythonTypes/PyType.cs +++ b/src/runtime/PythonTypes/PyType.cs @@ -53,7 +53,7 @@ public string Name { RawPointer = Util.ReadIntPtr(this, TypeOffset.tp_name), }; - return namePtr.ToString(System.Text.Encoding.UTF8)!; + return namePtr.ToString(Encodings.UTF8)!; } } @@ -155,6 +155,7 @@ private static StolenReference FromSpec(TypeSpec spec, PyTuple? bases = null) using var nativeSpec = new NativeTypeSpec(spec); var basesRef = bases is null ? default : bases.Reference; var result = Runtime.PyType_FromSpecWithBases(in nativeSpec, basesRef); + // Runtime.PyErr_Print(); return result.StealOrThrow(); } } diff --git a/src/runtime/Runtime.Delegates.cs b/src/runtime/Runtime.Delegates.cs index 0b6b75872..dc4a4b0a9 100644 --- a/src/runtime/Runtime.Delegates.cs +++ b/src/runtime/Runtime.Delegates.cs @@ -23,7 +23,17 @@ static Delegates() Py_EndInterpreter = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(Py_EndInterpreter), GetUnmanagedDll(_PythonDll)); PyThreadState_New = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyThreadState_New), GetUnmanagedDll(_PythonDll)); PyThreadState_Get = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyThreadState_Get), GetUnmanagedDll(_PythonDll)); - _PyThreadState_UncheckedGet = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(_PyThreadState_UncheckedGet), GetUnmanagedDll(_PythonDll)); + try + { + // Up until Python 3.13, this function was private and named + // slightly differently. + PyThreadState_GetUnchecked = (delegate* unmanaged[Cdecl])GetFunctionByName("_PyThreadState_UncheckedGet", GetUnmanagedDll(_PythonDll)); + } + catch (MissingMethodException) + { + + PyThreadState_GetUnchecked = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyThreadState_GetUnchecked), GetUnmanagedDll(_PythonDll)); + } try { PyGILState_Check = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyGILState_Check), GetUnmanagedDll(_PythonDll)); @@ -35,7 +45,6 @@ static Delegates() PyGILState_Ensure = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyGILState_Ensure), GetUnmanagedDll(_PythonDll)); PyGILState_Release = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyGILState_Release), GetUnmanagedDll(_PythonDll)); PyGILState_GetThisThreadState = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyGILState_GetThisThreadState), GetUnmanagedDll(_PythonDll)); - Py_Main = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(Py_Main), GetUnmanagedDll(_PythonDll)); PyEval_InitThreads = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyEval_InitThreads), GetUnmanagedDll(_PythonDll)); PyEval_ThreadsInitialized = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyEval_ThreadsInitialized), GetUnmanagedDll(_PythonDll)); PyEval_AcquireLock = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyEval_AcquireLock), GetUnmanagedDll(_PythonDll)); @@ -164,8 +173,8 @@ static Delegates() PyUnicode_AsUTF8 = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_AsUTF8), GetUnmanagedDll(_PythonDll)); PyUnicode_DecodeUTF16 = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_DecodeUTF16), GetUnmanagedDll(_PythonDll)); PyUnicode_GetLength = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_GetLength), GetUnmanagedDll(_PythonDll)); - PyUnicode_AsUnicode = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_AsUnicode), GetUnmanagedDll(_PythonDll)); PyUnicode_AsUTF16String = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_AsUTF16String), GetUnmanagedDll(_PythonDll)); + PyUnicode_ReadChar = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_ReadChar), GetUnmanagedDll(_PythonDll)); PyUnicode_FromOrdinal = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_FromOrdinal), GetUnmanagedDll(_PythonDll)); PyUnicode_InternFromString = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_InternFromString), GetUnmanagedDll(_PythonDll)); PyUnicode_Compare = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_Compare), GetUnmanagedDll(_PythonDll)); @@ -299,7 +308,8 @@ static Delegates() { throw new BadPythonDllException( "Runtime.PythonDLL was not set or does not point to a supported Python runtime DLL." + - " See https://github.com/pythonnet/pythonnet#embedding-python-in-net", + " See https://github.com/pythonnet/pythonnet#embedding-python-in-net." + + $" Value of PythonDLL: {PythonDLL ?? "null"}", e); } } @@ -314,12 +324,11 @@ static Delegates() internal static delegate* unmanaged[Cdecl] Py_EndInterpreter { get; } internal static delegate* unmanaged[Cdecl] PyThreadState_New { get; } internal static delegate* unmanaged[Cdecl] PyThreadState_Get { get; } - internal static delegate* unmanaged[Cdecl] _PyThreadState_UncheckedGet { get; } + internal static delegate* unmanaged[Cdecl] PyThreadState_GetUnchecked { get; } internal static delegate* unmanaged[Cdecl] PyGILState_Check { get; } internal static delegate* unmanaged[Cdecl] PyGILState_Ensure { get; } internal static delegate* unmanaged[Cdecl] PyGILState_Release { get; } internal static delegate* unmanaged[Cdecl] PyGILState_GetThisThreadState { get; } - internal static delegate* unmanaged[Cdecl] Py_Main { get; } internal static delegate* unmanaged[Cdecl] PyEval_InitThreads { get; } internal static delegate* unmanaged[Cdecl] PyEval_ThreadsInitialized { get; } internal static delegate* unmanaged[Cdecl] PyEval_AcquireLock { get; } @@ -441,7 +450,7 @@ static Delegates() internal static delegate* unmanaged[Cdecl] PyUnicode_AsUTF8 { get; } internal static delegate* unmanaged[Cdecl] PyUnicode_DecodeUTF16 { get; } internal static delegate* unmanaged[Cdecl] PyUnicode_GetLength { get; } - internal static delegate* unmanaged[Cdecl] PyUnicode_AsUnicode { get; } + internal static delegate* unmanaged[Cdecl] PyUnicode_ReadChar { get; } internal static delegate* unmanaged[Cdecl] PyUnicode_AsUTF16String { get; } internal static delegate* unmanaged[Cdecl] PyUnicode_FromOrdinal { get; } internal static delegate* unmanaged[Cdecl] PyUnicode_InternFromString { get; } diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 6238119ff..399608733 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading; using System.Collections.Generic; +using System.IO; using Python.Runtime.Native; using System.Linq; using static System.FormattableString; @@ -18,6 +19,8 @@ namespace Python.Runtime /// public unsafe partial class Runtime { + internal static PythonEnvironment PythonEnvironment = PythonEnvironment.FromEnv(); + public static string? PythonDLL { get => _PythonDll; @@ -25,33 +28,11 @@ public static string? PythonDLL { if (_isInitialized) throw new InvalidOperationException("This property must be set before runtime is initialized"); - _PythonDll = value; + PythonEnvironment.LibPython = value; } } - static string? _PythonDll = GetDefaultDllName(); - private static string? GetDefaultDllName() - { - string dll = Environment.GetEnvironmentVariable("PYTHONNET_PYDLL"); - if (dll is not null) return dll; - - string verString = Environment.GetEnvironmentVariable("PYTHONNET_PYVER"); - if (!Version.TryParse(verString, out var version)) return null; - - return GetDefaultDllName(version); - } - - private static string GetDefaultDllName(Version version) - { - string prefix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "" : "lib"; - string suffix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? Invariant($"{version.Major}{version.Minor}") - : Invariant($"{version.Major}.{version.Minor}"); - string ext = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".dll" - : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? ".dylib" - : ".so"; - return prefix + "python" + suffix + ext; - } + static string? _PythonDll => PythonEnvironment.LibPython; private static bool _isInitialized = false; internal static bool IsInitialized => _isInitialized; @@ -96,6 +77,18 @@ internal static int GetRun() return runNumber; } + static void EnsureProgramName() + { + if (!string.IsNullOrEmpty(PythonEngine.ProgramName)) + return; + + if (PythonEnvironment.IsValid) + { + PythonEngine.ProgramName = PythonEnvironment.ProgramName!; + return; + } + } + internal static bool HostedInPython; internal static bool ProcessIsTerminating; @@ -117,6 +110,8 @@ internal static void Initialize(bool initSigs = false) ); if (!interpreterAlreadyInitialized) { + EnsureProgramName(); + Py_InitializeEx(initSigs ? 1 : 0); NewRun(); @@ -158,6 +153,7 @@ internal static void Initialize(bool initSigs = false) ClassManager.Reset(); ClassDerivedObject.Reset(); TypeManager.Initialize(); + CLRObject.creationBlocked = false; _typesInitialized = true; // Initialize modules that depend on the runtime class. @@ -278,6 +274,10 @@ internal static void Shutdown() ClearClrModules(); RemoveClrRootModule(); + TryCollectingGarbage(MaxCollectRetriesOnShutdown, forceBreakLoops: true, + obj: true, derived: false, buffer: false); + CLRObject.creationBlocked = true; + NullGCHandles(ExtensionType.loadedExtensions); ClassManager.RemoveClasses(); TypeManager.RemoveTypes(); @@ -295,8 +295,7 @@ internal static void Shutdown() PyObjectConversions.Reset(); PyGC_Collect(); - bool everythingSeemsCollected = TryCollectingGarbage(MaxCollectRetriesOnShutdown, - forceBreakLoops: true); + bool everythingSeemsCollected = TryCollectingGarbage(MaxCollectRetriesOnShutdown); Debug.Assert(everythingSeemsCollected); Finalizer.Shutdown(); @@ -312,7 +311,7 @@ internal static void Shutdown() // Then release the GIL for good, if there is somehting to release // Use the unchecked version as the checked version calls `abort()` // if the current state is NULL. - if (_PyThreadState_UncheckedGet() != (PyThreadState*)0) + if (PyThreadState_GetUnchecked() != (PyThreadState*)0) { PyEval_SaveThread(); } @@ -328,7 +327,8 @@ internal static void Shutdown() const int MaxCollectRetriesOnShutdown = 20; internal static int _collected; - static bool TryCollectingGarbage(int runs, bool forceBreakLoops) + static bool TryCollectingGarbage(int runs, bool forceBreakLoops, + bool obj = true, bool derived = true, bool buffer = true) { if (runs <= 0) throw new ArgumentOutOfRangeException(nameof(runs)); @@ -341,7 +341,9 @@ static bool TryCollectingGarbage(int runs, bool forceBreakLoops) GC.Collect(); GC.WaitForPendingFinalizers(); pyCollected += PyGC_Collect(); - pyCollected += Finalizer.Instance.DisposeAll(); + pyCollected += Finalizer.Instance.DisposeAll(disposeObj: obj, + disposeDerived: derived, + disposeBuffer: buffer); } if (Volatile.Read(ref _collected) == 0 && pyCollected == 0) { @@ -598,23 +600,8 @@ internal static void CheckExceptionOccurred() [Obsolete("Use NewReference or PyObject constructor instead")] internal static unsafe void XIncref(BorrowedReference op) { -#if !CUSTOM_INCDEC_REF Py_IncRef(op); return; -#else - var p = (void*)op; - if ((void*)0 != p) - { - if (Is32Bit) - { - (*(int*)p)++; - } - else - { - (*(long*)p)++; - } - } -#endif } internal static unsafe void XDecref(StolenReference op) @@ -623,40 +610,9 @@ internal static unsafe void XDecref(StolenReference op) Debug.Assert(op == null || Refcount(new BorrowedReference(op.Pointer)) > 0); Debug.Assert(_isInitialized || Py_IsInitialized() != 0 || _Py_IsFinalizing() != false); #endif -#if !CUSTOM_INCDEC_REF if (op == null) return; Py_DecRef(op.AnalyzerWorkaround()); return; -#else - var p = (void*)op; - if ((void*)0 != p) - { - if (Is32Bit) - { - --(*(int*)p); - } - else - { - --(*(long*)p); - } - if ((*(int*)p) == 0) - { - // PyObject_HEAD: struct _typeobject *ob_type - void* t = Is32Bit - ? (void*)(*((uint*)p + 1)) - : (void*)(*((ulong*)p + 1)); - // PyTypeObject: destructor tp_dealloc - void* f = Is32Bit - ? (void*)(*((uint*)t + 6)) - : (void*)(*((ulong*)t + 6)); - if ((void*)0 == f) - { - return; - } - NativeCall.Void_Call_1(new IntPtr(f), op); - } - } -#endif } [Pure] @@ -672,6 +628,9 @@ internal static unsafe nint Refcount(BorrowedReference op) [Pure] internal static int Refcount32(BorrowedReference op) => checked((int)Refcount(op)); + internal static void TryUsingDll(Action op) => + TryUsingDll(() => { op(); return 0; }); + /// /// Call specified function, and handle PythonDLL-related failures. /// @@ -741,7 +700,7 @@ internal static T TryUsingDll(Func op) internal static PyThreadState* PyThreadState_Get() => Delegates.PyThreadState_Get(); - internal static PyThreadState* _PyThreadState_UncheckedGet() => Delegates._PyThreadState_UncheckedGet(); + internal static PyThreadState* PyThreadState_GetUnchecked() => Delegates.PyThreadState_GetUnchecked(); internal static int PyGILState_Check() => Delegates.PyGILState_Check(); @@ -755,20 +714,6 @@ internal static T TryUsingDll(Func op) internal static PyThreadState* PyGILState_GetThisThreadState() => Delegates.PyGILState_GetThisThreadState(); - public static int Py_Main(int argc, string[] argv) - { - var marshaler = StrArrayMarshaler.GetInstance(null); - var argvPtr = marshaler.MarshalManagedToNative(argv); - try - { - return Delegates.Py_Main(argc, argvPtr); - } - finally - { - marshaler.CleanUpNativeData(argvPtr); - } - } - internal static void PyEval_InitThreads() => Delegates.PyEval_InitThreads(); @@ -838,13 +783,13 @@ public static int Py_Main(int argc, string[] argv) internal static int PyRun_SimpleString(string code) { - using var codePtr = new StrPtr(code, Encoding.UTF8); + using var codePtr = new StrPtr(code); return Delegates.PyRun_SimpleStringFlags(codePtr, Utf8String); } internal static NewReference PyRun_String(string code, RunFlagType st, BorrowedReference globals, BorrowedReference locals) { - using var codePtr = new StrPtr(code, Encoding.UTF8); + using var codePtr = new StrPtr(code); return Delegates.PyRun_StringFlags(codePtr, st, globals, locals, Utf8String); } @@ -856,14 +801,15 @@ internal static NewReference PyRun_String(string code, RunFlagType st, BorrowedR /// internal static NewReference Py_CompileString(string str, string file, int start) { - using var strPtr = new StrPtr(str, Encoding.UTF8); + using var strPtr = new StrPtr(str); + using var fileObj = new PyString(file); return Delegates.Py_CompileStringObject(strPtr, fileObj, start, Utf8String, -1); } internal static NewReference PyImport_ExecCodeModule(string name, BorrowedReference code) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyImport_ExecCodeModule(namePtr, code); } @@ -910,13 +856,13 @@ internal static bool PyObject_IsIterable(BorrowedReference ob) internal static int PyObject_HasAttrString(BorrowedReference pointer, string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyObject_HasAttrString(pointer, namePtr); } internal static NewReference PyObject_GetAttrString(BorrowedReference pointer, string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyObject_GetAttrString(pointer, namePtr); } @@ -927,12 +873,12 @@ internal static NewReference PyObject_GetAttrString(BorrowedReference pointer, S internal static int PyObject_DelAttr(BorrowedReference @object, BorrowedReference name) => Delegates.PyObject_SetAttr(@object, name, null); internal static int PyObject_DelAttrString(BorrowedReference @object, string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyObject_SetAttrString(@object, namePtr, null); } internal static int PyObject_SetAttrString(BorrowedReference @object, string name, BorrowedReference value) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyObject_SetAttrString(@object, namePtr, value); } @@ -982,7 +928,7 @@ internal static BorrowedReference PyObject_GetWeakRefList(BorrowedReference ob) Debug.Assert(ob != null); var type = PyObject_TYPE(ob); int offset = Util.ReadInt32(type, TypeOffset.tp_weaklistoffset); - if (offset == 0) return BorrowedReference.Null; + if (offset <= 0) return BorrowedReference.Null; Debug.Assert(offset > 0); return Util.ReadRef(ob, offset); } @@ -1094,8 +1040,13 @@ internal static nint PyBuffer_SizeFromFormat(string format) internal static bool PyInt_Check(BorrowedReference ob) => PyObject_TypeCheck(ob, PyLongType); + internal static bool PyInt_CheckExact(BorrowedReference ob) + => PyObject_TypeCheckExact(ob, PyLongType); + internal static bool PyBool_Check(BorrowedReference ob) => PyObject_TypeCheck(ob, PyBoolType); + internal static bool PyBool_CheckExact(BorrowedReference ob) + => PyObject_TypeCheckExact(ob, PyBoolType); internal static NewReference PyInt_FromInt32(int value) => PyLong_FromLongLong(value); @@ -1109,7 +1060,7 @@ internal static bool PyBool_Check(BorrowedReference ob) internal static NewReference PyLong_FromString(string value, int radix) { - using var valPtr = new StrPtr(value, Encoding.UTF8); + using var valPtr = new StrPtr(value); return Delegates.PyLong_FromString(valPtr, IntPtr.Zero, radix); } @@ -1141,6 +1092,8 @@ internal static NewReference PyLong_FromString(string value, int radix) internal static bool PyFloat_Check(BorrowedReference ob) => PyObject_TypeCheck(ob, PyFloatType); + internal static bool PyFloat_CheckExact(BorrowedReference ob) + => PyObject_TypeCheckExact(ob, PyFloatType); /// /// Return value: New reference. @@ -1282,18 +1235,20 @@ internal static bool PyFloat_Check(BorrowedReference ob) // Python string API //==================================================================== internal static bool PyString_Check(BorrowedReference ob) - { - return PyObject_TYPE(ob) == PyStringType; - } + => PyObject_TypeCheck(ob, PyStringType); + internal static bool PyString_CheckExact(BorrowedReference ob) + => PyObject_TypeCheckExact(ob, PyStringType); internal static NewReference PyString_FromString(string value) { + int byteorder = BitConverter.IsLittleEndian ? -1 : 1; + int* byteorderPtr = &byteorder; fixed(char* ptr = value) return Delegates.PyUnicode_DecodeUTF16( (IntPtr)ptr, value.Length * sizeof(Char), IntPtr.Zero, - IntPtr.Zero + (IntPtr)byteorderPtr ); } @@ -1308,7 +1263,7 @@ internal static NewReference EmptyPyBytes() internal static NewReference PyByteArray_FromStringAndSize(IntPtr strPtr, nint len) => Delegates.PyByteArray_FromStringAndSize(strPtr, len); internal static NewReference PyByteArray_FromStringAndSize(string s) { - using var ptr = new StrPtr(s, Encoding.UTF8); + using var ptr = new StrPtr(s); return PyByteArray_FromStringAndSize(ptr.RawPointer, checked((nint)ptr.ByteCount)); } @@ -1326,16 +1281,17 @@ internal static IntPtr PyBytes_AsString(BorrowedReference ob) internal static nint PyUnicode_GetLength(BorrowedReference ob) => Delegates.PyUnicode_GetLength(ob); - internal static IntPtr PyUnicode_AsUnicode(BorrowedReference ob) => Delegates.PyUnicode_AsUnicode(ob); internal static NewReference PyUnicode_AsUTF16String(BorrowedReference ob) => Delegates.PyUnicode_AsUTF16String(ob); + internal static int PyUnicode_ReadChar(BorrowedReference ob, nint index) => Delegates.PyUnicode_ReadChar(ob, index); + internal static NewReference PyUnicode_FromOrdinal(int c) => Delegates.PyUnicode_FromOrdinal(c); internal static NewReference PyUnicode_InternFromString(string s) { - using var ptr = new StrPtr(s, Encoding.UTF8); + using var ptr = new StrPtr(s); return Delegates.PyUnicode_InternFromString(ptr); } @@ -1410,7 +1366,7 @@ internal static bool PyDict_Check(BorrowedReference ob) internal static BorrowedReference PyDict_GetItemString(BorrowedReference pointer, string key) { - using var keyStr = new StrPtr(key, Encoding.UTF8); + using var keyStr = new StrPtr(key); return Delegates.PyDict_GetItemString(pointer, keyStr); } @@ -1426,7 +1382,7 @@ internal static BorrowedReference PyDict_GetItemString(BorrowedReference pointer /// internal static int PyDict_SetItemString(BorrowedReference dict, string key, BorrowedReference value) { - using var keyPtr = new StrPtr(key, Encoding.UTF8); + using var keyPtr = new StrPtr(key); return Delegates.PyDict_SetItemString(dict, keyPtr, value); } @@ -1435,7 +1391,7 @@ internal static int PyDict_SetItemString(BorrowedReference dict, string key, Bor internal static int PyDict_DelItemString(BorrowedReference pointer, string key) { - using var keyPtr = new StrPtr(key, Encoding.UTF8); + using var keyPtr = new StrPtr(key); return Delegates.PyDict_DelItemString(pointer, keyPtr); } @@ -1550,7 +1506,7 @@ internal static bool PyIter_Check(BorrowedReference ob) internal static NewReference PyModule_New(string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyModule_New(namePtr); } @@ -1564,7 +1520,7 @@ internal static NewReference PyModule_New(string name) /// Return -1 on error, 0 on success. internal static int PyModule_AddObject(BorrowedReference module, string name, StolenReference value) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name); IntPtr valueAddr = value.DangerousGetAddressOrNull(); int res = Delegates.PyModule_AddObject(module, namePtr, valueAddr); // We can't just exit here because the reference is stolen only on success. @@ -1582,7 +1538,7 @@ internal static int PyModule_AddObject(BorrowedReference module, string name, St internal static NewReference PyImport_ImportModule(string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyImport_ImportModule(namePtr); } @@ -1591,7 +1547,7 @@ internal static NewReference PyImport_ImportModule(string name) internal static BorrowedReference PyImport_AddModule(string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyImport_AddModule(namePtr); } @@ -1619,13 +1575,13 @@ internal static void PySys_SetArgvEx(int argc, string[] argv, int updatepath) internal static BorrowedReference PySys_GetObject(string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PySys_GetObject(namePtr); } internal static int PySys_SetObject(string name, BorrowedReference ob) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PySys_SetObject(namePtr, ob); } @@ -1643,6 +1599,8 @@ internal static bool PyType_IsSubtype(BorrowedReference t1, BorrowedReference t2 return Delegates.PyType_IsSubtype(t1, t2); } + internal static bool PyObject_TypeCheckExact(BorrowedReference ob, BorrowedReference tp) + => PyObject_TYPE(ob) == tp; internal static bool PyObject_TypeCheck(BorrowedReference ob, BorrowedReference tp) { BorrowedReference t = PyObject_TYPE(ob); @@ -1722,7 +1680,7 @@ internal static IntPtr PyMem_Malloc(long size) internal static void PyErr_SetString(BorrowedReference ob, string message) { - using var msgPtr = new StrPtr(message, Encoding.UTF8); + using var msgPtr = new StrPtr(message); Delegates.PyErr_SetString(ob, msgPtr); } diff --git a/src/runtime/StateSerialization/MaybeType.cs b/src/runtime/StateSerialization/MaybeType.cs index f3c96e369..884b7edb0 100644 --- a/src/runtime/StateSerialization/MaybeType.cs +++ b/src/runtime/StateSerialization/MaybeType.cs @@ -15,7 +15,7 @@ internal struct MaybeType : ISerializable const string SerializationName = "n"; readonly string name; readonly Type type; - + public string DeletedMessage { get @@ -61,4 +61,4 @@ public void GetObjectData(SerializationInfo serializationInfo, StreamingContext serializationInfo.AddValue(SerializationName, name); } } -} \ No newline at end of file +} diff --git a/src/runtime/StateSerialization/NoopFormatter.cs b/src/runtime/StateSerialization/NoopFormatter.cs new file mode 100644 index 000000000..f05b7ebb2 --- /dev/null +++ b/src/runtime/StateSerialization/NoopFormatter.cs @@ -0,0 +1,14 @@ +using System; +using System.IO; +using System.Runtime.Serialization; + +namespace Python.Runtime; + +public class NoopFormatter : IFormatter { + public object Deserialize(Stream s) => throw new NotImplementedException(); + public void Serialize(Stream s, object o) {} + + public SerializationBinder? Binder { get; set; } + public StreamingContext Context { get; set; } + public ISurrogateSelector? SurrogateSelector { get; set; } +} diff --git a/src/runtime/StateSerialization/RuntimeData.cs b/src/runtime/StateSerialization/RuntimeData.cs index 204e15b5b..61e377aa4 100644 --- a/src/runtime/StateSerialization/RuntimeData.cs +++ b/src/runtime/StateSerialization/RuntimeData.cs @@ -1,7 +1,5 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; @@ -17,7 +15,36 @@ namespace Python.Runtime { public static class RuntimeData { - private static Type? _formatterType; + + public readonly static Func DefaultFormatterFactory = () => + { + try + { + var res = new BinaryFormatter(); + res.Serialize(new MemoryStream(), 1); // test if BinaryFormatter is usable + return res; + } + catch + { + return new NoopFormatter(); + } + }; + + private static Func _formatterFactory { get; set; } = DefaultFormatterFactory; + + public static Func FormatterFactory + { + get => _formatterFactory; + set + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + _formatterFactory = value; + } + } + + private static Type? _formatterType = null; public static Type? FormatterType { get => _formatterType; @@ -31,6 +58,14 @@ public static Type? FormatterType } } + /// + /// Callback called as a last step in the serialization process + /// + public static Action? PostStashHook { get; set; } = null; + /// + /// Callback called as the first step in the deserialization process + /// + public static Action? PreRestoreHook { get; set; } = null; public static ICLRObjectStorer? WrappersStorer { get; set; } /// @@ -74,6 +109,7 @@ internal static void Stash() using NewReference capsule = PyCapsule_New(mem, IntPtr.Zero, IntPtr.Zero); int res = PySys_SetObject("clr_data", capsule.BorrowOrThrow()); PythonException.ThrowIfIsNotZero(res); + PostStashHook?.Invoke(); } internal static void RestoreRuntimeData() @@ -90,6 +126,7 @@ internal static void RestoreRuntimeData() private static void RestoreRuntimeDataImpl() { + PreRestoreHook?.Invoke(); BorrowedReference capsule = PySys_GetObject("clr_data"); if (capsule.IsNull) { @@ -250,11 +287,102 @@ private static void RestoreRuntimeDataObjects(SharedObjectsState storage) } } + static readonly string serialization_key_namepsace = "pythonnet_serialization_"; + /// + /// Removes the serialization capsule from the `sys` module object. + /// + /// + /// The serialization data must have been set with StashSerializationData + /// + /// The name given to the capsule on the `sys` module object + public static void FreeSerializationData(string key) + { + key = serialization_key_namepsace + key; + BorrowedReference oldCapsule = PySys_GetObject(key); + if (!oldCapsule.IsNull) + { + IntPtr oldData = PyCapsule_GetPointer(oldCapsule, IntPtr.Zero); + Marshal.FreeHGlobal(oldData); + PyCapsule_SetPointer(oldCapsule, IntPtr.Zero); + PySys_SetObject(key, null); + } + } + + /// + /// Stores the data in the argument in a Python capsule and stores + /// the capsule on the `sys` module object with the name . + /// + /// + /// No checks on pre-existing names on the `sys` module object are made. + /// + /// The name given to the capsule on the `sys` module object + /// A MemoryStream that contains the data to be placed in the capsule + public static void StashSerializationData(string key, MemoryStream stream) + { + if (stream.TryGetBuffer(out var data)) + { + IntPtr mem = Marshal.AllocHGlobal(IntPtr.Size + data.Count); + + // store the length of the buffer first + Marshal.WriteIntPtr(mem, (IntPtr)data.Count); + Marshal.Copy(data.Array, data.Offset, mem + IntPtr.Size, data.Count); + + try + { + using NewReference capsule = PyCapsule_New(mem, IntPtr.Zero, IntPtr.Zero); + int res = PySys_SetObject(key, capsule.BorrowOrThrow()); + PythonException.ThrowIfIsNotZero(res); + } + catch + { + Marshal.FreeHGlobal(mem); + } + } + else + { + throw new NotImplementedException($"{nameof(stream)} must be exposable"); + } + + } + + static byte[] emptyBuffer = new byte[0]; + /// + /// Retreives the previously stored data on a Python capsule. + /// Throws if the object corresponding to the parameter + /// on the `sys` module object is not a capsule. + /// + /// The name given to the capsule on the `sys` module object + /// A MemoryStream containing the previously saved serialization data. + /// The stream is empty if no name matches the key. + public static MemoryStream GetSerializationData(string key) + { + BorrowedReference capsule = PySys_GetObject(key); + if (capsule.IsNull) + { + // nothing to do. + return new MemoryStream(emptyBuffer, writable:false); + } + var ptr = PyCapsule_GetPointer(capsule, IntPtr.Zero); + if (ptr == IntPtr.Zero) + { + // The PyCapsule API returns NULL on error; NULL cannot be stored + // as a capsule's value + PythonException.ThrowIfIsNull(null); + } + var len = (int)Marshal.ReadIntPtr(ptr); + byte[] buffer = new byte[len]; + Marshal.Copy(ptr+IntPtr.Size, buffer, 0, len); + return new MemoryStream(buffer, writable:false); + } + internal static IFormatter CreateFormatter() { - return FormatterType != null ? - (IFormatter)Activator.CreateInstance(FormatterType) - : new BinaryFormatter(); + + if (FormatterType != null) + { + return (IFormatter)Activator.CreateInstance(FormatterType); + } + return FormatterFactory(); } } } diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index 217b4820e..dbff1fbd4 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -374,7 +374,7 @@ static PyTuple GetBaseTypeTuple(Type clrType) return new PyTuple(bases); } - internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedReference py_base_type, BorrowedReference dictRef) + internal static NewReference CreateSubType(BorrowedReference py_name, ClassBase py_base_type, IList interfaces, BorrowedReference dictRef) { // Utility to create a subtype of a managed type with the ability for the // a python subtype able to override the managed implementation @@ -415,17 +415,10 @@ internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedRe } // create the new managed type subclassing the base managed type - if (ManagedType.GetManagedObject(py_base_type) is ClassBase baseClass) - { - return ReflectedClrType.CreateSubclass(baseClass, name, - ns: (string?)namespaceStr, - assembly: (string?)assembly, - dict: dictRef); - } - else - { - return Exceptions.RaiseTypeError("invalid base class, expected CLR class type"); - } + return ReflectedClrType.CreateSubclass(py_base_type, interfaces, name, + ns: (string?)namespaceStr, + assembly: (string?)assembly, + dict: dictRef); } internal static IntPtr WriteMethodDef(IntPtr mdef, IntPtr name, IntPtr func, PyMethodFlags flags, IntPtr doc) @@ -475,17 +468,20 @@ internal static PyType CreateMetatypeWithGCHandleOffset() int size = Util.ReadInt32(Runtime.PyTypeType, TypeOffset.tp_basicsize) + IntPtr.Size // tp_clr_inst_offset ; - var result = new PyType(new TypeSpec("clr._internal.GCOffsetBase", basicSize: size, - new TypeSpec.Slot[] - { - }, - TypeFlags.Default | TypeFlags.HeapType | TypeFlags.HaveGC), - bases: new PyTuple(new[] { py_type })); - - SetRequiredSlots(result, seen: new HashSet()); - - Runtime.PyType_Modified(result); + var slots = new[] { + new TypeSpec.Slot(TypeSlotID.tp_traverse, subtype_traverse), + new TypeSpec.Slot(TypeSlotID.tp_clear, subtype_clear) + }; + var result = new PyType( + new TypeSpec( + "clr._internal.GCOffsetBase", + basicSize: size, + slots: slots, + TypeFlags.Default | TypeFlags.HeapType | TypeFlags.HaveGC + ), + bases: new PyTuple(new[] { py_type }) + ); return result; } @@ -617,6 +613,16 @@ internal static PyType AllocateTypeObject(string name, PyType metatype) Util.WriteRef(type, TypeOffset.name, new NewReference(temp).Steal()); Util.WriteRef(type, TypeOffset.qualname, temp.Steal()); + // Ensure that tp_traverse and tp_clear are always set, since their + // existence is enforced in newer Python versions in PyType_Ready + Util.WriteIntPtr(type, TypeOffset.tp_traverse, subtype_traverse); + Util.WriteIntPtr(type, TypeOffset.tp_clear, subtype_clear); + + // This is a new mechanism in Python 3.14. We should eventually use it to implement + // a nicer type check, but for now we just need to ensure that it is set to NULL. + if (TypeOffset.ht_token != -1) + Util.WriteIntPtr(type, TypeOffset.ht_token, IntPtr.Zero); + InheritSubstructs(type.Reference.DangerousGetAddress()); return type; diff --git a/src/runtime/Types/ArrayObject.cs b/src/runtime/Types/ArrayObject.cs index b95934baf..f220d53fb 100644 --- a/src/runtime/Types/ArrayObject.cs +++ b/src/runtime/Types/ArrayObject.cs @@ -247,6 +247,12 @@ public static NewReference mp_subscript(BorrowedReference ob, BorrowedReference /// public static int mp_ass_subscript(BorrowedReference ob, BorrowedReference idx, BorrowedReference v) { + if (v.IsNull) + { + Exceptions.RaiseTypeError("'System.Array' object does not support item deletion"); + return -1; + } + var obj = (CLRObject)GetManagedObject(ob)!; var items = (Array)obj.inst; Type itemType = obj.inst.GetType().GetElementType(); diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 7296a1900..3fcb7ca4f 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -25,6 +25,7 @@ internal class ClassBase : ManagedType, IDeserializationCallback [NonSerialized] internal List dotNetMembers = new(); internal Indexer? indexer; + internal MethodBinder? del; internal readonly Dictionary richcompare = new(); internal MaybeType type; @@ -373,6 +374,8 @@ public static int tp_clear(BorrowedReference ob) return 0; } + static readonly HashSet ClearVisited = new(); + internal static unsafe int BaseUnmanagedClear(BorrowedReference ob) { var type = Runtime.PyObject_TYPE(ob); @@ -384,21 +387,20 @@ internal static unsafe int BaseUnmanagedClear(BorrowedReference ob) } var clear = (delegate* unmanaged[Cdecl])clearPtr; - bool usesSubtypeClear = clearPtr == TypeManager.subtype_clear; - if (usesSubtypeClear) + if (clearPtr == TypeManager.subtype_clear) { - // workaround for https://bugs.python.org/issue45266 (subtype_clear) - using var dict = Runtime.PyObject_GenericGetDict(ob); - if (Runtime.PyMapping_HasKey(dict.Borrow(), PyIdentifier.__clear_reentry_guard__) != 0) + var addr = ob.DangerousGetAddress(); + if (!ClearVisited.Add(addr)) return 0; - int res = Runtime.PyDict_SetItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__, Runtime.None); - if (res != 0) return res; - res = clear(ob); - Runtime.PyDict_DelItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__); + int res = clear(ob); + ClearVisited.Remove(addr); return res; } - return clear(ob); + else + { + return clear(ob); + } } protected override Dictionary OnSave(BorrowedReference ob) @@ -465,6 +467,11 @@ static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, Bo // with the index arg (method binders expect arg tuples). NewReference argsTuple = default; + if (v.IsNull) + { + return DelImpl(ob, idx, cls); + } + if (!Runtime.PyTuple_Check(idx)) { argsTuple = Runtime.PyTuple_New(1); @@ -497,13 +504,45 @@ static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, Bo // Add value to argument list Runtime.PyTuple_SetItem(real.Borrow(), i, v); - cls.indexer.SetItem(ob, real.Borrow()); + using var result = cls.indexer.SetItem(ob, real.Borrow()); + return result.IsNull() ? -1 : 0; + } + + /// Implements __delitem__ (del x[...]) for IList<T> and IDictionary<TKey, TValue>. + private static int DelImpl(BorrowedReference ob, BorrowedReference idx, ClassBase cls) + { + if (cls.del is null) + { + Exceptions.SetError(Exceptions.TypeError, "object does not support item deletion"); + return -1; + } + + if (Runtime.PyTuple_Check(idx)) + { + Exceptions.SetError(Exceptions.TypeError, "multi-index deletion not supported"); + return -1; + } + + using var argsTuple = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(argsTuple.Borrow(), 0, idx); + using var result = cls.del.Invoke(ob, argsTuple.Borrow(), kw: null); + if (result.IsNull()) + return -1; - if (Exceptions.ErrorOccurred()) + if (Runtime.PyBool_CheckExact(result.Borrow())) { + if (Runtime.PyObject_IsTrue(result.Borrow()) != 0) + return 0; + + Exceptions.SetError(Exceptions.KeyError, "key not found"); return -1; } + if (!result.IsNone()) + { + Exceptions.warn("unsupported return type for __delitem__", Exceptions.TypeError); + } + return 0; } diff --git a/src/runtime/Types/ClassDerived.cs b/src/runtime/Types/ClassDerived.cs index 02288faee..592eefd55 100644 --- a/src/runtime/Types/ClassDerived.cs +++ b/src/runtime/Types/ClassDerived.cs @@ -144,6 +144,7 @@ internal static NewReference ToPython(IPythonDerivedType obj) /// internal static Type CreateDerivedType(string name, Type baseType, + IList typeInterfaces, BorrowedReference py_dict, string? namespaceStr, string? assemblyName, @@ -163,7 +164,9 @@ internal static Type CreateDerivedType(string name, ModuleBuilder moduleBuilder = GetModuleBuilder(assemblyName, moduleName); Type baseClass = baseType; - var interfaces = new List { typeof(IPythonDerivedType) }; + var interfaces = new HashSet { typeof(IPythonDerivedType) }; + foreach(var interfaceType in typeInterfaces) + interfaces.Add(interfaceType); // if the base type is an interface then use System.Object as the base class // and add the base type to the list of interfaces this new class will implement. @@ -214,13 +217,17 @@ internal static Type CreateDerivedType(string name, } } - // override any virtual methods not already overridden by the properties above - MethodInfo[] methods = baseType.GetMethods(); + // override any virtual not already overridden by the properties above + // also override any interface method. + var methods = baseType.GetMethods().Concat(interfaces.SelectMany(x => x.GetMethods())); var virtualMethods = new HashSet(); foreach (MethodInfo method in methods) { if (!method.Attributes.HasFlag(MethodAttributes.Virtual) | - method.Attributes.HasFlag(MethodAttributes.Final)) + method.Attributes.HasFlag(MethodAttributes.Final) + // overriding generic virtual methods is not supported + // so a call to that should be deferred to the base class method. + || method.IsGenericMethod) { continue; } @@ -436,6 +443,7 @@ private static void AddVirtualMethod(MethodInfo method, Type baseType, TypeBuild il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ldtoken, method); + il.Emit(OpCodes.Ldtoken, method.DeclaringType); #pragma warning disable CS0618 // PythonDerivedType is for internal use only if (method.ReturnType == typeof(void)) { @@ -505,6 +513,7 @@ private static void AddPythonMethod(string methodName, PyObject func, TypeBuilde il.DeclareLocal(typeof(object[])); il.DeclareLocal(typeof(RuntimeMethodHandle)); + il.DeclareLocal(typeof(RuntimeTypeHandle)); // this il.Emit(OpCodes.Ldarg_0); @@ -546,6 +555,11 @@ private static void AddPythonMethod(string methodName, PyObject func, TypeBuilde il.Emit(OpCodes.Ldloca_S, 1); il.Emit(OpCodes.Initobj, typeof(RuntimeMethodHandle)); il.Emit(OpCodes.Ldloc_1); + + // type handle is also not required + il.Emit(OpCodes.Ldloca_S, 2); + il.Emit(OpCodes.Initobj, typeof(RuntimeTypeHandle)); + il.Emit(OpCodes.Ldloc_2); #pragma warning disable CS0618 // PythonDerivedType is for internal use only // invoke the method @@ -698,7 +712,7 @@ public class PythonDerivedType /// class) it calls it, otherwise it calls the base method. /// public static T? InvokeMethod(IPythonDerivedType obj, string methodName, string origMethodName, - object[] args, RuntimeMethodHandle methodHandle) + object[] args, RuntimeMethodHandle methodHandle, RuntimeTypeHandle declaringTypeHandle) { var self = GetPyObj(obj); @@ -724,7 +738,10 @@ public class PythonDerivedType } PyObject py_result = method.Invoke(pyargs); - PyTuple? result_tuple = MarshalByRefsBack(args, methodHandle, py_result, outsOffset: 1); + var clrMethod = methodHandle != default + ? MethodBase.GetMethodFromHandle(methodHandle, declaringTypeHandle) + : null; + PyTuple? result_tuple = MarshalByRefsBack(args, clrMethod, py_result, outsOffset: 1); return result_tuple is not null ? result_tuple[0].As() : py_result.As(); @@ -754,7 +771,7 @@ public class PythonDerivedType } public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, string origMethodName, - object?[] args, RuntimeMethodHandle methodHandle) + object?[] args, RuntimeMethodHandle methodHandle, RuntimeTypeHandle declaringTypeHandle) { var self = GetPyObj(obj); if (null != self.Ref) @@ -779,7 +796,10 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s } PyObject py_result = method.Invoke(pyargs); - MarshalByRefsBack(args, methodHandle, py_result, outsOffset: 0); + var clrMethod = methodHandle != default + ? MethodBase.GetMethodFromHandle(methodHandle, declaringTypeHandle) + : null; + MarshalByRefsBack(args, clrMethod, py_result, outsOffset: 0); return; } } @@ -811,12 +831,11 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s /// as a tuple of new values for those arguments, and updates corresponding /// elements of array. /// - private static PyTuple? MarshalByRefsBack(object?[] args, RuntimeMethodHandle methodHandle, PyObject pyResult, int outsOffset) + private static PyTuple? MarshalByRefsBack(object?[] args, MethodBase? method, PyObject pyResult, int outsOffset) { - if (methodHandle == default) return null; + if (method is null) return null; - var originalMethod = MethodBase.GetMethodFromHandle(methodHandle); - var parameters = originalMethod.GetParameters(); + var parameters = method.GetParameters(); PyTuple? outs = null; int byrefIndex = 0; for (int i = 0; i < parameters.Length; ++i) diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs index cc42039e8..f0585ffa6 100644 --- a/src/runtime/Types/ClassObject.cs +++ b/src/runtime/Types/ClassObject.cs @@ -1,7 +1,9 @@ using System; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Runtime.Serialization; namespace Python.Runtime @@ -47,6 +49,55 @@ internal NewReference GetDocString() return Runtime.PyString_FromString(str); } + private static string ConvertFlags(Enum value) + { + Type primitiveType = value.GetType().GetEnumUnderlyingType(); + string format = "X" + (Marshal.SizeOf(primitiveType) * 2).ToString(CultureInfo.InvariantCulture); + var primitive = (IFormattable)Convert.ChangeType(value, primitiveType); + return "0x" + primitive.ToString(format, null); + + } + + private static string ConvertValue(Enum value) + { + Type primitiveType = value.GetType().GetEnumUnderlyingType(); + return Convert.ChangeType(value, primitiveType).ToString()!; + } + + /// + /// given an enum, write a __repr__ string formatted in the same + /// way as a python repr string. Something like: + /// '<Color.GREEN: 2>'; + /// with a binary value for [Flags] enums + /// + /// Instace of the enum object + /// + private static string GetEnumReprString(Enum inst) + { + var obType = inst.GetType(); + + string strValue2 = obType.IsFlagsEnum() ? ConvertFlags(inst) : ConvertValue(inst); + + var repr = $"<{obType.Name}.{inst}: {strValue2}>"; + return repr; + } + + /// + /// ClassObject __repr__ implementation. + /// + public new static NewReference tp_repr(BorrowedReference ob) + { + if (GetManagedObject(ob) is not CLRObject co) + { + return Exceptions.RaiseTypeError("invalid object"); + } + if (co.inst.GetType().IsEnum) + { + return Runtime.PyString_FromString(GetEnumReprString((Enum)co.inst)); + } + + return ClassBase.tp_repr(ob); + } /// /// Implements __new__ for reflected classes and value types. diff --git a/src/runtime/Types/ClrObject.cs b/src/runtime/Types/ClrObject.cs index 4cf9062cb..afa136414 100644 --- a/src/runtime/Types/ClrObject.cs +++ b/src/runtime/Types/ClrObject.cs @@ -11,10 +11,15 @@ internal sealed class CLRObject : ManagedType { internal readonly object inst; + internal static bool creationBlocked = false; + // "borrowed" references internal static readonly HashSet reflectedObjects = new(); static NewReference Create(object ob, BorrowedReference tp) { + if (creationBlocked) + throw new InvalidOperationException("Reflected objects should not be created anymore."); + Debug.Assert(tp != null); var py = Runtime.PyType_GenericAlloc(tp, 0); @@ -61,6 +66,9 @@ internal static void Restore(object ob, BorrowedReference pyHandle, Dictionary? context) { + if (creationBlocked) + throw new InvalidOperationException("Reflected objects should not be loaded anymore."); + base.OnLoad(ob, context); GCHandle gc = GCHandle.Alloc(this); SetGCHandle(ob, gc); diff --git a/src/runtime/Types/Indexer.cs b/src/runtime/Types/Indexer.cs index 4903b6f76..fe6dab4c9 100644 --- a/src/runtime/Types/Indexer.cs +++ b/src/runtime/Types/Indexer.cs @@ -50,9 +50,9 @@ internal NewReference GetItem(BorrowedReference inst, BorrowedReference args) } - internal void SetItem(BorrowedReference inst, BorrowedReference args) + internal NewReference SetItem(BorrowedReference inst, BorrowedReference args) { - SetterBinder.Invoke(inst, args, null); + return SetterBinder.Invoke(inst, args, null); } internal bool NeedsDefaultArgs(BorrowedReference args) diff --git a/src/runtime/Types/ManagedType.cs b/src/runtime/Types/ManagedType.cs index 2ed9d7970..97a19497c 100644 --- a/src/runtime/Types/ManagedType.cs +++ b/src/runtime/Types/ManagedType.cs @@ -148,8 +148,9 @@ protected static void ClearObjectDict(BorrowedReference ob) { BorrowedReference type = Runtime.PyObject_TYPE(ob); int instanceDictOffset = Util.ReadInt32(type, TypeOffset.tp_dictoffset); - Debug.Assert(instanceDictOffset > 0); - Runtime.Py_CLEAR(ob, instanceDictOffset); + // Debug.Assert(instanceDictOffset > 0); + if (instanceDictOffset > 0) + Runtime.Py_CLEAR(ob, instanceDictOffset); } protected static BorrowedReference GetObjectDict(BorrowedReference ob) diff --git a/src/runtime/Types/ManagedTypes.cd b/src/runtime/Types/ManagedTypes.cd index 9a3e3de16..e6759265f 100644 --- a/src/runtime/Types/ManagedTypes.cd +++ b/src/runtime/Types/ManagedTypes.cd @@ -1,4 +1,4 @@ - + diff --git a/src/runtime/Types/MetaType.cs b/src/runtime/Types/MetaType.cs index 5b59f5139..ecbc9cac5 100644 --- a/src/runtime/Types/MetaType.cs +++ b/src/runtime/Types/MetaType.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; +using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Serialization; @@ -18,6 +21,7 @@ internal sealed class MetaType : ManagedType // set in Initialize private static PyType PyCLRMetaType; private static SlotsHolder _metaSlotsHodler; + private static int TypeDictOffset = -1; #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. internal static readonly string[] CustomMethods = new string[] @@ -32,6 +36,25 @@ internal sealed class MetaType : ManagedType public static PyType Initialize() { PyCLRMetaType = TypeManager.CreateMetaType(typeof(MetaType), out _metaSlotsHodler); + + // Retrieve the offset of the type's dictionary from PyType_Type for + // use in the tp_setattro implementation. + using (NewReference dictOffset = Runtime.PyObject_GetAttr(Runtime.PyTypeType, PyIdentifier.__dictoffset__)) + { + if (dictOffset.IsNull()) + { + throw new InvalidOperationException("Could not get __dictoffset__ from PyType_Type"); + } + + nint dictOffsetVal = Runtime.PyLong_AsSignedSize_t(dictOffset.Borrow()); + if (dictOffsetVal <= 0) + { + throw new InvalidOperationException("Could not get __dictoffset__ from PyType_Type"); + } + + TypeDictOffset = checked((int)dictOffsetVal); + } + return PyCLRMetaType; } @@ -41,6 +64,7 @@ public static void Release() { _metaSlotsHodler.ResetSlots(); } + TypeDictOffset = -1; PyCLRMetaType.Dispose(); } @@ -79,41 +103,80 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, BorrowedReference bases = Runtime.PyTuple_GetItem(args, 1); BorrowedReference dict = Runtime.PyTuple_GetItem(args, 2); - // We do not support multiple inheritance, so the bases argument - // should be a 1-item tuple containing the type we are subtyping. - // That type must itself have a managed implementation. We check - // that by making sure its metatype is the CLR metatype. + // Extract interface types and base class types. + var interfaces = new List(); - if (Runtime.PyTuple_Size(bases) != 1) - { - return Exceptions.RaiseTypeError("cannot use multiple inheritance with managed classes"); - } - - BorrowedReference base_type = Runtime.PyTuple_GetItem(bases, 0); - BorrowedReference mt = Runtime.PyObject_TYPE(base_type); + // More than one base type case be declared, but an exception will be thrown + // if more than one is a class/not an interface. + var baseTypes = new List(); - if (!(mt == PyCLRMetaType || mt == Runtime.PyTypeType)) + var baseClassCount = Runtime.PyTuple_Size(bases); + if (baseClassCount == 0) { - return Exceptions.RaiseTypeError("invalid metatype"); + return Exceptions.RaiseTypeError("zero base classes "); } - // Ensure that the reflected type is appropriate for subclassing, - // disallowing subclassing of delegates, enums and array types. - - if (GetManagedObject(base_type) is ClassBase cb) + for (nint i = 0; i < baseClassCount; i++) { - try + var baseTypeIt = Runtime.PyTuple_GetItem(bases, (int)i); + + if (GetManagedObject(baseTypeIt) is ClassBase classBaseIt) { - if (!cb.CanSubclass()) + if (!classBaseIt.type.Valid) + { + return Exceptions.RaiseTypeError("Invalid type used as a super type."); + } + if (classBaseIt.type.Value.IsInterface) + { + interfaces.Add(classBaseIt.type.Value); + } + else { - return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed"); + baseTypes.Add(classBaseIt); } } - catch (SerializationException) + else + { + return Exceptions.RaiseTypeError("Non .NET type used as super class for meta type. This is not supported."); + } + } + // if the base type count is 0, there might still be interfaces to implement. + if (baseTypes.Count == 0) + { + baseTypes.Add(new ClassBase(typeof(object))); + } + + // Multiple inheritance is not supported, unless the other types are interfaces + if (baseTypes.Count > 1) + { + var types = string.Join(", ", baseTypes.Select(baseType => baseType.type.Value)); + return Exceptions.RaiseTypeError($"Multiple inheritance with managed classes cannot be used. Types: {types} "); + } + + // check if the list of interfaces contains no duplicates. + if (interfaces.Distinct().Count() != interfaces.Count) + { + // generate a string containing the problematic types. + var duplicateTypes = interfaces.GroupBy(type => type) + .Where(typeGroup => typeGroup.Count() > 1) + .Select(typeGroup => typeGroup.Key); + var duplicateTypesString = string.Join(", ", duplicateTypes); + + return Exceptions.RaiseTypeError($"An interface can only be implemented once. Duplicate types: {duplicateTypesString}"); + } + + var cb = baseTypes[0]; + try + { + if (!cb.CanSubclass()) { - return Exceptions.RaiseTypeError($"Underlying C# Base class {cb.type} has been deleted"); + return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed"); } } + catch (SerializationException) + { + return Exceptions.RaiseTypeError($"Underlying C# Base class {cb.type} has been deleted"); + } BorrowedReference slots = Runtime.PyDict_GetItem(dict, PyIdentifier.__slots__); if (slots != null) @@ -130,10 +193,12 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, using var clsDict = new PyDict(dict); if (clsDict.HasKey("__assembly__") || clsDict.HasKey("__namespace__")) { - return TypeManager.CreateSubType(name, base_type, clsDict); + return TypeManager.CreateSubType(name, baseTypes[0], interfaces, clsDict); } } + var base_type = Runtime.PyTuple_GetItem(bases, 0); + // otherwise just create a basic type without reflecting back into the managed side. IntPtr func = Util.ReadIntPtr(Runtime.PyTypeType, TypeOffset.tp_new); NewReference type = NativeCall.Call_3(func, tp, args, kw); @@ -243,7 +308,28 @@ public static int tp_setattro(BorrowedReference tp, BorrowedReference name, Borr } } - int res = Runtime.PyObject_GenericSetAttr(tp, name, value); + // Access the type's dictionary directly + // + // We can not use the PyObject_GenericSetAttr because since Python + // 3.14 as https://github.com/python/cpython/pull/118454 intrdoduced + // an assertion to prevent it from being called from metatypes. + // + // The direct dictionary access is equivalent to what Cython does + // to work around the same issue: https://github.com/cython/cython/pull/6325 + BorrowedReference typeDict = new(Util.ReadIntPtr(tp, TypeDictOffset)); + int res; + if (value.IsNull) + { + res = Runtime.PyDict_DelItem(typeDict, name); + if (res != 0) + { + Exceptions.SetError(Exceptions.AttributeError, "attribute not found"); + } + } + else + { + res = Runtime.PyDict_SetItem(typeDict, name, value); + } Runtime.PyType_Modified(tp); return res; diff --git a/src/runtime/Types/MethodBinding.cs b/src/runtime/Types/MethodBinding.cs index 334d705a6..bfe22b0f3 100644 --- a/src/runtime/Types/MethodBinding.cs +++ b/src/runtime/Types/MethodBinding.cs @@ -237,6 +237,7 @@ public static NewReference tp_call(BorrowedReference ob, BorrowedReference args, } } } + return self.m.Invoke(target is null ? BorrowedReference.Null : target, args, kw, self.info.UnsafeValue); } finally diff --git a/src/runtime/Types/MethodObject.cs b/src/runtime/Types/MethodObject.cs index 05198da76..12484d301 100644 --- a/src/runtime/Types/MethodObject.cs +++ b/src/runtime/Types/MethodObject.cs @@ -19,20 +19,20 @@ internal class MethodObject : ExtensionType { [NonSerialized] private MethodBase[]? _info = null; + private readonly List infoList; internal string name; internal readonly MethodBinder binder; internal bool is_static = false; - internal PyString? doc; internal MaybeType type; - public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads) + public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads, bool argsReversed = false) { this.type = type; this.name = name; this.infoList = new List(); - binder = new MethodBinder(); + binder = new MethodBinder() { argsReversed = argsReversed }; foreach (MethodBase item in info) { this.infoList.Add(item); @@ -45,8 +45,8 @@ public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_t binder.allow_threads = allow_threads; } - public MethodObject(MaybeType type, string name, MethodBase[] info) - : this(type, name, info, allow_threads: AllowThreads(info)) + public MethodObject(MaybeType type, string name, MethodBase[] info, bool argsReversed = false) + : this(type, name, info, allow_threads: AllowThreads(info), argsReversed) { } diff --git a/src/runtime/Types/OperatorMethod.cs b/src/runtime/Types/OperatorMethod.cs index e3cc23370..a2ca73982 100644 --- a/src/runtime/Types/OperatorMethod.cs +++ b/src/runtime/Types/OperatorMethod.cs @@ -177,17 +177,14 @@ public static string ReversePyMethodName(string pyName) } /// - /// Check if the method is performing a reverse operation. + /// Check if the method should have a reversed operation. /// /// The operator method. /// - public static bool IsReverse(MethodBase method) + public static bool HaveReverse(MethodBase method) { - Type primaryType = method.IsOpsHelper() - ? method.DeclaringType.GetGenericArguments()[0] - : method.DeclaringType; - Type leftOperandType = method.GetParameters()[0].ParameterType; - return leftOperandType != primaryType; + var pi = method.GetParameters(); + return OpMethodMap.ContainsKey(method.Name) && pi.Length == 2; } public static void FilterMethods(MethodBase[] methods, out MethodBase[] forwardMethods, out MethodBase[] reverseMethods) @@ -196,14 +193,11 @@ public static void FilterMethods(MethodBase[] methods, out MethodBase[] forwardM var reverseMethodsList = new List(); foreach (var method in methods) { - if (IsReverse(method)) + forwardMethodsList.Add(method); + if (HaveReverse(method)) { reverseMethodsList.Add(method); - } else - { - forwardMethodsList.Add(method); } - } forwardMethods = forwardMethodsList.ToArray(); reverseMethods = reverseMethodsList.ToArray(); diff --git a/src/runtime/Types/ReflectedClrType.cs b/src/runtime/Types/ReflectedClrType.cs index d3d89bdb8..df9b26c29 100644 --- a/src/runtime/Types/ReflectedClrType.cs +++ b/src/runtime/Types/ReflectedClrType.cs @@ -30,22 +30,29 @@ public static ReflectedClrType GetOrCreate(Type type) return pyType; } - // Ensure, that matching Python type exists first. - // It is required for self-referential classes - // (e.g. with members, that refer to the same class) - pyType = AllocateClass(type); - ClassManager.cache.Add(type, pyType); + try + { + // Ensure, that matching Python type exists first. + // It is required for self-referential classes + // (e.g. with members, that refer to the same class) + pyType = AllocateClass(type); + ClassManager.cache.Add(type, pyType); - var impl = ClassManager.CreateClass(type); + var impl = ClassManager.CreateClass(type); - TypeManager.InitializeClassCore(type, pyType, impl); + TypeManager.InitializeClassCore(type, pyType, impl); - ClassManager.InitClassBase(type, impl, pyType); + ClassManager.InitClassBase(type, impl, pyType); - // Now we force initialize the Python type object to reflect the given - // managed type, filling the Python type slots with thunks that - // point to the managed methods providing the implementation. - TypeManager.InitializeClass(pyType, impl, type); + // Now we force initialize the Python type object to reflect the given + // managed type, filling the Python type slots with thunks that + // point to the managed methods providing the implementation. + TypeManager.InitializeClass(pyType, impl, type); + } + catch (Exception e) + { + throw new InternalPythonnetException($"Failed to create Python type for {type.FullName}", e); + } return pyType; } @@ -68,7 +75,7 @@ internal void Restore(ClassBase cb) TypeManager.InitializeClass(this, cb, cb.type.Value); } - internal static NewReference CreateSubclass(ClassBase baseClass, + internal static NewReference CreateSubclass(ClassBase baseClass, IList interfaces, string name, string? assembly, string? ns, BorrowedReference dict) { @@ -76,6 +83,7 @@ internal static NewReference CreateSubclass(ClassBase baseClass, { Type subType = ClassDerivedObject.CreateDerivedType(name, baseClass.type.Value, + interfaces, dict, ns, assembly); diff --git a/src/runtime/Util/CodeGenerator.cs b/src/runtime/Util/CodeGenerator.cs index 35a637113..6e0859da0 100644 --- a/src/runtime/Util/CodeGenerator.cs +++ b/src/runtime/Util/CodeGenerator.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Globalization; +using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Threading; @@ -17,13 +19,15 @@ internal class CodeGenerator private readonly AssemblyBuilder aBuilder; private readonly ModuleBuilder mBuilder; + const string NamePrefix = "__Python_Runtime_Generated_"; + internal CodeGenerator() { - var aname = new AssemblyName { Name = "__CodeGenerator_Assembly" }; + var aname = new AssemblyName { Name = GetUniqueAssemblyName(NamePrefix + "Assembly") }; var aa = AssemblyBuilderAccess.Run; aBuilder = Thread.GetDomain().DefineDynamicAssembly(aname, aa); - mBuilder = aBuilder.DefineDynamicModule("__CodeGenerator_Module"); + mBuilder = aBuilder.DefineDynamicModule(NamePrefix + "Module"); } /// @@ -77,5 +81,20 @@ internal static void GenerateMarshalByRefsBack(ILGenerator il, IReadOnlyList(AppDomain.CurrentDomain + .GetAssemblies() + .Select(a => a.GetName().Name)); + for (int i = 0; i < int.MaxValue; i++) + { + string candidate = name + i.ToString(CultureInfo.InvariantCulture); + if (!taken.Contains(candidate)) + return candidate; + } + + throw new NotSupportedException("Too many assemblies"); + } } } diff --git a/src/runtime/Util/Encodings.cs b/src/runtime/Util/Encodings.cs new file mode 100644 index 000000000..d5a0c6ff8 --- /dev/null +++ b/src/runtime/Util/Encodings.cs @@ -0,0 +1,10 @@ +using System; +using System.Text; + +namespace Python.Runtime; + +static class Encodings { + public static System.Text.Encoding UTF8 = new UTF8Encoding(false, true); + public static System.Text.Encoding UTF16 = new UnicodeEncoding(!BitConverter.IsLittleEndian, false, true); + public static System.Text.Encoding UTF32 = new UTF32Encoding(!BitConverter.IsLittleEndian, false, true); +} diff --git a/src/runtime/Util/EventHandlerCollection.cs b/src/runtime/Util/EventHandlerCollection.cs index 551893799..0cd03d0fd 100644 --- a/src/runtime/Util/EventHandlerCollection.cs +++ b/src/runtime/Util/EventHandlerCollection.cs @@ -99,6 +99,10 @@ internal bool RemoveEventHandler(BorrowedReference target, BorrowedReference han continue; } list.RemoveAt(i); + if (list.Count == 0) + { + Remove(key); + } return true; } diff --git a/src/runtime/Util/PythonEnvironment.cs b/src/runtime/Util/PythonEnvironment.cs new file mode 100644 index 000000000..b1ebc7fa5 --- /dev/null +++ b/src/runtime/Util/PythonEnvironment.cs @@ -0,0 +1,188 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using static System.FormattableString; + +namespace Python.Runtime; + + +internal class PythonEnvironment +{ + readonly static string PYDLL_ENV_VAR = "PYTHONNET_PYDLL"; + readonly static string PYEXE_ENV_VAR = "PYTHONNET_PYEXE"; + readonly static string PYNET_VENV_ENV_VAR = "PYTHONNET_VENV"; + readonly static string VENV_ENV_VAR = "VIRTUAL_ENV"; + + public string? VenvPath { get; private set; } + public string? Home { get; private set; } + public Version? Version { get; private set; } + public string? ProgramName { get; set; } + public string? LibPython { get; set; } + + public bool IsValid => + !string.IsNullOrEmpty(ProgramName) && !string.IsNullOrEmpty(LibPython); + + + // TODO: Move the lib-guessing step to separate function, use together with + // PYTHONNET_PYEXE or a path lookup as last resort + + // Initialize PythonEnvironment instance from environment variables. + // + // If PYTHONNET_PYEXE and PYTHONNET_PYDLL are set, these always have precedence. + // If PYTHONNET_VENV or VIRTUAL_ENV is set, we interpret the environment as a venv + // and set the ProgramName/LibPython accordingly. PYTHONNET_VENV takes precedence. + public static PythonEnvironment FromEnv() + { + var pydll = Environment.GetEnvironmentVariable(PYDLL_ENV_VAR); + var pydllSet = !string.IsNullOrEmpty(pydll); + var pyexe = Environment.GetEnvironmentVariable(PYEXE_ENV_VAR); + var pyexeSet = !string.IsNullOrEmpty(pyexe); + var pynetVenv = Environment.GetEnvironmentVariable(PYNET_VENV_ENV_VAR); + var pynetVenvSet = !string.IsNullOrEmpty(pynetVenv); + var venv = Environment.GetEnvironmentVariable(VENV_ENV_VAR); + var venvSet = !string.IsNullOrEmpty(venv); + + PythonEnvironment? res = new(); + + if (pynetVenvSet) + res = FromVenv(pynetVenv) ?? res; + else if (venvSet) + res = FromVenv(venv) ?? res; + + if (pyexeSet) + res.ProgramName = pyexe; + + if (pydllSet) + res.LibPython = pydll; + + return res; + } + + public static PythonEnvironment? FromVenv(string path) + { + var env = new PythonEnvironment + { + VenvPath = path + }; + + string venvCfg = Path.Combine(path, "pyvenv.cfg"); + + if (!File.Exists(venvCfg)) + return null; + + var settings = TryParse(venvCfg); + + if (!settings.ContainsKey("home")) + return null; + + env.Home = settings["home"]; + var pname = ProgramNameFromPath(path); + if (File.Exists(pname)) + env.ProgramName = pname; + + if (settings.TryGetValue("version", out string versionStr)) + { + _ = Version.TryParse(versionStr, out Version versionObj); + env.Version = versionObj; + } + else if (settings.TryGetValue("version_info", out versionStr)) + { + _ = Version.TryParse(versionStr, out Version versionObj); + env.Version = versionObj; + } + + env.LibPython = FindLibPython(env.Home, env.Version); + + return env; + } + + private static Dictionary TryParse(string venvCfg) + { + var settings = new Dictionary(); + + string[] lines = File.ReadAllLines(venvCfg); + + // The actually used format is really primitive: " = " + foreach (string line in lines) + { + var split = line.Split(new[] { '=' }, 2); + + if (split.Length != 2) + continue; + + settings[split[0].Trim()] = split[1].Trim(); + } + + return settings; + } + + private static string? FindLibPython(string home, Version? maybeVersion) + { + // TODO: Check whether there is a .dll/.so/.dylib next to the executable + + if (maybeVersion is Version version) + { + return FindLibPythonInHome(home, version); + } + + return null; + } + + private static string? FindLibPythonInHome(string home, Version version) + { + var libPythonName = GetDefaultDllName(version); + + List pathsToCheck = new(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + var arch = RuntimeInformation.ProcessArchitecture; + if (arch == Architecture.X64 || arch == Architecture.Arm64) + { + // multilib systems + pathsToCheck.Add("../lib64"); + } + pathsToCheck.Add("../lib"); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + pathsToCheck.Add("."); + } + else + { + pathsToCheck.Add("../lib"); + } + + return pathsToCheck + .Select(path => Path.Combine(home, path, libPythonName)) + .FirstOrDefault(File.Exists); + } + + private static string ProgramNameFromPath(string path) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Path.Combine(path, "Scripts", "python.exe"); + } + else + { + return Path.Combine(path, "bin", "python"); + } + } + + internal static string GetDefaultDllName(Version version) + { + string prefix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "" : "lib"; + + string suffix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Invariant($"{version.Major}{version.Minor}") + : Invariant($"{version.Major}.{version.Minor}"); + + string ext = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".dll" + : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? ".dylib" + : ".so"; + + return prefix + "python" + suffix + ext; + } +} diff --git a/src/runtime/Util/ReflectionUtil.cs b/src/runtime/Util/ReflectionUtil.cs index 58d0a506e..0fad2d4b2 100644 --- a/src/runtime/Util/ReflectionUtil.cs +++ b/src/runtime/Util/ReflectionUtil.cs @@ -53,4 +53,11 @@ public static BindingFlags GetBindingFlags(this PropertyInfo property) flags |= accessor.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic; return flags; } + + public static Type? TryGetGenericDefinition(this Type type) + { + if (type is null) throw new ArgumentNullException(nameof(type)); + + return type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : null; + } } diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index 3adc5c0c6..c2455b1a5 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -1,6 +1,6 @@ - netstandard2.0;net6.0 + netstandard2.0;net10.0 true true ..\pythonnet.snk diff --git a/src/testing/classtest.cs b/src/testing/classtest.cs index 68c0d8c55..993afdfc9 100644 --- a/src/testing/classtest.cs +++ b/src/testing/classtest.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; namespace Python.Test @@ -59,4 +60,15 @@ public ClassCtorTest2(string v) internal class InternalClass { } + + public class SimpleClass + { + public static void TestObject(object obj) + { + if ((!(obj is ISayHello1 && obj is SimpleClass))) + { + throw new Exception("Expected ISayHello and SimpleClass instance"); + } + } + } } diff --git a/src/testing/generictest.cs b/src/testing/generictest.cs index 238435811..b333910c2 100644 --- a/src/testing/generictest.cs +++ b/src/testing/generictest.cs @@ -136,4 +136,12 @@ public static T[] EchoRange(T[] items) return items; } } + + public abstract class GenericVirtualMethodTest + { + public virtual Q VirtMethod(Q arg1) + { + return arg1; + } + } } diff --git a/src/testing/interfacetest.cs b/src/testing/interfacetest.cs index 7c5d937b9..a1a7106b5 100644 --- a/src/testing/interfacetest.cs +++ b/src/testing/interfacetest.cs @@ -79,7 +79,7 @@ private interface IPrivate { } } - + public interface IOutArg { string MyMethod_Out(string name, out int index); @@ -93,4 +93,25 @@ public static int CallMyMethod_Out(IOutArg myInterface) return index; } } + + public interface IGenericInterface + { + public T Get(T x); + } + + public class SpecificInterfaceUser + { + public SpecificInterfaceUser(IGenericInterface some, int x) + { + some.Get(x); + } + } + + public class GenericInterfaceUser + { + public GenericInterfaceUser(IGenericInterface some, T x) + { + some.Get(x); + } + } } diff --git a/tests/conftest.py b/tests/conftest.py index e61e3680e..1ac20e1dd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -79,9 +79,10 @@ def pytest_configure(config): ["dotnet", "publish", "-f", fw, "-o", str(bin_path), str(test_proj_path)] ) - from pythonnet import load - - load(runtime_opt, **runtime_params) + import os + os.environ["PYTHONNET_RUNTIME"] = runtime_opt + for k, v in runtime_params.items(): + os.environ[f"PYTHONNET_{runtime_opt.upper()}_{k.upper()}"] = v import clr diff --git a/tests/domain_tests/App.config b/tests/domain_tests/App.config index 56efbc7b5..20939707c 100644 --- a/tests/domain_tests/App.config +++ b/tests/domain_tests/App.config @@ -1,4 +1,4 @@ - + diff --git a/tests/domain_tests/Python.DomainReloadTests.csproj b/tests/domain_tests/Python.DomainReloadTests.csproj index 9cb61c6f4..a7d6d4f6b 100644 --- a/tests/domain_tests/Python.DomainReloadTests.csproj +++ b/tests/domain_tests/Python.DomainReloadTests.csproj @@ -14,13 +14,17 @@ - + - + + 1.* + all + runtime; build; native; contentfiles; analyzers + diff --git a/tests/domain_tests/TestRunner.cs b/tests/domain_tests/TestRunner.cs index 4f6a3ea28..bbee81b3d 100644 --- a/tests/domain_tests/TestRunner.cs +++ b/tests/domain_tests/TestRunner.cs @@ -1132,6 +1132,66 @@ import System ", }, + new TestCase + { + Name = "test_serialize_unserializable_object", + DotNetBefore = @" + namespace TestNamespace + { + public class NotSerializableTextWriter : System.IO.TextWriter + { + override public System.Text.Encoding Encoding { get { return System.Text.Encoding.ASCII;} } + } + [System.Serializable] + public static class SerializableWriter + { + private static System.IO.TextWriter _writer = null; + public static System.IO.TextWriter Writer {get { return _writer; }} + public static void CreateInternalWriter() + { + _writer = System.IO.TextWriter.Synchronized(new NotSerializableTextWriter()); + } + } + } +", + DotNetAfter = @" + namespace TestNamespace + { + public class NotSerializableTextWriter : System.IO.TextWriter + { + override public System.Text.Encoding Encoding { get { return System.Text.Encoding.ASCII;} } + } + [System.Serializable] + public static class SerializableWriter + { + private static System.IO.TextWriter _writer = null; + public static System.IO.TextWriter Writer {get { return _writer; }} + public static void CreateInternalWriter() + { + _writer = System.IO.TextWriter.Synchronized(new NotSerializableTextWriter()); + } + } + } + ", + PythonCode = @" +import sys + +def before_reload(): + import clr + import System + clr.AddReference('DomainTests') + import TestNamespace + TestNamespace.SerializableWriter.CreateInternalWriter(); + sys.__obj = TestNamespace.SerializableWriter.Writer + sys.__obj.WriteLine('test') + +def after_reload(): + import clr + import System + sys.__obj.WriteLine('test') + + ", + } }; /// @@ -1142,7 +1202,59 @@ import System const string CaseRunnerTemplate = @" using System; using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; using Python.Runtime; + +namespace Serialization +{{ + // Classes in this namespace is mostly useful for test_serialize_unserializable_object + class NotSerializableSerializer : ISerializationSurrogate + {{ + public NotSerializableSerializer() + {{ + }} + public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) + {{ + info.AddValue(""notSerialized_tp"", obj.GetType()); + }} + public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) + {{ + if (info == null) + {{ + return null; + }} + Type typeObj = info.GetValue(""notSerialized_tp"", typeof(Type)) as Type; + if (typeObj == null) + {{ + return null; + }} + + obj = Activator.CreateInstance(typeObj); + return obj; + }} + }} + class NonSerializableSelector : SurrogateSelector + {{ + public override ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector) + {{ + if (type == null) + {{ + throw new ArgumentNullException(); + }} + selector = (ISurrogateSelector)this; + if (type.IsSerializable) + {{ + return null; // use whichever default + }} + else + {{ + return (ISerializationSurrogate)(new NotSerializableSerializer()); + }} + }} + }} +}} + namespace CaseRunner {{ class CaseRunner @@ -1151,6 +1263,11 @@ public static int Main() {{ try {{ + RuntimeData.FormatterFactory = () => + {{ + return new BinaryFormatter(){{SurrogateSelector = new Serialization.NonSerializableSelector()}}; + }}; + PythonEngine.Initialize(); using (Py.GIL()) {{ diff --git a/tests/domain_tests/test_domain_reload.py b/tests/domain_tests/test_domain_reload.py index 8999e481b..1e5e8e81b 100644 --- a/tests/domain_tests/test_domain_reload.py +++ b/tests/domain_tests/test_domain_reload.py @@ -88,3 +88,6 @@ def test_nested_type(): def test_import_after_reload(): _run_test("import_after_reload") + +def test_import_after_reload(): + _run_test("test_serialize_unserializable_object") \ No newline at end of file diff --git a/tests/test_conversion.py b/tests/test_conversion.py index bb686dd52..dd70f900a 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -510,6 +510,9 @@ def test_string_conversion(): ob.StringField = System.String(u'\uffff\uffff') assert ob.StringField == u'\uffff\uffff' + ob.StringField = System.String("\ufeffbom") + assert ob.StringField == "\ufeffbom" + ob.StringField = None assert ob.StringField is None diff --git a/tests/test_enum.py b/tests/test_enum.py index f24f95b36..3d3edba10 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -143,6 +143,16 @@ def test_enum_undefined_value(): Test.FieldTest().EnumField = Test.ShortEnum(20, True) +def test_enum_repr(): + """Test enumeration repr.""" + from System import DayOfWeek + + assert repr(DayOfWeek.Monday) == "" + + assert repr(Test.FlagsEnum(7)) == "" + assert repr(Test.FlagsEnum(8)) == "" + + def test_enum_conversion(): """Test enumeration conversion.""" ob = Test.FieldTest() diff --git a/tests/test_indexer.py b/tests/test_indexer.py index 8cf3150ba..108573f0d 100644 --- a/tests/test_indexer.py +++ b/tests/test_indexer.py @@ -668,3 +668,39 @@ def test_public_inherited_overloaded_indexer(): with pytest.raises(TypeError): ob[[]] + +def test_del_indexer_dict(): + """Test deleting indexers (__delitem__).""" + from System.Collections.Generic import Dictionary, KeyNotFoundException + d = Dictionary[str, str]() + d["delme"] = "1" + with pytest.raises(KeyError): + del d["nonexistent"] + del d["delme"] + with pytest.raises(KeyError): + del d["delme"] + +def test_del_indexer_list(): + """Test deleting indexers (__delitem__).""" + from System import ArgumentOutOfRangeException + from System.Collections.Generic import List + l = List[str]() + l.Add("1") + with pytest.raises(ArgumentOutOfRangeException): + del l[3] + del l[0] + assert len(l) == 0 + +def test_del_indexer_array(): + """Test deleting indexers (__delitem__).""" + from System import Array + l = Array[str](0) + with pytest.raises(TypeError): + del l[0] + +def test_del_indexer_absent(): + """Test deleting indexers (__delitem__).""" + from System import Uri + l = Uri("http://www.example.com") + with pytest.raises(TypeError): + del l[0] diff --git a/tests/test_method.py b/tests/test_method.py index b86bbd6b4..8bba32a3f 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -4,8 +4,29 @@ import System import pytest +import sys +import gc +import tracemalloc from Python.Test import MethodTest +@pytest.fixture(scope="function") +def memory_usage_tracking(): + was_tracing = tracemalloc.is_tracing() + if not was_tracing: + tracemalloc.start() + yield + if not was_tracing: + tracemalloc.stop() + +def _get_total_memory_bytes() -> int: + """Get total memory consumption combining .NET GC memory and Python tracemalloc.""" + dotnet_memory = System.GC.GetTotalMemory(forceFullCollection=False) + # Get Python-side memory + current, _peak = tracemalloc.get_traced_memory() + # Return combined measurement + return dotnet_memory + current + + def test_instance_method_overwritable(): """Test instance method overwriting.""" @@ -110,7 +131,7 @@ def test_overloaded_method_inheritance(): def test_method_descriptor_abuse(): """Test method descriptor abuse.""" - desc = MethodTest.__dict__['PublicMethod'] + desc = MethodTest.__dict__["PublicMethod"] with pytest.raises(TypeError): desc.__get__(0, 0) @@ -122,7 +143,7 @@ def test_method_descriptor_abuse(): def test_method_docstrings(): """Test standard method docstring generation""" method = MethodTest.GetType - value = 'System.Type GetType()' + value = "System.Type GetType()" assert method.__doc__ == value @@ -180,36 +201,36 @@ def test_null_array_conversion(): def test_string_params_args(): """Test use of string params.""" - result = MethodTest.TestStringParamsArg('one', 'two', 'three') + result = MethodTest.TestStringParamsArg("one", "two", "three") assert result.Length == 4 assert len(result) == 4, result - assert result[0] == 'one' - assert result[1] == 'two' - assert result[2] == 'three' + assert result[0] == "one" + assert result[1] == "two" + assert result[2] == "three" # ensures params string[] overload takes precedence over params object[] - assert result[3] == 'tail' + assert result[3] == "tail" - result = MethodTest.TestStringParamsArg(['one', 'two', 'three']) + result = MethodTest.TestStringParamsArg(["one", "two", "three"]) assert len(result) == 4 - assert result[0] == 'one' - assert result[1] == 'two' - assert result[2] == 'three' - assert result[3] == 'tail' + assert result[0] == "one" + assert result[1] == "two" + assert result[2] == "three" + assert result[3] == "tail" def test_object_params_args(): """Test use of object params.""" - result = MethodTest.TestObjectParamsArg('one', 'two', 'three') + result = MethodTest.TestObjectParamsArg("one", "two", "three") assert len(result) == 3, result - assert result[0] == 'one' - assert result[1] == 'two' - assert result[2] == 'three' + assert result[0] == "one" + assert result[1] == "two" + assert result[2] == "three" - result = MethodTest.TestObjectParamsArg(['one', 'two', 'three']) + result = MethodTest.TestObjectParamsArg(["one", "two", "three"]) assert len(result) == 3, result - assert result[0] == 'one' - assert result[1] == 'two' - assert result[2] == 'three' + assert result[0] == "one" + assert result[1] == "two" + assert result[2] == "three" def test_value_params_args(): @@ -233,38 +254,42 @@ def test_non_params_array_in_last_place(): result = MethodTest.TestNonParamsArrayInLastPlace(1, 2, 3) assert result + def test_params_methods_with_no_params(): """Tests that passing no arguments to a params method passes an empty array""" result = MethodTest.TestValueParamsArg() assert len(result) == 0 - result = MethodTest.TestOneArgWithParams('Some String') + result = MethodTest.TestOneArgWithParams("Some String") assert len(result) == 0 - result = MethodTest.TestTwoArgWithParams('Some String', 'Some Other String') + result = MethodTest.TestTwoArgWithParams("Some String", "Some Other String") assert len(result) == 0 + def test_params_methods_with_non_lists(): """Tests that passing single parameters to a params method will convert into an array on the .NET side""" - result = MethodTest.TestOneArgWithParams('Some String', [1, 2, 3, 4]) + result = MethodTest.TestOneArgWithParams("Some String", [1, 2, 3, 4]) assert len(result) == 4 - result = MethodTest.TestOneArgWithParams('Some String', 1, 2, 3, 4) + result = MethodTest.TestOneArgWithParams("Some String", 1, 2, 3, 4) assert len(result) == 4 - result = MethodTest.TestOneArgWithParams('Some String', [5]) + result = MethodTest.TestOneArgWithParams("Some String", [5]) assert len(result) == 1 - result = MethodTest.TestOneArgWithParams('Some String', 5) + result = MethodTest.TestOneArgWithParams("Some String", 5) assert len(result) == 1 + def test_params_method_with_lists(): """Tests passing multiple lists to a params object[] method""" - result = MethodTest.TestObjectParamsArg([],[]) + result = MethodTest.TestObjectParamsArg([], []) assert len(result) == 2 + def test_string_out_params(): """Test use of string out-parameters.""" result = MethodTest.TestStringOutParams("hi", "there") @@ -468,15 +493,13 @@ def test_two_default_param(): def test_explicit_selection_with_out_modifier(): """Check explicit overload selection with out modifiers.""" refstr = System.String("").GetType().MakeByRefType() - result = MethodTest.TestStringOutParams.__overloads__[str, refstr]( - "hi", "there") + result = MethodTest.TestStringOutParams.__overloads__[str, refstr]("hi", "there") assert isinstance(result, tuple) assert len(result) == 2 assert result[0] is True assert result[1] == "output string" - result = MethodTest.TestStringOutParams.__overloads__[str, refstr]( - "hi", None) + result = MethodTest.TestStringOutParams.__overloads__[str, refstr]("hi", None) assert isinstance(result, tuple) assert len(result) == 2 assert result[0] is True @@ -486,15 +509,13 @@ def test_explicit_selection_with_out_modifier(): def test_explicit_selection_with_ref_modifier(): """Check explicit overload selection with ref modifiers.""" refstr = System.String("").GetType().MakeByRefType() - result = MethodTest.TestStringRefParams.__overloads__[str, refstr]( - "hi", "there") + result = MethodTest.TestStringRefParams.__overloads__[str, refstr]("hi", "there") assert isinstance(result, tuple) assert len(result) == 2 assert result[0] is True assert result[1] == "output string" - result = MethodTest.TestStringRefParams.__overloads__[str, refstr]( - "hi", None) + result = MethodTest.TestStringRefParams.__overloads__[str, refstr]("hi", None) assert isinstance(result, tuple) assert len(result) == 2 assert result[0] is True @@ -520,8 +541,8 @@ def test_explicit_overload_selection(): value = MethodTest.Overloaded.__overloads__[System.SByte](127) assert value == 127 - value = MethodTest.Overloaded.__overloads__[System.Char](u'A') - assert value == u'A' + value = MethodTest.Overloaded.__overloads__[System.Char]("A") + assert value == "A" value = MethodTest.Overloaded.__overloads__[System.Char](65535) assert value == chr(65535) @@ -535,37 +556,28 @@ def test_explicit_overload_selection(): value = MethodTest.Overloaded.__overloads__[int](2147483647) assert value == 2147483647 - value = MethodTest.Overloaded.__overloads__[System.Int64]( - 9223372036854775807 - ) + value = MethodTest.Overloaded.__overloads__[System.Int64](9223372036854775807) assert value == 9223372036854775807 value = MethodTest.Overloaded.__overloads__[System.UInt16](65000) assert value == 65000 - value = MethodTest.Overloaded.__overloads__[System.UInt32]( - 4294967295 - ) + value = MethodTest.Overloaded.__overloads__[System.UInt32](4294967295) assert value == 4294967295 - value = MethodTest.Overloaded.__overloads__[System.UInt64]( - 18446744073709551615 - ) + value = MethodTest.Overloaded.__overloads__[System.UInt64](18446744073709551615) assert value == 18446744073709551615 value = MethodTest.Overloaded.__overloads__[System.Single](3.402823e38) assert value == System.Single(3.402823e38) - value = MethodTest.Overloaded.__overloads__[System.Double]( - 1.7976931348623157e308) + value = MethodTest.Overloaded.__overloads__[System.Double](1.7976931348623157e308) assert value == 1.7976931348623157e308 - value = MethodTest.Overloaded.__overloads__[float]( - 1.7976931348623157e308) + value = MethodTest.Overloaded.__overloads__[float](1.7976931348623157e308) assert value == 1.7976931348623157e308 - value = MethodTest.Overloaded.__overloads__[System.Decimal]( - System.Decimal.One) + value = MethodTest.Overloaded.__overloads__[System.Decimal](System.Decimal.One) assert value == System.Decimal.One value = MethodTest.Overloaded.__overloads__[System.String]("spam") @@ -590,7 +602,8 @@ def test_explicit_overload_selection(): atype = Array[System.Object] value = MethodTest.Overloaded.__overloads__[str, int, atype]( - "one", 1, atype([1, 2, 3])) + "one", 1, atype([1, 2, 3]) + ) assert value == 3 value = MethodTest.Overloaded.__overloads__[str, int]("one", 1) @@ -632,10 +645,10 @@ def test_overload_selection_with_array_types(): assert value[1] == 127 vtype = Array[System.Char] - input_ = vtype([u'A', u'Z']) + input_ = vtype(["A", "Z"]) value = MethodTest.Overloaded.__overloads__[vtype](input_) - assert value[0] == u'A' - assert value[1] == u'Z' + assert value[0] == "A" + assert value[1] == "Z" vtype = Array[System.Char] input_ = vtype([0, 65535]) @@ -769,7 +782,7 @@ def test_we_can_bind_to_encoding_get_string(): from System.Text import Encoding from System.IO import MemoryStream - my_bytes = Encoding.UTF8.GetBytes('Some testing string') + my_bytes = Encoding.UTF8.GetBytes("Some testing string") stream = MemoryStream() stream.Write(my_bytes, 0, my_bytes.Length) stream.Position = 0 @@ -784,8 +797,8 @@ def test_we_can_bind_to_encoding_get_string(): temp = Encoding.UTF8.GetString(buff, 0, read) data.append(temp) - data = ''.join(data) - assert data == 'Some testing string' + data = "".join(data) + assert data == "Some testing string" def test_wrong_overload(): @@ -833,6 +846,7 @@ def test_no_object_in_param(): with pytest.raises(TypeError): MethodTest.TestOverloadedNoObject(2147483648) + def test_object_in_param(): """Test regression introduced by #151 in which Object method overloads aren't being used. See #203 for issue.""" @@ -916,9 +930,10 @@ def test_object_in_multiparam_exception(): e = excinfo.value c = e.__cause__ - assert c.GetType().FullName == 'System.AggregateException' + assert c.GetType().FullName == "System.AggregateException" assert len(c.InnerExceptions) == 2 + def test_case_sensitive(): """Test that case-sensitivity is respected. GH#81""" @@ -931,26 +946,27 @@ def test_case_sensitive(): with pytest.raises(AttributeError): MethodTest.casesensitive() + def test_getting_generic_method_binding_does_not_leak_ref_count(): """Test that managed object is freed after calling generic method. Issue #691""" from PlainOldNamespace import PlainOldClass - import sys - refCount = sys.getrefcount(PlainOldClass().GenericMethod[str]) assert refCount == 1 -def test_getting_generic_method_binding_does_not_leak_memory(): + +def test_getting_generic_method_binding_does_not_leak_memory(memory_usage_tracking): """Test that managed object is freed after calling generic method. Issue #691""" from PlainOldNamespace import PlainOldClass - import psutil, os, gc, clr - - process = psutil.Process(os.getpid()) - processBytesBeforeCall = process.memory_info().rss - print("\n\nMemory consumption (bytes) at start of test: " + str(processBytesBeforeCall)) + tracemalloc.start() + processBytesBeforeCall = _get_total_memory_bytes() + print( + "\n\nMemory consumption (bytes) at start of test: " + + str(processBytesBeforeCall) + ) iterations = 500 for i in range(iterations): @@ -959,7 +975,7 @@ def test_getting_generic_method_binding_does_not_leak_memory(): gc.collect() System.GC.Collect() - processBytesAfterCall = process.memory_info().rss + processBytesAfterCall = _get_total_memory_bytes() print("Memory consumption (bytes) at end of test: " + str(processBytesAfterCall)) processBytesDelta = processBytesAfterCall - processBytesBeforeCall print("Memory delta: " + str(processBytesDelta)) @@ -967,31 +983,32 @@ def test_getting_generic_method_binding_does_not_leak_memory(): bytesAllocatedPerIteration = pow(2, 20) # 1MB bytesLeakedPerIteration = processBytesDelta / iterations - # Allow 50% threshold - this shows the original issue is fixed, which leaks the full allocated bytes per iteration - failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration / 2 + # Allow 90% threshold - this shows the original issue is fixed, which leaks the full allocated bytes per iteration + # Increased from 50% to ensure that it works on Windows with Python >3.13 + failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration * 0.9 assert bytesLeakedPerIteration < failThresholdBytesLeakedPerIteration + def test_getting_overloaded_method_binding_does_not_leak_ref_count(): """Test that managed object is freed after calling overloaded method. Issue #691""" from PlainOldNamespace import PlainOldClass - import sys - refCount = sys.getrefcount(PlainOldClass().OverloadedMethod.Overloads[int]) assert refCount == 1 -def test_getting_overloaded_method_binding_does_not_leak_memory(): + +def test_getting_overloaded_method_binding_does_not_leak_memory(memory_usage_tracking): """Test that managed object is freed after calling overloaded method. Issue #691""" from PlainOldNamespace import PlainOldClass - import psutil, os, gc, clr - - process = psutil.Process(os.getpid()) - processBytesBeforeCall = process.memory_info().rss - print("\n\nMemory consumption (bytes) at start of test: " + str(processBytesBeforeCall)) + processBytesBeforeCall = _get_total_memory_bytes() + print( + "\n\nMemory consumption (bytes) at start of test: " + + str(processBytesBeforeCall) + ) iterations = 500 for i in range(iterations): @@ -1000,7 +1017,7 @@ def test_getting_overloaded_method_binding_does_not_leak_memory(): gc.collect() System.GC.Collect() - processBytesAfterCall = process.memory_info().rss + processBytesAfterCall = _get_total_memory_bytes() print("Memory consumption (bytes) at end of test: " + str(processBytesAfterCall)) processBytesDelta = processBytesAfterCall - processBytesBeforeCall print("Memory delta: " + str(processBytesDelta)) @@ -1013,6 +1030,7 @@ def test_getting_overloaded_method_binding_does_not_leak_memory(): assert bytesLeakedPerIteration < failThresholdBytesLeakedPerIteration + def test_getting_method_overloads_binding_does_not_leak_ref_count(): """Test that managed object is freed after calling overloaded method. Issue #691""" @@ -1023,16 +1041,17 @@ def test_getting_method_overloads_binding_does_not_leak_ref_count(): refCount = sys.getrefcount(PlainOldClass().OverloadedMethod.Overloads) assert refCount == 1 -def test_getting_method_overloads_binding_does_not_leak_memory(): + +def test_getting_method_overloads_binding_does_not_leak_memory(memory_usage_tracking): """Test that managed object is freed after calling overloaded method. Issue #691""" from PlainOldNamespace import PlainOldClass - import psutil, os, gc, clr - - process = psutil.Process(os.getpid()) - processBytesBeforeCall = process.memory_info().rss - print("\n\nMemory consumption (bytes) at start of test: " + str(processBytesBeforeCall)) + processBytesBeforeCall = _get_total_memory_bytes() + print( + "\n\nMemory consumption (bytes) at start of test: " + + str(processBytesBeforeCall) + ) iterations = 500 for i in range(iterations): @@ -1041,7 +1060,7 @@ def test_getting_method_overloads_binding_does_not_leak_memory(): gc.collect() System.GC.Collect() - processBytesAfterCall = process.memory_info().rss + processBytesAfterCall = _get_total_memory_bytes() print("Memory consumption (bytes) at end of test: " + str(processBytesAfterCall)) processBytesDelta = processBytesAfterCall - processBytesBeforeCall print("Memory delta: " + str(processBytesDelta)) @@ -1049,18 +1068,17 @@ def test_getting_method_overloads_binding_does_not_leak_memory(): bytesAllocatedPerIteration = pow(2, 20) # 1MB bytesLeakedPerIteration = processBytesDelta / iterations - # Allow 50% threshold - this shows the original issue is fixed, which leaks the full allocated bytes per iteration - failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration / 2 + # Allow 90% threshold - this shows the original issue is fixed, which leaks the full allocated bytes per iteration + failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration * 0.9 assert bytesLeakedPerIteration < failThresholdBytesLeakedPerIteration + def test_getting_overloaded_constructor_binding_does_not_leak_ref_count(): """Test that managed object is freed after calling overloaded constructor, constructorbinding.cs mp_subscript. Issue #691""" from PlainOldNamespace import PlainOldClass - import sys - # simple test refCount = sys.getrefcount(PlainOldClass.Overloads[int]) assert refCount == 1 @@ -1068,7 +1086,7 @@ def test_getting_overloaded_constructor_binding_does_not_leak_ref_count(): def test_default_params(): # all positional parameters - res = MethodTest.DefaultParams(1,2,3,4) + res = MethodTest.DefaultParams(1, 2, 3, 4) assert res == "1234" res = MethodTest.DefaultParams(1, 2, 3) @@ -1099,7 +1117,8 @@ def test_default_params(): assert res == "1037" with pytest.raises(TypeError): - MethodTest.DefaultParams(1,2,3,4,5) + MethodTest.DefaultParams(1, 2, 3, 4, 5) + def test_optional_params(): res = MethodTest.OptionalParams(1, 2, 3, 4) @@ -1138,10 +1157,10 @@ def test_optional_params(): res = MethodTest.OptionalParams_TestMissing(None) assert res == False - res = MethodTest.OptionalParams_TestMissing(a = None) + res = MethodTest.OptionalParams_TestMissing(a=None) assert res == False - res = MethodTest.OptionalParams_TestMissing(a='hi') + res = MethodTest.OptionalParams_TestMissing(a="hi") assert res == False res = MethodTest.OptionalParams_TestReferenceType() @@ -1153,12 +1172,13 @@ def test_optional_params(): res = MethodTest.OptionalParams_TestReferenceType(a=None) assert res == True - res = MethodTest.OptionalParams_TestReferenceType('hi') + res = MethodTest.OptionalParams_TestReferenceType("hi") assert res == False - res = MethodTest.OptionalParams_TestReferenceType(a='hi') + res = MethodTest.OptionalParams_TestReferenceType(a="hi") assert res == False + def test_optional_and_default_params(): res = MethodTest.OptionalAndDefaultParams() @@ -1176,12 +1196,13 @@ def test_optional_and_default_params(): res = MethodTest.OptionalAndDefaultParams2() assert res == "0012" - res = MethodTest.OptionalAndDefaultParams2(a=1,b=2,c=3,d=4) + res = MethodTest.OptionalAndDefaultParams2(a=1, b=2, c=3, d=4) assert res == "1234" res = MethodTest.OptionalAndDefaultParams2(b=2, c=3) assert res == "0232" + def test_default_params_overloads(): res = MethodTest.DefaultParamsWithOverloading(1, 2) assert res == "12" @@ -1207,16 +1228,19 @@ def test_default_params_overloads(): res = MethodTest.DefaultParamsWithOverloading(1, d=1) assert res == "1671XXX" + def test_default_params_overloads_ambiguous_call(): with pytest.raises(TypeError): MethodTest.DefaultParamsWithOverloading() + def test_keyword_arg_method_resolution(): from Python.Test import MethodArityTest ob = MethodArityTest() assert ob.Foo(1, b=2) == "Arity 2" + def test_params_array_overload(): res = MethodTest.ParamsArrayOverloaded() assert res == "without params-array" @@ -1242,6 +1266,7 @@ def test_params_array_overload(): res = MethodTest.ParamsArrayOverloaded(1, 2, 3, i=1) assert res == "with params-array" + @pytest.mark.skip(reason="FIXME: incorrectly failing") def test_params_array_overloaded_failing(): res = MethodTest.ParamsArrayOverloaded(1, 2, i=1) @@ -1250,13 +1275,16 @@ def test_params_array_overloaded_failing(): res = MethodTest.ParamsArrayOverloaded(paramsArray=[], i=1) assert res == "with params-array" + def test_method_encoding(): MethodTest.EncodingTestÅngström() + def test_method_with_pointer_array_argument(): with pytest.raises(TypeError): MethodTest.PointerArray([0]) + def test_method_call_implicit_conversion(): class IntAnswerMixin: diff --git a/tests/test_module.py b/tests/test_module.py index ddfa7bb36..0c20dcfc0 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -344,6 +344,20 @@ def test_clr_add_reference(): with pytest.raises(FileNotFoundException): AddReference("somethingtotallysilly") + +def test_clr_add_reference_bad_path(): + import sys + from clr import AddReference + from System.IO import FileNotFoundException + bad_path = "hello\0world" + sys.path.append(bad_path) + try: + with pytest.raises(FileNotFoundException): + AddReference("test_clr_add_reference_bad_path") + finally: + sys.path.remove(bad_path) + + def test_clr_get_clr_type(): """Test clr.GetClrType().""" from clr import GetClrType diff --git a/tests/test_mp_length.py b/tests/test_mp_length.py index e86fff288..8b6e56b7c 100644 --- a/tests/test_mp_length.py +++ b/tests/test_mp_length.py @@ -1,63 +1,63 @@ -# -*- coding: utf-8 -*- - -"""Test __len__ for .NET classes implementing ICollection/ICollection.""" - -import System -import pytest -from Python.Test import MpLengthCollectionTest, MpLengthExplicitCollectionTest, MpLengthGenericCollectionTest, MpLengthExplicitGenericCollectionTest - -def test_simple___len__(): - """Test __len__ for simple ICollection implementers""" - import System - import System.Collections.Generic - l = System.Collections.Generic.List[int]() - assert len(l) == 0 - l.Add(5) - l.Add(6) - assert len(l) == 2 - - d = System.Collections.Generic.Dictionary[int, int]() - assert len(d) == 0 - d.Add(4, 5) - assert len(d) == 1 - - a = System.Array[int]([0,1,2,3]) - assert len(a) == 4 - -def test_custom_collection___len__(): - """Test __len__ for custom collection class""" - s = MpLengthCollectionTest() - assert len(s) == 3 - -def test_custom_collection_explicit___len__(): - """Test __len__ for custom collection class that explicitly implements ICollection""" - s = MpLengthExplicitCollectionTest() - assert len(s) == 2 - -def test_custom_generic_collection___len__(): - """Test __len__ for custom generic collection class""" - s = MpLengthGenericCollectionTest[int]() - s.Add(1) - s.Add(2) - assert len(s) == 2 - -def test_custom_generic_collection_explicit___len__(): - """Test __len__ for custom generic collection that explicity implements ICollection""" - s = MpLengthExplicitGenericCollectionTest[int]() - s.Add(1) - s.Add(10) - assert len(s) == 2 - -def test_len_through_interface_generic(): - """Test __len__ for ICollection""" - import System.Collections.Generic - l = System.Collections.Generic.List[int]() - coll = System.Collections.Generic.ICollection[int](l) - assert len(coll) == 0 - -def test_len_through_interface(): - """Test __len__ for ICollection""" - import System.Collections - l = System.Collections.ArrayList() - coll = System.Collections.ICollection(l) - assert len(coll) == 0 +# -*- coding: utf-8 -*- + +"""Test __len__ for .NET classes implementing ICollection/ICollection.""" + +import System +import pytest +from Python.Test import MpLengthCollectionTest, MpLengthExplicitCollectionTest, MpLengthGenericCollectionTest, MpLengthExplicitGenericCollectionTest + +def test_simple___len__(): + """Test __len__ for simple ICollection implementers""" + import System + import System.Collections.Generic + l = System.Collections.Generic.List[int]() + assert len(l) == 0 + l.Add(5) + l.Add(6) + assert len(l) == 2 + + d = System.Collections.Generic.Dictionary[int, int]() + assert len(d) == 0 + d.Add(4, 5) + assert len(d) == 1 + + a = System.Array[int]([0,1,2,3]) + assert len(a) == 4 + +def test_custom_collection___len__(): + """Test __len__ for custom collection class""" + s = MpLengthCollectionTest() + assert len(s) == 3 + +def test_custom_collection_explicit___len__(): + """Test __len__ for custom collection class that explicitly implements ICollection""" + s = MpLengthExplicitCollectionTest() + assert len(s) == 2 + +def test_custom_generic_collection___len__(): + """Test __len__ for custom generic collection class""" + s = MpLengthGenericCollectionTest[int]() + s.Add(1) + s.Add(2) + assert len(s) == 2 + +def test_custom_generic_collection_explicit___len__(): + """Test __len__ for custom generic collection that explicity implements ICollection""" + s = MpLengthExplicitGenericCollectionTest[int]() + s.Add(1) + s.Add(10) + assert len(s) == 2 + +def test_len_through_interface_generic(): + """Test __len__ for ICollection""" + import System.Collections.Generic + l = System.Collections.Generic.List[int]() + coll = System.Collections.Generic.ICollection[int](l) + assert len(coll) == 0 + +def test_len_through_interface(): + """Test __len__ for ICollection""" + import System.Collections + l = System.Collections.ArrayList() + coll = System.Collections.ICollection(l) + assert len(coll) == 0 diff --git a/tests/test_subclass.py b/tests/test_subclass.py index fa82c3663..c6ab7650f 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -9,7 +9,7 @@ import System import pytest from Python.Test import (IInterfaceTest, SubClassTest, EventArgsTest, - FunctionsTest) + FunctionsTest, IGenericInterface, GenericVirtualMethodTest, SimpleClass, ISayHello1) from System.Collections.Generic import List @@ -29,6 +29,17 @@ def bar(self, x, i): return InterfaceTestClass +def interface_generic_class_fixture(subnamespace): + + class GenericInterfaceImpl(IGenericInterface[int]): + __namespace__ = "Python.Test." + subnamespace + + def Get(self, x): + return x + + return GenericInterfaceImpl + + def derived_class_fixture(subnamespace): """Delay creation of class until test starts.""" @@ -306,3 +317,30 @@ class Derived(BaseClass): import gc gc.collect() + +def test_generic_interface(): + from System import Int32 + from Python.Test import GenericInterfaceUser, SpecificInterfaceUser + + GenericInterfaceImpl = interface_generic_class_fixture(test_generic_interface.__name__) + + obj = GenericInterfaceImpl() + SpecificInterfaceUser(obj, Int32(0)) + GenericInterfaceUser[Int32](obj, Int32(0)) + +def test_virtual_generic_method(): + class OverloadingSubclass(GenericVirtualMethodTest): + __namespace__ = "test_virtual_generic_method_cls" + class OverloadingSubclass2(OverloadingSubclass): + __namespace__ = "test_virtual_generic_method_cls" + obj = OverloadingSubclass() + assert obj.VirtMethod[int](5) == 5 + obj = OverloadingSubclass2() + assert obj.VirtMethod[int](5) == 5 + +def test_implement_interface_and_class(): + class DualSubClass0(ISayHello1, SimpleClass): + __namespace__ = "Test" + def SayHello(self): + return "hello" + obj = DualSubClass0() diff --git a/tools/geninterop/geninterop.py b/tools/geninterop/geninterop.py old mode 100644 new mode 100755 index 0c80c1904..89186737f --- a/tools/geninterop/geninterop.py +++ b/tools/geninterop/geninterop.py @@ -1,6 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - +#!/usr/bin/env uv run +# /// script +# dependencies = ["pycparser"] +# /// """ TypeOffset is a C# class that mirrors the in-memory layout of heap allocated Python objects. @@ -13,40 +14,26 @@ - clang """ -from __future__ import print_function - -import logging import os +import shutil import sys import sysconfig import subprocess -if sys.version_info.major > 2: - from io import StringIO -else: - from StringIO import StringIO - +from io import StringIO +from pathlib import Path from pycparser import c_ast, c_parser -_log = logging.getLogger() -logging.basicConfig(level=logging.DEBUG) - -PY_MAJOR = sys.version_info[0] -PY_MINOR = sys.version_info[1] - # rename some members from their C name when generating the C# _typeoffset_member_renames = { "ht_name": "name", - "ht_qualname": "qualname" + "ht_qualname": "qualname", + "getitem": "spec_cache_getitem", } def _check_output(*args, **kwargs): - """Check output wrapper for py2/py3 compatibility""" - output = subprocess.check_output(*args, **kwargs) - if PY_MAJOR == 2: - return output - return output.decode("ascii") + return subprocess.check_output(*args, **kwargs, encoding="utf8") class AstParser(object): @@ -90,9 +77,11 @@ def visit(self, node): self.visit_ptrdecl(node) elif isinstance(node, c_ast.IdentifierType): self.visit_identifier(node) + elif isinstance(node, c_ast.Union): + self.visit_union(node) def visit_ast(self, ast): - for name, node in ast.children(): + for _name, node in ast.children(): self.visit(node) def visit_typedef(self, typedef): @@ -113,7 +102,7 @@ def visit_struct(self, struct): self.visit(decl) self._struct_members_stack.pop(0) self._struct_stack.pop(0) - elif self._ptr_decl_depth: + elif self._ptr_decl_depth or self._struct_members_stack: # the struct is empty, but add it as a member to the current # struct as the current member maybe a pointer to it. self._add_struct_member(struct.name) @@ -133,6 +122,16 @@ def visit_identifier(self, identifier): type_name = " ".join(identifier.names) self._add_struct_member(type_name) + def visit_union(self, union): + # Treat the field as if it was just the first declaration for now. This + # is not really correct, but handles the one case that is relevant for + # us right now (ob_refcnt being "split" in Python 3.12) + if self._struct_members_stack and union.decls: + decl = union.decls[0] + self._struct_members_stack.pop(0) + self._struct_members_stack.insert(0, decl.name) + self.visit(decl) + def _add_struct_member(self, type_name): if not (self._struct_stack and self._struct_members_stack): return @@ -141,7 +140,8 @@ def _add_struct_member(self, type_name): current_struct = self._struct_stack[0] member_name = self._struct_members_stack[0] struct_members = self._struct_members.setdefault( - self._get_struct_name(current_struct), []) + self._get_struct_name(current_struct), [] + ) # get the node associated with this type node = None @@ -179,7 +179,6 @@ def _get_struct_name(self, node): class Writer(object): - def __init__(self): self._stream = StringIO() @@ -193,34 +192,47 @@ def to_string(self): return self._stream.getvalue() -def preprocess_python_headers(): +def preprocess_python_headers(*, cc=None, include_py=None): """Return Python.h pre-processed, ready for parsing. Requires clang. """ - fake_libc_include = os.path.join(os.path.dirname(__file__), - "fake_libc_include") + this_path = Path(__file__).parent + + fake_libc_include = this_path / "fake_libc_include" include_dirs = [fake_libc_include] - include_py = sysconfig.get_config_var("INCLUDEPY") + if cc is None: + cc = shutil.which("clang") + if cc is None: + cc = shutil.which("gcc") + if cc is None: + raise RuntimeError("No suitable C compiler found, need clang or gcc") + + if include_py is None: + include_py = sysconfig.get_config_var("INCLUDEPY") + include_py = Path(include_py) + include_dirs.append(include_py) - include_args = [c for p in include_dirs for c in ["-I", p]] + include_args = [c for p in include_dirs for c in ["-I", str(p)]] + # fmt: off defines = [ "-D", "__attribute__(x)=", "-D", "__inline__=inline", "-D", "__asm__=;#pragma asm", "-D", "__int64=long long", - "-D", "_POSIX_THREADS" + "-D", "_POSIX_THREADS", ] - if os.name == 'nt': + if sys.platform == "win32": defines.extend([ "-D", "__inline=inline", "-D", "__ptr32=", "-D", "__ptr64=", "-D", "__declspec(x)=", ]) + #fmt: on if hasattr(sys, "abiflags"): if "d" in sys.abiflags: @@ -228,8 +240,8 @@ def preprocess_python_headers(): if "u" in sys.abiflags: defines.extend(("-D", "PYTHON_WITH_WIDE_UNICODE")) - python_h = os.path.join(include_py, "Python.h") - cmd = ["clang", "-pthread"] + include_args + defines + ["-E", python_h] + python_h = include_py / "Python.h" + cmd = [cc, "-pthread"] + include_args + defines + ["-E", str(python_h)] # normalize as the parser doesn't like windows line endings. lines = [] @@ -240,16 +252,13 @@ def preprocess_python_headers(): return "\n".join(lines) - -def gen_interop_head(writer): +def gen_interop_head(writer, version, abi_flags): filename = os.path.basename(__file__) - abi_flags = getattr(sys, "abiflags", "").replace("m", "") - py_ver = "{0}.{1}".format(PY_MAJOR, PY_MINOR) - class_definition = """ -// Auto-generated by %s. + class_definition = f""" +// Auto-generated by {filename}. // DO NOT MODIFY BY HAND. -// Python %s: ABI flags: '%s' +// Python {".".join(map(str, version[:2]))}: ABI flags: '{abi_flags}' // ReSharper disable InconsistentNaming // ReSharper disable IdentifierTypo @@ -261,7 +270,7 @@ def gen_interop_head(writer): using Python.Runtime.Native; namespace Python.Runtime -{""" % (filename, py_ver, abi_flags) +{{""" writer.extend(class_definition) @@ -271,25 +280,24 @@ def gen_interop_tail(writer): writer.extend(tail) -def gen_heap_type_members(parser, writer, type_name = None): +def gen_heap_type_members(parser, writer, type_name): """Generate the TypeOffset C# class""" members = parser.get_struct_members("PyHeapTypeObject") - type_name = type_name or "TypeOffset{0}{1}".format(PY_MAJOR, PY_MINOR) - class_definition = """ + class_definition = f""" [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Following CPython", Scope = "type")] [StructLayout(LayoutKind.Sequential)] - internal class {0} : GeneratedTypeOffsets, ITypeOffsets + internal class {type_name} : GeneratedTypeOffsets, ITypeOffsets {{ - public {0}() {{ }} + public {type_name}() {{ }} // Auto-generated from PyHeapTypeObject in Python.h -""".format(type_name) +""" # All the members are sizeof(void*) so we don't need to do any # extra work to determine the size based on the type. - for name, tpy in members: + for name, _type in members: name = _typeoffset_member_renames.get(name, name) class_definition += " public int %s { get; private set; }\n" % name @@ -304,17 +312,18 @@ def gen_structure_code(parser, writer, type_name, indent): return False out = writer.append out(indent, "[StructLayout(LayoutKind.Sequential)]") - out(indent, "internal struct %s" % type_name) + out(indent, f"internal struct {type_name}") out(indent, "{") - for name, tpy in members: - out(indent + 1, "public IntPtr %s;" % name) + for name, _type in members: + out(indent + 1, f"public IntPtr {name};") out(indent, "}") out() return True -def main(): + +def main(*, cc=None, include_py=None, version=None, out=None): # preprocess Python.h and build the AST - python_h = preprocess_python_headers() + python_h = preprocess_python_headers(cc=cc, include_py=include_py) parser = c_parser.CParser() ast = parser.parse(python_h) @@ -323,21 +332,47 @@ def main(): ast_parser.visit(ast) writer = Writer() + + if include_py and not version: + raise RuntimeError("If the include path is overridden, version must be " + "defined" + ) + + if version: + version = version.split('.') + else: + version = sys.version_info + # generate the C# code - offsets_type_name = "NativeTypeOffset" if len(sys.argv) > 1 else None - gen_interop_head(writer) + abi_flags = getattr(sys, "abiflags", "").replace("m", "") + gen_interop_head(writer, version, abi_flags) - gen_heap_type_members(ast_parser, writer, type_name = offsets_type_name) + type_name = f"TypeOffset{version[0]}{version[1]}{abi_flags}" + gen_heap_type_members(ast_parser, writer, type_name) gen_interop_tail(writer) interop_cs = writer.to_string() - if len(sys.argv) > 1: - with open(sys.argv[1], "w") as fh: - fh.write(interop_cs) - else: + if not out or out == "-": print(interop_cs) + else: + with open(out, "w") as fh: + fh.write(interop_cs) if __name__ == "__main__": - sys.exit(main()) + import argparse + + a = argparse.ArgumentParser("Interop file generator for Python.NET") + a.add_argument("--cc", help="C compiler to use, either clang or gcc") + a.add_argument("--include-py", help="Include path of Python") + a.add_argument("--version", help="Python version") + a.add_argument("--out", help="Output path", default="-") + args = a.parse_args() + + sys.exit(main( + cc=args.cc, + include_py=args.include_py, + out=args.out, + version=args.version + )) diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000..668679574 --- /dev/null +++ b/uv.lock @@ -0,0 +1,430 @@ +version = 1 +revision = 3 +requires-python = ">=3.10, <3.15" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version < '3.11'", +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "clr-loader" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/46/7eea92b6aa2d68af78e049cbecec5f757f1aad44ecdecdc16bbad7eead51/clr_loader-0.3.1.tar.gz", hash = "sha256:2e073e9aaf49d1ae2f56ecba27987ad5fb68be4bcd9dd34a5bed8f0e4e128366", size = 86805, upload-time = "2026-04-18T17:49:44.287Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/da/ec1a6e36624000b6df0dd61183c42342ee5814c073315e802cadaad04d2f/clr_loader-0.3.1-py3-none-any.whl", hash = "sha256:cbad189de20d202a7d621956b0fc38049e13c9bf7ca2923441eff725cd121aa1", size = 55730, upload-time = "2026-04-18T17:49:42.99Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "find-libpython" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/60/951b7ca316ab3ec928ed788de5fcb30b4a0292704e50b872c8edf24c11fe/find_libpython-0.5.1.tar.gz", hash = "sha256:12a0fb39ff8dcc64ad0fd554b1bd142ea4a8c4c18e5da6043a547ce7b25559fe", size = 9402, upload-time = "2026-02-11T03:18:04.844Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/1f/1d6079f4f0540aaa368aa20d89d98eda42f081c397a822c547340e32d1e3/find_libpython-0.5.1-py3-none-any.whl", hash = "sha256:723a8cfe6fed255a1f58b53c62ed556fb340ec0d456e9863ebc01a5cc047607d", size = 9201, upload-time = "2026-02-11T03:18:03.263Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" }, + { url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" }, + { url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" }, + { url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" }, + { url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" }, + { url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" }, + { url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" }, + { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, + { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, + { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, + { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, + { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, + { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, + { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, + { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" }, + { url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" }, +] + +[[package]] +name = "packaging" +version = "26.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "pythonnet" +source = { editable = "." } +dependencies = [ + { name = "clr-loader" }, +] + +[package.dev-dependencies] +dev = [ + { name = "find-libpython" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [{ name = "clr-loader", specifier = ">=0.3.1,<0.4.0" }] + +[package.metadata.requires-dev] +dev = [ + { name = "find-libpython", specifier = ">=0.3" }, + { name = "numpy", marker = "python_full_version < '3.10'", specifier = "<2" }, + { name = "numpy", marker = "python_full_version >= '3.10'", specifier = ">=2" }, + { name = "pytest", specifier = ">=6" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] diff --git a/version.txt b/version.txt index 0f9d6b15d..a416f3693 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.0-dev +3.1.0-rc.0