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 eef0e666d..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: |
- pip3.8 install -r requirements.txt
- pip3.8 install pytest numpy # for tests
-
- - name: Build and Install
- run: |
- pip3.8 install -v .
-
- - name: Set Python DLL path (non Windows)
- run: |
- echo PYTHONNET_PYDLL=$(python3.8 -m find_libpython) >> $GITHUB_ENV
-
- - name: Embedding tests
- run: dotnet test --logger "console;verbosity=detailed" src/embed_tests/
-
- - name: Python Tests (Mono)
- run: python3.8 -m pytest --runtime mono
-
- - name: Python Tests (.NET Core)
- run: python3.8 -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 30163cd14..e892009f2 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -6,7 +6,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Doxygen Action
uses: mattnotmitt/doxygen-action@1.12.0
with:
@@ -19,7 +19,7 @@ jobs:
- name: Upload artifact
# Automatically uploads an artifact from the './_site' directory by default
- uses: actions/upload-pages-artifact@v3
+ 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@v4
+ uses: actions/deploy-pages@v5
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 8485189e1..8ead06fba 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -19,98 +19,106 @@ jobs:
- category: windows
platform: x86
instance: windows-latest
+ suffix: -windows-x86-none
- 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-13
+ instance: macos-15
+ suffix: -macos-x86_64-none
+
+ - category: macos
+ platform: arm64
+ instance: macos-15
+ suffix: -macos-aarch64-none
- python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
+ python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
- # This fails in pytest with:
- # CSC : error CS4023: /platform:anycpu32bitpreferred can only be used with /t:exe, /t:winexe and /t:appcontainerexe [D:\a\pythonnet\pythonnet\src\runtime\Python.Runtime.csproj]
exclude:
- - os:
+ # Fails with initfs_encoding error
+ - os:
category: windows
platform: x86
- python: "3.13"
+ python: "3.10"
- steps:
- - name: Set Environment on macOS
- uses: maxim-lobanov/setup-xamarin@v1
- if: ${{ matrix.os.category == 'macos' }}
- with:
- mono-version: latest
+ # 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'
-
- - name: Set up Python ${{ matrix.python }}
- uses: actions/setup-python@v2
- with:
- python-version: ${{ matrix.python }}
+ dotnet-version: '10.0'
architecture: ${{ matrix.os.platform }}
- - name: Install dependencies
- run: |
- pip install --upgrade -r requirements.txt
- pip install numpy # for tests
+ - run: dotnet restore
- - name: Build and Install
- run: |
- pip install -v .
+ - name: Install Mono
+ uses: pythonnet/clr-loader/.github/actions/install-mono@main
+ with:
+ arch: ${{ matrix.os.platform }}
- - name: Set Python DLL path and PYTHONHOME (non Windows)
- if: ${{ matrix.os.category != 'windows' }}
- run: |
- echo PYTHONNET_PYDLL=$(python -m find_libpython) >> $GITHUB_ENV
- echo PYTHONHOME=$(python -c 'import sys; print(sys.prefix)') >> $GITHUB_ENV
+ - name: Set up Python ${{ matrix.python }}
+ uses: astral-sh/setup-uv@v7
+ with:
+ python-version: cpython-${{ matrix.python }}${{ matrix.os.suffix }}
+ cache-python: true
+ activate-environment: true
+ enable-cache: true
- - name: Set Python DLL path and PYTHONHOME (Windows)
- if: ${{ matrix.os.category == 'windows' }}
- run: |
- Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append -InputObject "PYTHONNET_PYDLL=$(python -m find_libpython)"
- Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append -InputObject "PYTHONHOME=$(python -c 'import sys; print(sys.prefix)')"
+ - name: Synchronize the virtual environment
+ run: uv sync --managed-python
- - name: Embedding tests
- if: ${{ matrix.python != '3.13' }}
- run: dotnet test --runtime any-${{ matrix.os.platform }} --logger "console;verbosity=detailed" src/embed_tests/
+ - 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.category != '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.os.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.category == 'windows' }}
run: pytest --runtime netfx
- name: Python tests run from .NET
- if: ${{ matrix.python != '3.13' }}
run: dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/
-
- - name: Perf tests
- if: ${{ (matrix.python == '3.8') && (matrix.os.platform == 'x64') }}
- run: |
- pip install --force --no-deps --target src/perf_tests/baseline/ pythonnet==2.5.2
- dotnet test --configuration Release --runtime any-${{ matrix.os.platform }} --logger "console;verbosity=detailed" src/perf_tests/
-
- # TODO: Run mono tests on Windows?
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/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/pyproject.toml b/pyproject.toml
index 968998e8d..be33a6afb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,33 +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.7,<0.3.0"
+ "clr_loader>=0.3.1,<0.4.0"
]
-requires-python = ">=3.7, <3.14"
+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",
@@ -38,10 +35,9 @@ dynamic = ["version"]
[dependency-groups]
dev = [
"pytest >= 6",
- "find_libpython >= 0.3.0",
+ "find_libpython >= 0.3",
"numpy >=2 ; python_version >= '3.10'",
"numpy <2 ; python_version < '3.10'",
- "psutil"
]
[[project.authors]]
@@ -55,7 +51,6 @@ Sources = "https://github.com/pythonnet/pythonnet"
[tool.setuptools]
zip-safe = false
py-modules = ["clr"]
-license-files = []
[tool.setuptools.dynamic.version]
file = "version.txt"
diff --git a/pythonnet.sln b/pythonnet.sln
index cf684c0e1..9dfeb44b1 100644
--- a/pythonnet.sln
+++ b/pythonnet.sln
@@ -8,8 +8,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.EmbeddingTest", "src
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}"
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 33f38cca7..000000000
--- a/requirements.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-# Requirements for both Travis and AppVeyor
-pytest
-psutil
-
-# Coverage upload
-coverage
-codecov
-
-wheel
-pycparser
-clr-loader==0.2.*
-
-# Discover libpython
-find_libpython==0.3.*
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 c8b8ecb6e..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 = @"
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
index c216f4214..94a30726b 100644
--- a/src/embed_tests/Events.cs
+++ b/src/embed_tests/Events.cs
@@ -10,18 +10,6 @@ namespace Python.EmbeddingTest;
public class Events
{
- [OneTimeSetUp]
- public void SetUp()
- {
- PythonEngine.Initialize();
- }
-
- [OneTimeTearDown]
- public void Dispose()
- {
- PythonEngine.Shutdown();
- }
-
[Test]
public void UsingDoesNotLeak()
{
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 6cab4dd07..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,7 +38,7 @@ 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));
}
}
@@ -219,10 +207,10 @@ public void TestCreateModuleWithFilename()
using var modWithName = PyModule.FromString("mod_with_name", "", "some_filename");
- Assert.AreEqual("none", mod.Get("__file__"));
- Assert.AreEqual("none", modWithoutName.Get("__file__"));
- Assert.AreEqual("none", modNullName.Get("__file__"));
- Assert.AreEqual("some_filename", modWithName.Get("__file__"));
+ 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"));
}
///
@@ -235,17 +223,17 @@ 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);
}
}
@@ -266,10 +254,10 @@ 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);
}
}
@@ -289,10 +277,10 @@ 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);
}
}
@@ -345,22 +333,22 @@ public void TestVariables()
{
(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));
}
}
@@ -420,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
@@ -434,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");
@@ -448,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 a59b9c97b..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
diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs
index 0b28c3a35..13855adef 100644
--- a/src/runtime/PythonEngine.cs
+++ b/src/runtime/PythonEngine.cs
@@ -135,7 +135,7 @@ public static string PythonPath
}
public static Version MinSupportedVersion => new(3, 7);
- public static Version MaxSupportedVersion => new(3, 13, 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/Runtime.Delegates.cs b/src/runtime/Runtime.Delegates.cs
index 262dc1e19..dc4a4b0a9 100644
--- a/src/runtime/Runtime.Delegates.cs
+++ b/src/runtime/Runtime.Delegates.cs
@@ -308,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);
}
}
diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs
index c8f022860..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();
diff --git a/src/runtime/StateSerialization/RuntimeData.cs b/src/runtime/StateSerialization/RuntimeData.cs
index 8eda9ce0b..61e377aa4 100644
--- a/src/runtime/StateSerialization/RuntimeData.cs
+++ b/src/runtime/StateSerialization/RuntimeData.cs
@@ -20,7 +20,9 @@ public static class RuntimeData
{
try
{
- return new BinaryFormatter();
+ var res = new BinaryFormatter();
+ res.Serialize(new MemoryStream(), 1); // test if BinaryFormatter is usable
+ return res;
}
catch
{
diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs
index 559d5148e..dbff1fbd4 100644
--- a/src/runtime/TypeManager.cs
+++ b/src/runtime/TypeManager.cs
@@ -618,6 +618,11 @@ internal static PyType AllocateTypeObject(string name, PyType metatype)
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/ClassBase.cs b/src/runtime/Types/ClassBase.cs
index 2d6ce8a47..3fcb7ca4f 100644
--- a/src/runtime/Types/ClassBase.cs
+++ b/src/runtime/Types/ClassBase.cs
@@ -374,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);
@@ -385,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)
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 57fcaa232..ecbc9cac5 100644
--- a/src/runtime/Types/MetaType.cs
+++ b/src/runtime/Types/MetaType.cs
@@ -21,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[]
@@ -35,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;
}
@@ -44,6 +64,7 @@ public static void Release()
{
_metaSlotsHodler.ResetSlots();
}
+ TypeDictOffset = -1;
PyCLRMetaType.Dispose();
}
@@ -287,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/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/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/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/test_method.py b/tests/test_method.py
index c70200c7e..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,17 +1041,17 @@ def test_getting_method_overloads_binding_does_not_leak_ref_count():
refCount = sys.getrefcount(PlainOldClass().OverloadedMethod.Overloads)
assert refCount == 1
-@pytest.mark.xfail(reason="Fails locally, need to investigate later", strict=False)
-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):
@@ -1042,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))
@@ -1050,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
@@ -1069,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)
@@ -1100,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)
@@ -1139,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()
@@ -1154,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()
@@ -1177,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"
@@ -1208,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"
@@ -1243,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)
@@ -1251,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_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/tools/geninterop/geninterop.py b/tools/geninterop/geninterop.py
index 6d80bcfa6..89186737f 100755
--- 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.
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