From b5c222ca7bda1e9d37c788286356a71db1f6e124 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 13 Oct 2022 12:06:26 -0700 Subject: [PATCH 001/194] a few debug helper properties --- src/runtime/Finalizer.cs | 2 ++ src/runtime/PythonTypes/PyObject.cs | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/runtime/Finalizer.cs b/src/runtime/Finalizer.cs index f4b465ecb..713564f08 100644 --- a/src/runtime/Finalizer.cs +++ b/src/runtime/Finalizer.cs @@ -364,6 +364,8 @@ struct PendingFinalization { public IntPtr PyObj; public BorrowedReference Ref => new(PyObj); + public ManagedType? Managed => ManagedType.GetManagedObject(Ref); + public nint RefCount => Runtime.Refcount(Ref); public int RuntimeRun; #if TRACE_ALLOC public string StackTrace; diff --git a/src/runtime/PythonTypes/PyObject.cs b/src/runtime/PythonTypes/PyObject.cs index ce86753eb..bda2d9c02 100644 --- a/src/runtime/PythonTypes/PyObject.cs +++ b/src/runtime/PythonTypes/PyObject.cs @@ -1051,9 +1051,20 @@ public PyList Dir() return Runtime.GetManagedString(strval.BorrowOrThrow()); } - string? DebuggerDisplay => DebugUtil.HaveInterpreterLock() - ? this.ToString() - : $"pyobj at 0x{this.rawPtr:X} (get Py.GIL to see more info)"; + ManagedType? InternalManagedObject => ManagedType.GetManagedObject(this.Reference); + + string? DebuggerDisplay + { + get + { + if (DebugUtil.HaveInterpreterLock()) + return this.ToString(); + var obj = this.InternalManagedObject; + return obj is { } + ? obj.ToString() + : $"pyobj at 0x{this.rawPtr:X} (get Py.GIL to see more info)"; + } + } /// From 6838ee1ce2866d04d4a516801b1f2f7bc1aa2d55 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 13 Oct 2022 13:36:17 -0700 Subject: [PATCH 002/194] fixed name collision for generated delegate dispatchers --- src/runtime/Util/CodeGenerator.cs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/runtime/Util/CodeGenerator.cs b/src/runtime/Util/CodeGenerator.cs index 35a637113..6e0859da0 100644 --- a/src/runtime/Util/CodeGenerator.cs +++ b/src/runtime/Util/CodeGenerator.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Globalization; +using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Threading; @@ -17,13 +19,15 @@ internal class CodeGenerator private readonly AssemblyBuilder aBuilder; private readonly ModuleBuilder mBuilder; + const string NamePrefix = "__Python_Runtime_Generated_"; + internal CodeGenerator() { - var aname = new AssemblyName { Name = "__CodeGenerator_Assembly" }; + var aname = new AssemblyName { Name = GetUniqueAssemblyName(NamePrefix + "Assembly") }; var aa = AssemblyBuilderAccess.Run; aBuilder = Thread.GetDomain().DefineDynamicAssembly(aname, aa); - mBuilder = aBuilder.DefineDynamicModule("__CodeGenerator_Module"); + mBuilder = aBuilder.DefineDynamicModule(NamePrefix + "Module"); } /// @@ -77,5 +81,20 @@ internal static void GenerateMarshalByRefsBack(ILGenerator il, IReadOnlyList(AppDomain.CurrentDomain + .GetAssemblies() + .Select(a => a.GetName().Name)); + for (int i = 0; i < int.MaxValue; i++) + { + string candidate = name + i.ToString(CultureInfo.InvariantCulture); + if (!taken.Contains(candidate)) + return candidate; + } + + throw new NotSupportedException("Too many assemblies"); + } } } From 5a28fd41db0bd1a78477717dbb91f1a10703a074 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 13 Oct 2022 12:07:39 -0700 Subject: [PATCH 003/194] delete target object from event handler collections when it has no more event handlers fixes https://github.com/pythonnet/pythonnet/issues/1972 --- CHANGELOG.md | 2 + src/embed_tests/Events.cs | 67 ++++++++++++++++++++++ src/runtime/Util/EventHandlerCollection.cs | 4 ++ 3 files changed, 73 insertions(+) create mode 100644 src/embed_tests/Events.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d33ee327..9781f289c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Fixed +- Fixed objects leaking when Python attached event handlers to them even if they were later removed + ## [3.0.0](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.0) - 2022-09-29 diff --git a/src/embed_tests/Events.cs b/src/embed_tests/Events.cs new file mode 100644 index 000000000..c216f4214 --- /dev/null +++ b/src/embed_tests/Events.cs @@ -0,0 +1,67 @@ +using System; +using System.Diagnostics; +using System.Threading; + +using NUnit.Framework; + +using Python.Runtime; + +namespace Python.EmbeddingTest; + +public class Events +{ + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + [Test] + public void UsingDoesNotLeak() + { + using var scope = Py.CreateScope(); + scope.Exec(@" +import gc + +from Python.EmbeddingTest import ClassWithEventHandler + +def event_handler(): + pass + +for _ in range(2000): + example = ClassWithEventHandler() + example.LeakEvent += event_handler + example.LeakEvent -= event_handler + del example + +gc.collect() +"); + Runtime.Runtime.TryCollectingGarbage(10); + Assert.AreEqual(0, ClassWithEventHandler.alive); + } +} + +public class ClassWithEventHandler +{ + internal static int alive; + + public event EventHandler LeakEvent; + private Array arr; // dummy array to exacerbate memory leak + + public ClassWithEventHandler() + { + Interlocked.Increment(ref alive); + this.arr = new int[800]; + } + + ~ClassWithEventHandler() + { + Interlocked.Decrement(ref alive); + } +} diff --git a/src/runtime/Util/EventHandlerCollection.cs b/src/runtime/Util/EventHandlerCollection.cs index 551893799..0cd03d0fd 100644 --- a/src/runtime/Util/EventHandlerCollection.cs +++ b/src/runtime/Util/EventHandlerCollection.cs @@ -99,6 +99,10 @@ internal bool RemoveEventHandler(BorrowedReference target, BorrowedReference han continue; } list.RemoveAt(i); + if (list.Count == 0) + { + Remove(key); + } return true; } From c422abdba471a114f04519c08abc7bba71163afb Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 18 Sep 2022 16:00:02 +0200 Subject: [PATCH 004/194] Add Python 3.11 type offsets --- src/runtime/Native/TypeOffset311.cs | 141 ++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 src/runtime/Native/TypeOffset311.cs diff --git a/src/runtime/Native/TypeOffset311.cs b/src/runtime/Native/TypeOffset311.cs new file mode 100644 index 000000000..7691236c9 --- /dev/null +++ b/src/runtime/Native/TypeOffset311.cs @@ -0,0 +1,141 @@ + +// Auto-generated by geninterop.py. +// DO NOT MODIFY BY HAND. + +// Python 3.11: ABI flags: '' + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +using Python.Runtime.Native; + +namespace Python.Runtime +{ + [SuppressMessage("Style", "IDE1006:Naming Styles", + Justification = "Following CPython", + Scope = "type")] + + [StructLayout(LayoutKind.Sequential)] + internal class TypeOffset311 : GeneratedTypeOffsets, ITypeOffsets + { + public TypeOffset311() { } + // Auto-generated from PyHeapTypeObject in Python.h + public int ob_refcnt { get; private set; } + public int ob_type { get; private set; } + public int ob_size { get; private set; } + public int tp_name { get; private set; } + public int tp_basicsize { get; private set; } + public int tp_itemsize { get; private set; } + public int tp_dealloc { get; private set; } + public int tp_vectorcall_offset { get; private set; } + public int tp_getattr { get; private set; } + public int tp_setattr { get; private set; } + public int tp_as_async { get; private set; } + public int tp_repr { get; private set; } + public int tp_as_number { get; private set; } + public int tp_as_sequence { get; private set; } + public int tp_as_mapping { get; private set; } + public int tp_hash { get; private set; } + public int tp_call { get; private set; } + public int tp_str { get; private set; } + public int tp_getattro { get; private set; } + public int tp_setattro { get; private set; } + public int tp_as_buffer { get; private set; } + public int tp_flags { get; private set; } + public int tp_doc { get; private set; } + public int tp_traverse { get; private set; } + public int tp_clear { get; private set; } + public int tp_richcompare { get; private set; } + public int tp_weaklistoffset { get; private set; } + public int tp_iter { get; private set; } + public int tp_iternext { get; private set; } + public int tp_methods { get; private set; } + public int tp_members { get; private set; } + public int tp_getset { get; private set; } + public int tp_base { get; private set; } + public int tp_dict { get; private set; } + public int tp_descr_get { get; private set; } + public int tp_descr_set { get; private set; } + public int tp_dictoffset { get; private set; } + public int tp_init { get; private set; } + public int tp_alloc { get; private set; } + public int tp_new { get; private set; } + public int tp_free { get; private set; } + public int tp_is_gc { get; private set; } + public int tp_bases { get; private set; } + public int tp_mro { get; private set; } + public int tp_cache { get; private set; } + public int tp_subclasses { get; private set; } + public int tp_weaklist { get; private set; } + public int tp_del { get; private set; } + public int tp_version_tag { get; private set; } + public int tp_finalize { get; private set; } + public int tp_vectorcall { get; private set; } + public int am_await { get; private set; } + public int am_aiter { get; private set; } + public int am_anext { get; private set; } + public int am_send { get; private set; } + public int nb_add { get; private set; } + public int nb_subtract { get; private set; } + public int nb_multiply { get; private set; } + public int nb_remainder { get; private set; } + public int nb_divmod { get; private set; } + public int nb_power { get; private set; } + public int nb_negative { get; private set; } + public int nb_positive { get; private set; } + public int nb_absolute { get; private set; } + public int nb_bool { get; private set; } + public int nb_invert { get; private set; } + public int nb_lshift { get; private set; } + public int nb_rshift { get; private set; } + public int nb_and { get; private set; } + public int nb_xor { get; private set; } + public int nb_or { get; private set; } + public int nb_int { get; private set; } + public int nb_reserved { get; private set; } + public int nb_float { get; private set; } + public int nb_inplace_add { get; private set; } + public int nb_inplace_subtract { get; private set; } + public int nb_inplace_multiply { get; private set; } + public int nb_inplace_remainder { get; private set; } + public int nb_inplace_power { get; private set; } + public int nb_inplace_lshift { get; private set; } + public int nb_inplace_rshift { get; private set; } + public int nb_inplace_and { get; private set; } + public int nb_inplace_xor { get; private set; } + public int nb_inplace_or { get; private set; } + public int nb_floor_divide { get; private set; } + public int nb_true_divide { get; private set; } + public int nb_inplace_floor_divide { get; private set; } + public int nb_inplace_true_divide { get; private set; } + public int nb_index { get; private set; } + public int nb_matrix_multiply { get; private set; } + public int nb_inplace_matrix_multiply { get; private set; } + public int mp_length { get; private set; } + public int mp_subscript { get; private set; } + public int mp_ass_subscript { get; private set; } + public int sq_length { get; private set; } + public int sq_concat { get; private set; } + public int sq_repeat { get; private set; } + public int sq_item { get; private set; } + public int was_sq_slice { get; private set; } + public int sq_ass_item { get; private set; } + public int was_sq_ass_slice { get; private set; } + public int sq_contains { get; private set; } + public int sq_inplace_concat { get; private set; } + public int sq_inplace_repeat { get; private set; } + public int bf_getbuffer { get; private set; } + public int bf_releasebuffer { get; private set; } + public int name { get; private set; } + public int ht_slots { get; private set; } + public int qualname { get; private set; } + public int ht_cached_keys { get; private set; } + public int ht_module { get; private set; } + public int _ht_tpname { get; private set; } + } +} + From d6c024c8fb891024cbe7045268a0ca74351c547b Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Mon, 24 Oct 2022 09:53:59 -0700 Subject: [PATCH 005/194] explicit functions for exact type checks --- src/runtime/Runtime.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 6238119ff..7110f3cb0 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -1094,8 +1094,13 @@ internal static nint PyBuffer_SizeFromFormat(string format) internal static bool PyInt_Check(BorrowedReference ob) => PyObject_TypeCheck(ob, PyLongType); + internal static bool PyInt_CheckExact(BorrowedReference ob) + => PyObject_TypeCheckExact(ob, PyLongType); + internal static bool PyBool_Check(BorrowedReference ob) => PyObject_TypeCheck(ob, PyBoolType); + internal static bool PyBool_CheckExact(BorrowedReference ob) + => PyObject_TypeCheckExact(ob, PyBoolType); internal static NewReference PyInt_FromInt32(int value) => PyLong_FromLongLong(value); @@ -1141,6 +1146,8 @@ internal static NewReference PyLong_FromString(string value, int radix) internal static bool PyFloat_Check(BorrowedReference ob) => PyObject_TypeCheck(ob, PyFloatType); + internal static bool PyFloat_CheckExact(BorrowedReference ob) + => PyObject_TypeCheckExact(ob, PyFloatType); /// /// Return value: New reference. @@ -1282,9 +1289,9 @@ internal static bool PyFloat_Check(BorrowedReference ob) // Python string API //==================================================================== internal static bool PyString_Check(BorrowedReference ob) - { - return PyObject_TYPE(ob) == PyStringType; - } + => PyObject_TypeCheck(ob, PyStringType); + internal static bool PyString_CheckExact(BorrowedReference ob) + => PyObject_TypeCheckExact(ob, PyStringType); internal static NewReference PyString_FromString(string value) { @@ -1643,6 +1650,8 @@ internal static bool PyType_IsSubtype(BorrowedReference t1, BorrowedReference t2 return Delegates.PyType_IsSubtype(t1, t2); } + internal static bool PyObject_TypeCheckExact(BorrowedReference ob, BorrowedReference tp) + => PyObject_TYPE(ob) == tp; internal static bool PyObject_TypeCheck(BorrowedReference ob, BorrowedReference tp) { BorrowedReference t = PyObject_TYPE(ob); From 782a0e5e01bd118043497e7327d09c355f101631 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Mon, 24 Oct 2022 09:56:23 -0700 Subject: [PATCH 006/194] allow decoders to override conversion of types derived from primitives when target type is System.Object useful to be able to change what numpy.float64 is converted to related to https://github.com/pythonnet/pythonnet/issues/1957 this is an alternative to https://github.com/pythonnet/pythonnet/pull/1958 --- src/embed_tests/Codecs.cs | 13 +++++++++++++ src/runtime/Converter.cs | 24 ++++++++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index be416bc15..9b764d43f 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -355,6 +355,19 @@ from datetime import datetime scope.Exec("Codecs.AcceptsDateTime(datetime(2021, 1, 22))"); } + [Test] + public void FloatDerivedDecoded() + { + using var scope = Py.CreateScope(); + scope.Exec(@"class FloatDerived(float): pass"); + using var floatDerived = scope.Eval("FloatDerived"); + var decoder = new DecoderReturningPredefinedValue(floatDerived, 42); + PyObjectConversions.RegisterDecoder(decoder); + using var result = scope.Eval("FloatDerived()"); + object decoded = result.As(); + Assert.AreEqual(42, decoded); + } + [Test] public void ExceptionDecodedNoInstance() { diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index 3c46e9034..73bbd4a3a 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -361,28 +361,44 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType, // conversions (Python string -> managed string). if (obType == objectType) { - if (Runtime.PyString_Check(value)) + if (Runtime.PyString_CheckExact(value)) { return ToPrimitive(value, stringType, out result, setError); } - if (Runtime.PyBool_Check(value)) + if (Runtime.PyBool_CheckExact(value)) { return ToPrimitive(value, boolType, out result, setError); } - if (Runtime.PyFloat_Check(value)) + if (Runtime.PyFloat_CheckExact(value)) { return ToPrimitive(value, doubleType, out result, setError); } - // give custom codecs a chance to take over conversion of ints and sequences + // give custom codecs a chance to take over conversion + // of ints, sequences, and types derived from primitives BorrowedReference pyType = Runtime.PyObject_TYPE(value); if (PyObjectConversions.TryDecode(value, pyType, obType, out result)) { return true; } + if (Runtime.PyString_Check(value)) + { + return ToPrimitive(value, stringType, out result, setError); + } + + if (Runtime.PyBool_Check(value)) + { + return ToPrimitive(value, boolType, out result, setError); + } + + if (Runtime.PyFloat_Check(value)) + { + return ToPrimitive(value, doubleType, out result, setError); + } + if (Runtime.PyInt_Check(value)) { result = new PyInt(value); From 2b5291000e0fae50bdf3db3e9a7a38157b319ba6 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 18 Sep 2022 16:03:40 +0200 Subject: [PATCH 007/194] Add Python 3.11 to metadata and workflows --- .github/workflows/main.yml | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2cc793621..ea93ce18c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ jobs: fail-fast: false matrix: os: [windows, ubuntu, macos] - python: ["3.7", "3.8", "3.9", "3.10"] + python: ["3.7", "3.8", "3.9", "3.10", "3.11"] platform: [x64, x86] exclude: - os: ubuntu diff --git a/pyproject.toml b/pyproject.toml index 91f386fc6..d0512cc45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ classifiers = [ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Operating System :: MacOS :: MacOS X", From c0b4eb285ef3af1fdf90b0461e14c26b9969cf7a Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 19 Sep 2022 11:09:01 +0200 Subject: [PATCH 008/194] Improve geninterop script to handle new case in 3.11 --- tools/geninterop/geninterop.py | 144 +++++++++++++++++++-------------- 1 file changed, 83 insertions(+), 61 deletions(-) mode change 100644 => 100755 tools/geninterop/geninterop.py diff --git a/tools/geninterop/geninterop.py b/tools/geninterop/geninterop.py old mode 100644 new mode 100755 index 0c80c1904..78e4d45c2 --- a/tools/geninterop/geninterop.py +++ b/tools/geninterop/geninterop.py @@ -13,40 +13,26 @@ - clang """ -from __future__ import print_function - -import logging import os +import shutil import sys import sysconfig import subprocess -if sys.version_info.major > 2: - from io import StringIO -else: - from StringIO import StringIO - +from io import StringIO +from pathlib import Path from pycparser import c_ast, c_parser -_log = logging.getLogger() -logging.basicConfig(level=logging.DEBUG) - -PY_MAJOR = sys.version_info[0] -PY_MINOR = sys.version_info[1] - # rename some members from their C name when generating the C# _typeoffset_member_renames = { "ht_name": "name", - "ht_qualname": "qualname" + "ht_qualname": "qualname", + "getitem": "spec_cache_getitem", } def _check_output(*args, **kwargs): - """Check output wrapper for py2/py3 compatibility""" - output = subprocess.check_output(*args, **kwargs) - if PY_MAJOR == 2: - return output - return output.decode("ascii") + return subprocess.check_output(*args, **kwargs, encoding="utf8") class AstParser(object): @@ -92,7 +78,7 @@ def visit(self, node): self.visit_identifier(node) def visit_ast(self, ast): - for name, node in ast.children(): + for _name, node in ast.children(): self.visit(node) def visit_typedef(self, typedef): @@ -113,7 +99,7 @@ def visit_struct(self, struct): self.visit(decl) self._struct_members_stack.pop(0) self._struct_stack.pop(0) - elif self._ptr_decl_depth: + elif self._ptr_decl_depth or self._struct_members_stack: # the struct is empty, but add it as a member to the current # struct as the current member maybe a pointer to it. self._add_struct_member(struct.name) @@ -141,7 +127,8 @@ def _add_struct_member(self, type_name): current_struct = self._struct_stack[0] member_name = self._struct_members_stack[0] struct_members = self._struct_members.setdefault( - self._get_struct_name(current_struct), []) + self._get_struct_name(current_struct), [] + ) # get the node associated with this type node = None @@ -179,7 +166,6 @@ def _get_struct_name(self, node): class Writer(object): - def __init__(self): self._stream = StringIO() @@ -193,34 +179,47 @@ def to_string(self): return self._stream.getvalue() -def preprocess_python_headers(): +def preprocess_python_headers(*, cc=None, include_py=None): """Return Python.h pre-processed, ready for parsing. Requires clang. """ - fake_libc_include = os.path.join(os.path.dirname(__file__), - "fake_libc_include") + this_path = Path(__file__).parent + + fake_libc_include = this_path / "fake_libc_include" include_dirs = [fake_libc_include] - include_py = sysconfig.get_config_var("INCLUDEPY") + if cc is None: + cc = shutil.which("clang") + if cc is None: + cc = shutil.which("gcc") + if cc is None: + raise RuntimeError("No suitable C compiler found, need clang or gcc") + + if include_py is None: + include_py = sysconfig.get_config_var("INCLUDEPY") + include_py = Path(include_py) + include_dirs.append(include_py) - include_args = [c for p in include_dirs for c in ["-I", p]] + include_args = [c for p in include_dirs for c in ["-I", str(p)]] + # fmt: off defines = [ "-D", "__attribute__(x)=", "-D", "__inline__=inline", "-D", "__asm__=;#pragma asm", "-D", "__int64=long long", - "-D", "_POSIX_THREADS" + "-D", "_POSIX_THREADS", ] - if os.name == 'nt': + if sys.platform == "win32": defines.extend([ "-D", "__inline=inline", "-D", "__ptr32=", "-D", "__ptr64=", "-D", "__declspec(x)=", ]) + #fmt: on if hasattr(sys, "abiflags"): if "d" in sys.abiflags: @@ -228,8 +227,8 @@ def preprocess_python_headers(): if "u" in sys.abiflags: defines.extend(("-D", "PYTHON_WITH_WIDE_UNICODE")) - python_h = os.path.join(include_py, "Python.h") - cmd = ["clang", "-pthread"] + include_args + defines + ["-E", python_h] + python_h = include_py / "Python.h" + cmd = [cc, "-pthread"] + include_args + defines + ["-E", str(python_h)] # normalize as the parser doesn't like windows line endings. lines = [] @@ -240,16 +239,13 @@ def preprocess_python_headers(): return "\n".join(lines) - -def gen_interop_head(writer): +def gen_interop_head(writer, version, abi_flags): filename = os.path.basename(__file__) - abi_flags = getattr(sys, "abiflags", "").replace("m", "") - py_ver = "{0}.{1}".format(PY_MAJOR, PY_MINOR) - class_definition = """ -// Auto-generated by %s. + class_definition = f""" +// Auto-generated by {filename}. // DO NOT MODIFY BY HAND. -// Python %s: ABI flags: '%s' +// Python {".".join(version[:2])}: ABI flags: '{abi_flags}' // ReSharper disable InconsistentNaming // ReSharper disable IdentifierTypo @@ -261,7 +257,7 @@ def gen_interop_head(writer): using Python.Runtime.Native; namespace Python.Runtime -{""" % (filename, py_ver, abi_flags) +{{""" writer.extend(class_definition) @@ -271,25 +267,24 @@ def gen_interop_tail(writer): writer.extend(tail) -def gen_heap_type_members(parser, writer, type_name = None): +def gen_heap_type_members(parser, writer, type_name): """Generate the TypeOffset C# class""" members = parser.get_struct_members("PyHeapTypeObject") - type_name = type_name or "TypeOffset{0}{1}".format(PY_MAJOR, PY_MINOR) - class_definition = """ + class_definition = f""" [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Following CPython", Scope = "type")] [StructLayout(LayoutKind.Sequential)] - internal class {0} : GeneratedTypeOffsets, ITypeOffsets + internal class {type_name} : GeneratedTypeOffsets, ITypeOffsets {{ - public {0}() {{ }} + public {type_name}() {{ }} // Auto-generated from PyHeapTypeObject in Python.h -""".format(type_name) +""" # All the members are sizeof(void*) so we don't need to do any # extra work to determine the size based on the type. - for name, tpy in members: + for name, _type in members: name = _typeoffset_member_renames.get(name, name) class_definition += " public int %s { get; private set; }\n" % name @@ -304,17 +299,18 @@ def gen_structure_code(parser, writer, type_name, indent): return False out = writer.append out(indent, "[StructLayout(LayoutKind.Sequential)]") - out(indent, "internal struct %s" % type_name) + out(indent, f"internal struct {type_name}") out(indent, "{") - for name, tpy in members: - out(indent + 1, "public IntPtr %s;" % name) + for name, _type in members: + out(indent + 1, f"public IntPtr {name};") out(indent, "}") out() return True -def main(): + +def main(*, cc=None, include_py=None, version=None, out=None): # preprocess Python.h and build the AST - python_h = preprocess_python_headers() + python_h = preprocess_python_headers(cc=cc, include_py=include_py) parser = c_parser.CParser() ast = parser.parse(python_h) @@ -323,21 +319,47 @@ def main(): ast_parser.visit(ast) writer = Writer() + + if include_py and not version: + raise RuntimeError("If the include path is overridden, version must be " + "defined" + ) + + if version: + version = version.split('.') + else: + version = sys.version_info + # generate the C# code - offsets_type_name = "NativeTypeOffset" if len(sys.argv) > 1 else None - gen_interop_head(writer) + abi_flags = getattr(sys, "abiflags", "").replace("m", "") + gen_interop_head(writer, version, abi_flags) - gen_heap_type_members(ast_parser, writer, type_name = offsets_type_name) + type_name = f"TypeOffset{version[0]}{version[1]}{abi_flags}" + gen_heap_type_members(ast_parser, writer, type_name) gen_interop_tail(writer) interop_cs = writer.to_string() - if len(sys.argv) > 1: - with open(sys.argv[1], "w") as fh: - fh.write(interop_cs) - else: + if not out or out == "-": print(interop_cs) + else: + with open(out, "w") as fh: + fh.write(interop_cs) if __name__ == "__main__": - sys.exit(main()) + import argparse + + a = argparse.ArgumentParser("Interop file generator for Python.NET") + a.add_argument("--cc", help="C compiler to use, either clang or gcc") + a.add_argument("--include-py", help="Include path of Python") + a.add_argument("--version", help="Python version") + a.add_argument("--out", help="Output path", default="-") + args = a.parse_args() + + sys.exit(main( + cc=args.cc, + include_py=args.include_py, + out=args.out, + version=args.version + )) From cc8606856278a511a2965c8224032cab40c6958d Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 19 Sep 2022 11:09:18 +0200 Subject: [PATCH 009/194] Fix offsets for 3.11 --- src/runtime/Native/TypeOffset311.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/Native/TypeOffset311.cs b/src/runtime/Native/TypeOffset311.cs index 7691236c9..de5afacb9 100644 --- a/src/runtime/Native/TypeOffset311.cs +++ b/src/runtime/Native/TypeOffset311.cs @@ -136,6 +136,6 @@ public TypeOffset311() { } public int ht_cached_keys { get; private set; } public int ht_module { get; private set; } public int _ht_tpname { get; private set; } + public int spec_cache_getitem { get; private set; } } } - From 5636262b27f883c484369c89ecdbcd54afa97a56 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 15 Oct 2022 21:41:55 +0200 Subject: [PATCH 010/194] Update requires-python --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d0512cc45..52f1adb18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "clr_loader>=0.2.2,<0.3.0" ] -requires-python = ">=3.7, <3.11" +requires-python = ">=3.7, <3.12" classifiers = [ "Development Status :: 5 - Production/Stable", From 8668579834623a1ef196840b8f2a93407f52701b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 15 Oct 2022 22:13:30 +0200 Subject: [PATCH 011/194] Define slots before initialization --- src/runtime/PythonTypes/PyType.cs | 1 + src/runtime/TypeManager.cs | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/runtime/PythonTypes/PyType.cs b/src/runtime/PythonTypes/PyType.cs index 260800592..af796a5c5 100644 --- a/src/runtime/PythonTypes/PyType.cs +++ b/src/runtime/PythonTypes/PyType.cs @@ -155,6 +155,7 @@ private static StolenReference FromSpec(TypeSpec spec, PyTuple? bases = null) using var nativeSpec = new NativeTypeSpec(spec); var basesRef = bases is null ? default : bases.Reference; var result = Runtime.PyType_FromSpecWithBases(in nativeSpec, basesRef); + // Runtime.PyErr_Print(); return result.StealOrThrow(); } } diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index 217b4820e..6170f820f 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -475,17 +475,20 @@ internal static PyType CreateMetatypeWithGCHandleOffset() int size = Util.ReadInt32(Runtime.PyTypeType, TypeOffset.tp_basicsize) + IntPtr.Size // tp_clr_inst_offset ; - var result = new PyType(new TypeSpec("clr._internal.GCOffsetBase", basicSize: size, - new TypeSpec.Slot[] - { - - }, - TypeFlags.Default | TypeFlags.HeapType | TypeFlags.HaveGC), - bases: new PyTuple(new[] { py_type })); - SetRequiredSlots(result, seen: new HashSet()); - - Runtime.PyType_Modified(result); + var slots = new[] { + new TypeSpec.Slot(TypeSlotID.tp_traverse, subtype_traverse), + new TypeSpec.Slot(TypeSlotID.tp_clear, subtype_clear) + }; + var result = new PyType( + new TypeSpec( + "clr._internal.GCOffsetBase", + basicSize: size, + slots: slots, + TypeFlags.Default | TypeFlags.HeapType | TypeFlags.HaveGC + ), + bases: new PyTuple(new[] { py_type }) + ); return result; } From 218610ea33ef22ec1836f4fd43c3223676de5e51 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Fri, 28 Oct 2022 16:00:56 -0700 Subject: [PATCH 012/194] fixed positive PyInt converted to negative BigInteger BigInteger constructor uses the sign bit in the first byte. Since we explicitly handle the sign, the fix is to prepend a zero byte to the number, which does not change it, but ensures sign bit is zero. fixes https://github.com/pythonnet/pythonnet/issues/1990 --- CHANGELOG.md | 1 + src/embed_tests/TestPyInt.cs | 17 ++++++++++++----- src/runtime/PythonTypes/PyInt.cs | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9781f289c..71c36b412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Fixed - Fixed objects leaking when Python attached event handlers to them even if they were later removed +- Fixed `PyInt` conversion to `BigInteger` and `System.String` produced incorrect result for values between 128 and 255. ## [3.0.0](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.0) - 2022-09-29 diff --git a/src/embed_tests/TestPyInt.cs b/src/embed_tests/TestPyInt.cs index 822fe0715..c147e074b 100644 --- a/src/embed_tests/TestPyInt.cs +++ b/src/embed_tests/TestPyInt.cs @@ -191,16 +191,23 @@ public void ToBigInteger() { 0, 1, 2, 0x10, + 0x79, + 0x80, + 0x81, + 0xFF, 0x123, + 0x8000, 0x1234, + 0x8001, + 0x4000, + 0xFF, }; simpleValues = simpleValues.Concat(simpleValues.Select(v => -v)).ToArray(); - foreach (var val in simpleValues) - { - var pyInt = new PyInt(val); - Assert.AreEqual((BigInteger)val, pyInt.ToBigInteger()); - } + var expected = simpleValues.Select(v => new BigInteger(v)).ToArray(); + var actual = simpleValues.Select(v => new PyInt(v).ToBigInteger()).ToArray(); + + CollectionAssert.AreEqual(expected, actual); } [Test] diff --git a/src/runtime/PythonTypes/PyInt.cs b/src/runtime/PythonTypes/PyInt.cs index 6b3dbf210..e71462b74 100644 --- a/src/runtime/PythonTypes/PyInt.cs +++ b/src/runtime/PythonTypes/PyInt.cs @@ -212,7 +212,7 @@ public BigInteger ToBigInteger() offset++; neg = true; } - byte[] littleEndianBytes = new byte[(hex.Length - offset + 1) / 2]; + byte[] littleEndianBytes = new byte[(hex.Length - offset + 1) / 2 + 1]; for (; offset < hex.Length; offset++) { int littleEndianHexIndex = hex.Length - 1 - offset; From ddf5a701d2542c81b6bf0dcc6ad4a5a440713408 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 30 Oct 2022 21:36:03 +0100 Subject: [PATCH 013/194] Only clear dict if tp_dictoffset > 0 --- src/runtime/Types/ManagedType.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/runtime/Types/ManagedType.cs b/src/runtime/Types/ManagedType.cs index 2ed9d7970..97a19497c 100644 --- a/src/runtime/Types/ManagedType.cs +++ b/src/runtime/Types/ManagedType.cs @@ -148,8 +148,9 @@ protected static void ClearObjectDict(BorrowedReference ob) { BorrowedReference type = Runtime.PyObject_TYPE(ob); int instanceDictOffset = Util.ReadInt32(type, TypeOffset.tp_dictoffset); - Debug.Assert(instanceDictOffset > 0); - Runtime.Py_CLEAR(ob, instanceDictOffset); + // Debug.Assert(instanceDictOffset > 0); + if (instanceDictOffset > 0) + Runtime.Py_CLEAR(ob, instanceDictOffset); } protected static BorrowedReference GetObjectDict(BorrowedReference ob) From d3b56ffeeb35091365676362665460268d087a87 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 30 Oct 2022 21:40:26 +0100 Subject: [PATCH 014/194] Ensure that sub-processes in tests use the same runtime settings --- tests/conftest.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e61e3680e..1ac20e1dd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -79,9 +79,10 @@ def pytest_configure(config): ["dotnet", "publish", "-f", fw, "-o", str(bin_path), str(test_proj_path)] ) - from pythonnet import load - - load(runtime_opt, **runtime_params) + import os + os.environ["PYTHONNET_RUNTIME"] = runtime_opt + for k, v in runtime_params.items(): + os.environ[f"PYTHONNET_{runtime_opt.upper()}_{k.upper()}"] = v import clr From a6efeaee77404b75e557bae5cf795cbdd704bd00 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 30 Oct 2022 21:40:42 +0100 Subject: [PATCH 015/194] Update MaxSupportedVersion --- src/runtime/PythonEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index e5879ae67..ddf597c4f 100644 --- a/src/runtime/PythonEngine.cs +++ b/src/runtime/PythonEngine.cs @@ -128,7 +128,7 @@ public static string PythonPath } public static Version MinSupportedVersion => new(3, 7); - public static Version MaxSupportedVersion => new(3, 10, int.MaxValue, int.MaxValue); + public static Version MaxSupportedVersion => new(3, 11, int.MaxValue, int.MaxValue); public static bool IsSupportedVersion(Version version) => version >= MinSupportedVersion && version <= MaxSupportedVersion; public static string Version From da082ac487190aed5d325a346b0a6c268c870020 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 30 Oct 2022 23:16:57 +0100 Subject: [PATCH 016/194] Enforce tp_traverse/clear in AllocateTypeObject --- src/runtime/TypeManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index 6170f820f..e0a78ba49 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -620,6 +620,11 @@ internal static PyType AllocateTypeObject(string name, PyType metatype) Util.WriteRef(type, TypeOffset.name, new NewReference(temp).Steal()); Util.WriteRef(type, TypeOffset.qualname, temp.Steal()); + // Ensure that tp_traverse and tp_clear are always set, since their + // existence is enforced in newer Python versions in PyType_Ready + Util.WriteIntPtr(type, TypeOffset.tp_traverse, subtype_traverse); + Util.WriteIntPtr(type, TypeOffset.tp_clear, subtype_clear); + InheritSubstructs(type.Reference.DangerousGetAddress()); return type; From e9283e3ec35184c7c4464540756f3d20c1301bfd Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 31 Oct 2022 14:51:18 +0100 Subject: [PATCH 017/194] Ensure that Python is initialized before probing properties --- src/embed_tests/TestPythonEngineProperties.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs index ca9164a1d..bbaff047f 100644 --- a/src/embed_tests/TestPythonEngineProperties.cs +++ b/src/embed_tests/TestPythonEngineProperties.cs @@ -9,6 +9,7 @@ public class TestPythonEngineProperties [Test] public static void GetBuildinfoDoesntCrash() { + PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.BuildInfo; @@ -21,6 +22,7 @@ public static void GetBuildinfoDoesntCrash() [Test] public static void GetCompilerDoesntCrash() { + PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.Compiler; @@ -34,6 +36,7 @@ public static void GetCompilerDoesntCrash() [Test] public static void GetCopyrightDoesntCrash() { + PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.Copyright; @@ -46,6 +49,7 @@ public static void GetCopyrightDoesntCrash() [Test] public static void GetPlatformDoesntCrash() { + PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.Platform; @@ -58,6 +62,7 @@ public static void GetPlatformDoesntCrash() [Test] public static void GetVersionDoesntCrash() { + PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.Version; From cc97b8a49646e952a774e105f77d00f21761d864 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 1 Nov 2022 14:46:39 +0100 Subject: [PATCH 018/194] Add an Action variant of TryUsingDll --- src/runtime/Runtime.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 6238119ff..26e83a5f9 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -672,6 +672,9 @@ internal static unsafe nint Refcount(BorrowedReference op) [Pure] internal static int Refcount32(BorrowedReference op) => checked((int)Refcount(op)); + internal static void TryUsingDll(Action op) => + TryUsingDll(() => { op(); return 0; }); + /// /// Call specified function, and handle PythonDLL-related failures. /// From 096f50a21e235b6a8a15c011d0a390468901daa2 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 2 Nov 2022 06:36:34 +0100 Subject: [PATCH 019/194] Adjust code a bit and skip PythonHome tests for empty strings --- src/embed_tests/TestPythonEngineProperties.cs | 49 ++++++++++++------- src/runtime/PythonEngine.cs | 15 ++++-- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs index bbaff047f..be91d7f45 100644 --- a/src/embed_tests/TestPythonEngineProperties.cs +++ b/src/embed_tests/TestPythonEngineProperties.cs @@ -96,9 +96,6 @@ public static void GetProgramNameDefault() /// Test default behavior of PYTHONHOME. If ENVVAR is set it will /// return the same value. If not, returns EmptyString. /// - /// - /// AppVeyor.yml has been update to tests with ENVVAR set. - /// [Test] public static void GetPythonHomeDefault() { @@ -114,22 +111,19 @@ public static void GetPythonHomeDefault() [Test] public void SetPythonHome() { - // We needs to ensure that engine was started and shutdown at least once before setting dummy home. - // Otherwise engine will not run with dummy path with random problem. - if (!PythonEngine.IsInitialized) - { - PythonEngine.Initialize(); - } - + PythonEngine.Initialize(); + var pythonHomeBackup = PythonEngine.PythonHome; PythonEngine.Shutdown(); - var pythonHomeBackup = PythonEngine.PythonHome; + 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. @@ -139,15 +133,12 @@ public void SetPythonHome() [Test] public void SetPythonHomeTwice() { - // We needs to ensure that engine was started and shutdown at least once before setting dummy home. - // Otherwise engine will not run with dummy path with random problem. - if (!PythonEngine.IsInitialized) - { - PythonEngine.Initialize(); - } + PythonEngine.Initialize(); + var pythonHomeBackup = PythonEngine.PythonHome; PythonEngine.Shutdown(); - var pythonHomeBackup = PythonEngine.PythonHome; + if (pythonHomeBackup == "") + Assert.Inconclusive("Can't reset PythonHome to empty string, skipping"); var pythonHome = "/dummypath/"; @@ -161,6 +152,26 @@ public void SetPythonHomeTwice() 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() { @@ -207,7 +218,7 @@ public void SetPythonPath() // 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. + // After PythonPath is set, then PythonEngine.PythonPath will correctly return the full search path. PythonEngine.Shutdown(); diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index ddf597c4f..4ed45b9e9 100644 --- a/src/runtime/PythonEngine.cs +++ b/src/runtime/PythonEngine.cs @@ -47,6 +47,14 @@ public static bool IsInitialized get { return initialized; } } + private static void EnsureInitialized() + { + if (!IsInitialized) + throw new InvalidOperationException( + "Python must be initialized for this operation" + ); + } + /// Set to true to enable GIL debugging assistance. public static bool DebugGIL { get; set; } = false; @@ -96,6 +104,7 @@ public static string PythonHome { get { + EnsureInitialized(); IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetPythonHome()); return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? ""; } @@ -103,10 +112,8 @@ public static string PythonHome { // this value is null in the beginning Marshal.FreeHGlobal(_pythonHome); - _pythonHome = Runtime.TryUsingDll( - () => UcsMarshaler.Py3UnicodePy2StringtoPtr(value) - ); - Runtime.Py_SetPythonHome(_pythonHome); + _pythonHome = UcsMarshaler.Py3UnicodePy2StringtoPtr(value); + Runtime.TryUsingDll(() => Runtime.Py_SetPythonHome(_pythonHome)); } } From 461c9c708ce36a054c363700f232a1b5df09ed2e Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 2 Nov 2022 07:59:03 +0100 Subject: [PATCH 020/194] Add tests for deriving a Python subclass from a generic interface --- src/testing/interfacetest.cs | 23 ++++++++++++++++++++++- tests/test_subclass.py | 23 ++++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/testing/interfacetest.cs b/src/testing/interfacetest.cs index 7c5d937b9..a1a7106b5 100644 --- a/src/testing/interfacetest.cs +++ b/src/testing/interfacetest.cs @@ -79,7 +79,7 @@ private interface IPrivate { } } - + public interface IOutArg { string MyMethod_Out(string name, out int index); @@ -93,4 +93,25 @@ public static int CallMyMethod_Out(IOutArg myInterface) return index; } } + + public interface IGenericInterface + { + public T Get(T x); + } + + public class SpecificInterfaceUser + { + public SpecificInterfaceUser(IGenericInterface some, int x) + { + some.Get(x); + } + } + + public class GenericInterfaceUser + { + public GenericInterfaceUser(IGenericInterface some, T x) + { + some.Get(x); + } + } } diff --git a/tests/test_subclass.py b/tests/test_subclass.py index fa82c3663..a51e89da3 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -9,7 +9,7 @@ import System import pytest from Python.Test import (IInterfaceTest, SubClassTest, EventArgsTest, - FunctionsTest) + FunctionsTest, IGenericInterface) from System.Collections.Generic import List @@ -29,6 +29,17 @@ def bar(self, x, i): return InterfaceTestClass +def interface_generic_class_fixture(subnamespace): + + class GenericInterfaceImpl(IGenericInterface[int]): + __namespace__ = "Python.Test." + subnamespace + + def Get(self, x): + return x + + return GenericInterfaceImpl + + def derived_class_fixture(subnamespace): """Delay creation of class until test starts.""" @@ -306,3 +317,13 @@ class Derived(BaseClass): import gc gc.collect() + +def test_generic_interface(): + from System import Int32 + from Python.Test import GenericInterfaceUser, SpecificInterfaceUser + + GenericInterfaceImpl = interface_generic_class_fixture(test_generic_interface.__name__) + + obj = GenericInterfaceImpl() + SpecificInterfaceUser(obj, Int32(0)) + GenericInterfaceUser[Int32](obj, Int32(0)) From 15e2e9596ccf6c23ea1bc27db4977055455c170d Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 2 Nov 2022 08:29:02 +0100 Subject: [PATCH 021/194] Set PYTHONHOME for tests --- .github/workflows/main.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ea93ce18c..93963c70a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,15 +54,17 @@ jobs: run: | pip install -v . - - name: Set Python DLL path (non Windows) + - name: Set Python DLL path and PYTHONHOME (non Windows) if: ${{ matrix.os != 'windows' }} run: | echo PYTHONNET_PYDLL=$(python -m find_libpython) >> $GITHUB_ENV + echo PYTHONHOME=$(python -c 'import sys; print(sys.prefix)') >> $GITHUB_ENV - - name: Set Python DLL path (Windows) + - name: Set Python DLL path and PYTHONHOME (Windows) if: ${{ matrix.os == '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: Embedding tests run: dotnet test --runtime any-${{ matrix.platform }} --logger "console;verbosity=detailed" src/embed_tests/ From cc364c359beac37b218cd302af3b600f29e17b35 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 2 Nov 2022 09:08:32 +0100 Subject: [PATCH 022/194] Update changelog and add Python 3.11 to metadata --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71c36b412..8ac8e6c06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Added +- Support for Python 3.11 + ### Changed ### Fixed From 5c1e02f0b5d38def43e8547b5ca701d724f80c80 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 2 Nov 2022 11:15:04 -0700 Subject: [PATCH 023/194] fixed resolution of generic methods in Python implementations since RuntimeMethodHandle does not encode generic arguments, I had to supply RuntimeTypeHandle of the declaring type to be able to get fully specified method --- src/runtime/Types/ClassDerived.cs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/runtime/Types/ClassDerived.cs b/src/runtime/Types/ClassDerived.cs index 02288faee..cf6d9b16b 100644 --- a/src/runtime/Types/ClassDerived.cs +++ b/src/runtime/Types/ClassDerived.cs @@ -436,6 +436,7 @@ private static void AddVirtualMethod(MethodInfo method, Type baseType, TypeBuild il.Emit(OpCodes.Ldloc_0); il.Emit(OpCodes.Ldtoken, method); + il.Emit(OpCodes.Ldtoken, method.DeclaringType); #pragma warning disable CS0618 // PythonDerivedType is for internal use only if (method.ReturnType == typeof(void)) { @@ -505,6 +506,7 @@ private static void AddPythonMethod(string methodName, PyObject func, TypeBuilde il.DeclareLocal(typeof(object[])); il.DeclareLocal(typeof(RuntimeMethodHandle)); + il.DeclareLocal(typeof(RuntimeTypeHandle)); // this il.Emit(OpCodes.Ldarg_0); @@ -546,6 +548,11 @@ private static void AddPythonMethod(string methodName, PyObject func, TypeBuilde il.Emit(OpCodes.Ldloca_S, 1); il.Emit(OpCodes.Initobj, typeof(RuntimeMethodHandle)); il.Emit(OpCodes.Ldloc_1); + + // type handle is also not required + il.Emit(OpCodes.Ldloca_S, 2); + il.Emit(OpCodes.Initobj, typeof(RuntimeTypeHandle)); + il.Emit(OpCodes.Ldloc_2); #pragma warning disable CS0618 // PythonDerivedType is for internal use only // invoke the method @@ -698,7 +705,7 @@ public class PythonDerivedType /// class) it calls it, otherwise it calls the base method. /// public static T? InvokeMethod(IPythonDerivedType obj, string methodName, string origMethodName, - object[] args, RuntimeMethodHandle methodHandle) + object[] args, RuntimeMethodHandle methodHandle, RuntimeTypeHandle declaringTypeHandle) { var self = GetPyObj(obj); @@ -724,7 +731,10 @@ public class PythonDerivedType } PyObject py_result = method.Invoke(pyargs); - PyTuple? result_tuple = MarshalByRefsBack(args, methodHandle, py_result, outsOffset: 1); + var clrMethod = methodHandle != default + ? MethodBase.GetMethodFromHandle(methodHandle, declaringTypeHandle) + : null; + PyTuple? result_tuple = MarshalByRefsBack(args, clrMethod, py_result, outsOffset: 1); return result_tuple is not null ? result_tuple[0].As() : py_result.As(); @@ -754,7 +764,7 @@ public class PythonDerivedType } public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, string origMethodName, - object?[] args, RuntimeMethodHandle methodHandle) + object?[] args, RuntimeMethodHandle methodHandle, RuntimeTypeHandle declaringTypeHandle) { var self = GetPyObj(obj); if (null != self.Ref) @@ -779,7 +789,10 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s } PyObject py_result = method.Invoke(pyargs); - MarshalByRefsBack(args, methodHandle, py_result, outsOffset: 0); + var clrMethod = methodHandle != default + ? MethodBase.GetMethodFromHandle(methodHandle, declaringTypeHandle) + : null; + MarshalByRefsBack(args, clrMethod, py_result, outsOffset: 0); return; } } @@ -811,12 +824,11 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s /// as a tuple of new values for those arguments, and updates corresponding /// elements of array. /// - private static PyTuple? MarshalByRefsBack(object?[] args, RuntimeMethodHandle methodHandle, PyObject pyResult, int outsOffset) + private static PyTuple? MarshalByRefsBack(object?[] args, MethodBase? method, PyObject pyResult, int outsOffset) { - if (methodHandle == default) return null; + if (method is null) return null; - var originalMethod = MethodBase.GetMethodFromHandle(methodHandle); - var parameters = originalMethod.GetParameters(); + var parameters = method.GetParameters(); PyTuple? outs = null; int byrefIndex = 0; for (int i = 0; i < parameters.Length; ++i) From e28dd80c3a8fb60bd62671b765ff41c98ac5b1e9 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 3 Nov 2022 00:21:39 +0100 Subject: [PATCH 024/194] Release 3.0.1 --- CHANGELOG.md | 11 +++++++++++ version.txt | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac8e6c06..13bf09c2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,14 +9,25 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Added +### Changed + +### Fixed + +## [3.0.1](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.1) - 2022-11-03 + +### Added + - Support for Python 3.11 ### Changed +- Allow decoders to override conversion of types derived from primitive types + ### Fixed - Fixed objects leaking when Python attached event handlers to them even if they were later removed - Fixed `PyInt` conversion to `BigInteger` and `System.String` produced incorrect result for values between 128 and 255. +- Fixed implementing a generic interface with a Python class ## [3.0.0](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.0) - 2022-09-29 diff --git a/version.txt b/version.txt index 0f9d6b15d..cb2b00e4f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.0-dev +3.0.1 From 556882de020032df9d77ccd81ac754a55069f3ec Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 3 Nov 2022 00:29:37 +0100 Subject: [PATCH 025/194] Reset version to dev --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index cb2b00e4f..0f9d6b15d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.1 +3.1.0-dev From 17296982aa2e0780a09205b36d99d329c4d337f7 Mon Sep 17 00:00:00 2001 From: Rolf Madsen Date: Wed, 30 Nov 2022 22:38:42 +0100 Subject: [PATCH 026/194] 1776 Generic Virtual Method Causes Invalid Program: Fix + test (#2026) - If a method is virtual AND generic, it cannot be overridden by the python class. Hence the method call is deferred to the base class. - Added a unit test which verifies this behavior is now working. NOTE: The test causes crash when run from `PythonTestRunner`. See https://github.com/pythonnet/pythonnet/issues/2035 --- CHANGELOG.md | 2 ++ src/runtime/Types/ClassDerived.cs | 5 ++++- src/testing/generictest.cs | 8 ++++++++ tests/test_subclass.py | 14 +++++++++++++- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13bf09c2c..2347895a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Fixed +- Fixed error occuring when inheriting a class containing a virtual generic method. + ## [3.0.1](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.1) - 2022-11-03 ### Added diff --git a/src/runtime/Types/ClassDerived.cs b/src/runtime/Types/ClassDerived.cs index cf6d9b16b..61c602783 100644 --- a/src/runtime/Types/ClassDerived.cs +++ b/src/runtime/Types/ClassDerived.cs @@ -220,7 +220,10 @@ internal static Type CreateDerivedType(string name, foreach (MethodInfo method in methods) { if (!method.Attributes.HasFlag(MethodAttributes.Virtual) | - method.Attributes.HasFlag(MethodAttributes.Final)) + method.Attributes.HasFlag(MethodAttributes.Final) + // overriding generic virtual methods is not supported + // so a call to that should be deferred to the base class method. + || method.IsGenericMethod) { continue; } diff --git a/src/testing/generictest.cs b/src/testing/generictest.cs index 238435811..b333910c2 100644 --- a/src/testing/generictest.cs +++ b/src/testing/generictest.cs @@ -136,4 +136,12 @@ public static T[] EchoRange(T[] items) return items; } } + + public abstract class GenericVirtualMethodTest + { + public virtual Q VirtMethod(Q arg1) + { + return arg1; + } + } } diff --git a/tests/test_subclass.py b/tests/test_subclass.py index a51e89da3..504b82548 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -9,7 +9,7 @@ import System import pytest from Python.Test import (IInterfaceTest, SubClassTest, EventArgsTest, - FunctionsTest, IGenericInterface) + FunctionsTest, IGenericInterface, GenericVirtualMethodTest) from System.Collections.Generic import List @@ -327,3 +327,15 @@ def test_generic_interface(): obj = GenericInterfaceImpl() SpecificInterfaceUser(obj, Int32(0)) GenericInterfaceUser[Int32](obj, Int32(0)) + +def test_virtual_generic_method(): + class OverloadingSubclass(GenericVirtualMethodTest): + __namespace__ = "test_virtual_generic_method_cls" + class OverloadingSubclass2(OverloadingSubclass): + __namespace__ = "test_virtual_generic_method_cls" + obj = OverloadingSubclass() + assert obj.VirtMethod[int](5) == 5 + obj = OverloadingSubclass2() + assert obj.VirtMethod[int](5) == 5 + + From 4020a0ca4738abdcb556c3d81f237081687b6461 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 17 Jan 2023 15:35:11 -0800 Subject: [PATCH 027/194] don't force setuptools upgrade during installation of requirements for testing upgrade caused InvalidVersion: Invalid version: '0.23ubuntu1' on Ubuntu due to setuptools dropping support for that version format --- .github/workflows/ARM.yml | 2 +- requirements.txt | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ARM.yml b/.github/workflows/ARM.yml index af257bcb8..0492b7a3a 100644 --- a/.github/workflows/ARM.yml +++ b/.github/workflows/ARM.yml @@ -27,7 +27,7 @@ jobs: - name: Install dependencies run: | - pip install --upgrade -r requirements.txt + pip install -r requirements.txt pip install pytest numpy # for tests - name: Build and Install diff --git a/requirements.txt b/requirements.txt index 8e911ef5a..33f38cca7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,8 +8,7 @@ codecov wheel pycparser -setuptools -clr-loader +clr-loader==0.2.* # Discover libpython -find_libpython \ No newline at end of file +find_libpython==0.3.* From a0418119b452e29a3e9f025fbed67fe19412fbee Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 18 Jan 2023 18:13:47 +0100 Subject: [PATCH 028/194] Ensure that `load` only runs once (#2079) Fixes #2078. --- CHANGELOG.md | 3 ++- pythonnet/__init__.py | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2347895a8..94a85e094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Fixed -- Fixed error occuring when inheriting a class containing a virtual generic method. +- Fixed error occuring when inheriting a class containing a virtual generic method. +- Make a second call to `pythonnet.load` a no-op, as it was intended. ## [3.0.1](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.1) - 2022-11-03 diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py index 9d7b6c20a..5c1ca108a 100644 --- a/pythonnet/__init__.py +++ b/pythonnet/__init__.py @@ -120,7 +120,9 @@ def load(runtime: Union[clr_loader.Runtime, str, None] = None, **params: str) -> The same parameters as for `set_runtime` can be used. By default, `set_default_runtime` is called if no environment has been set yet and no - parameters are passed.""" + parameters are passed. + + After a successful call, further invocations will return immediately.""" global _LOADED, _LOADER_ASSEMBLY if _LOADED: @@ -142,6 +144,8 @@ def load(runtime: Union[clr_loader.Runtime, str, None] = None, **params: str) -> if func(b"") != 0: raise RuntimeError("Failed to initialize Python.Runtime.dll") + + _LOADED = True import atexit From c2fa4677035ef51d883be14aea9e41acfef281ea Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 29 Jan 2023 15:00:14 +0100 Subject: [PATCH 029/194] Remove suggestions to use `internal` functions (#2092) Fixes #2091 --- doc/source/dotnet.rst | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/doc/source/dotnet.rst b/doc/source/dotnet.rst index d02a6f0cb..03d729ee5 100644 --- a/doc/source/dotnet.rst +++ b/doc/source/dotnet.rst @@ -42,16 +42,10 @@ application. Before interacting with any of the objects or APIs provided by the ``Python.Runtime`` namespace, calling code must have acquired the Python -global interpreter lock by calling the ``PythonEngine.AcquireLock`` -method. The only exception to this rule is the -``PythonEngine.Initialize`` method, which may be called at startup -without having acquired the GIL. - -When finished using Python APIs, managed code must call a corresponding -``PythonEngine.ReleaseLock`` to release the GIL and allow other threads -to use Python. - -A ``using`` statement may be used to acquire and release the GIL: +global interpreter lock by ``using'' ``Py.GIL()``. The only exception to +this rule is the ``PythonEngine.Initialize`` method, which may be called +at startup without having acquired the GIL. The GIL is released again +by disposing the return value of `Py.GIL()`: .. code:: csharp @@ -59,11 +53,28 @@ A ``using`` statement may be used to acquire and release the GIL: { PythonEngine.Exec("doStuff()"); } + + // or + { + using var _ = Py.GIL() + PythonEngine.Exec("doStuff()"); + } + + // or + var gil = Py.GIL(); + try + { + PythonEngine.Exec("doStuff()"); + } + finally + { + gil.Dispose(); + } -The AcquireLock and ReleaseLock methods are thin wrappers over the -unmanaged ``PyGILState_Ensure`` and ``PyGILState_Release`` functions -from the Python API, and the documentation for those APIs applies to the -managed versions. +The ``Py.GIL()'' object is a thin wrapper over the unmanaged +``PyGILState_Ensure`` (on construction) and ``PyGILState_Release`` (on +disposal) functions from the Python API, and the documentation for those +APIs applies to the managed versions. Passing C# Objects to the Python Engine --------------------------------------- From a404d6e4d2ef6182763bd626ab08e0de4400e621 Mon Sep 17 00:00:00 2001 From: Rolf Madsen Date: Fri, 3 Feb 2023 23:59:20 +0100 Subject: [PATCH 030/194] 1783 Implement Interface And Inherit Class (#2028) * 1783 Implement Interface And Inherit Class Added support for multiple inheritance when inheriting from one base class and/or multiple interfaces. Added a unit test verifying that it works with a simple class and interface. This unit test would previously have failed since multiple types are in the class super class list. --- CHANGELOG.md | 2 + src/python_tests_runner/PythonTestRunner.cs | 1 + src/runtime/StateSerialization/MaybeType.cs | 4 +- src/runtime/TypeManager.cs | 17 ++-- src/runtime/Types/ClassDerived.cs | 10 ++- src/runtime/Types/MetaType.cs | 90 +++++++++++++++------ src/runtime/Types/ReflectedClrType.cs | 3 +- src/testing/classtest.cs | 12 +++ tests/test_subclass.py | 9 ++- 9 files changed, 105 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94a85e094..818d90c52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Fixed error occuring when inheriting a class containing a virtual generic method. - Make a second call to `pythonnet.load` a no-op, as it was intended. +- Added support for multiple inheritance when inheriting from a class and/or multiple interfaces. + ## [3.0.1](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.1) - 2022-11-03 ### Added diff --git a/src/python_tests_runner/PythonTestRunner.cs b/src/python_tests_runner/PythonTestRunner.cs index 05298997b..f97cc5aec 100644 --- a/src/python_tests_runner/PythonTestRunner.cs +++ b/src/python_tests_runner/PythonTestRunner.cs @@ -35,6 +35,7 @@ static IEnumerable PythonTestCases() // Add the test that you want to debug here. yield return new[] { "test_indexer", "test_boolean_indexer" }; yield return new[] { "test_delegate", "test_bool_delegate" }; + yield return new[] { "test_subclass", "test_implement_interface_and_class" }; } /// diff --git a/src/runtime/StateSerialization/MaybeType.cs b/src/runtime/StateSerialization/MaybeType.cs index f3c96e369..884b7edb0 100644 --- a/src/runtime/StateSerialization/MaybeType.cs +++ b/src/runtime/StateSerialization/MaybeType.cs @@ -15,7 +15,7 @@ internal struct MaybeType : ISerializable const string SerializationName = "n"; readonly string name; readonly Type type; - + public string DeletedMessage { get @@ -61,4 +61,4 @@ public void GetObjectData(SerializationInfo serializationInfo, StreamingContext serializationInfo.AddValue(SerializationName, name); } } -} \ No newline at end of file +} diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index e0a78ba49..559d5148e 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -374,7 +374,7 @@ static PyTuple GetBaseTypeTuple(Type clrType) return new PyTuple(bases); } - internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedReference py_base_type, BorrowedReference dictRef) + internal static NewReference CreateSubType(BorrowedReference py_name, ClassBase py_base_type, IList interfaces, BorrowedReference dictRef) { // Utility to create a subtype of a managed type with the ability for the // a python subtype able to override the managed implementation @@ -415,17 +415,10 @@ internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedRe } // create the new managed type subclassing the base managed type - if (ManagedType.GetManagedObject(py_base_type) is ClassBase baseClass) - { - return ReflectedClrType.CreateSubclass(baseClass, name, - ns: (string?)namespaceStr, - assembly: (string?)assembly, - dict: dictRef); - } - else - { - return Exceptions.RaiseTypeError("invalid base class, expected CLR class type"); - } + return ReflectedClrType.CreateSubclass(py_base_type, interfaces, name, + ns: (string?)namespaceStr, + assembly: (string?)assembly, + dict: dictRef); } internal static IntPtr WriteMethodDef(IntPtr mdef, IntPtr name, IntPtr func, PyMethodFlags flags, IntPtr doc) diff --git a/src/runtime/Types/ClassDerived.cs b/src/runtime/Types/ClassDerived.cs index 61c602783..592eefd55 100644 --- a/src/runtime/Types/ClassDerived.cs +++ b/src/runtime/Types/ClassDerived.cs @@ -144,6 +144,7 @@ internal static NewReference ToPython(IPythonDerivedType obj) /// internal static Type CreateDerivedType(string name, Type baseType, + IList typeInterfaces, BorrowedReference py_dict, string? namespaceStr, string? assemblyName, @@ -163,7 +164,9 @@ internal static Type CreateDerivedType(string name, ModuleBuilder moduleBuilder = GetModuleBuilder(assemblyName, moduleName); Type baseClass = baseType; - var interfaces = new List { typeof(IPythonDerivedType) }; + var interfaces = new HashSet { typeof(IPythonDerivedType) }; + foreach(var interfaceType in typeInterfaces) + interfaces.Add(interfaceType); // if the base type is an interface then use System.Object as the base class // and add the base type to the list of interfaces this new class will implement. @@ -214,8 +217,9 @@ internal static Type CreateDerivedType(string name, } } - // override any virtual methods not already overridden by the properties above - MethodInfo[] methods = baseType.GetMethods(); + // override any virtual not already overridden by the properties above + // also override any interface method. + var methods = baseType.GetMethods().Concat(interfaces.SelectMany(x => x.GetMethods())); var virtualMethods = new HashSet(); foreach (MethodInfo method in methods) { diff --git a/src/runtime/Types/MetaType.cs b/src/runtime/Types/MetaType.cs index 5b59f5139..57fcaa232 100644 --- a/src/runtime/Types/MetaType.cs +++ b/src/runtime/Types/MetaType.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; +using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Serialization; @@ -79,41 +82,80 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, BorrowedReference bases = Runtime.PyTuple_GetItem(args, 1); BorrowedReference dict = Runtime.PyTuple_GetItem(args, 2); - // We do not support multiple inheritance, so the bases argument - // should be a 1-item tuple containing the type we are subtyping. - // That type must itself have a managed implementation. We check - // that by making sure its metatype is the CLR metatype. + // Extract interface types and base class types. + var interfaces = new List(); - if (Runtime.PyTuple_Size(bases) != 1) - { - return Exceptions.RaiseTypeError("cannot use multiple inheritance with managed classes"); - } + // More than one base type case be declared, but an exception will be thrown + // if more than one is a class/not an interface. + var baseTypes = new List(); - BorrowedReference base_type = Runtime.PyTuple_GetItem(bases, 0); - BorrowedReference mt = Runtime.PyObject_TYPE(base_type); - - if (!(mt == PyCLRMetaType || mt == Runtime.PyTypeType)) + var baseClassCount = Runtime.PyTuple_Size(bases); + if (baseClassCount == 0) { - return Exceptions.RaiseTypeError("invalid metatype"); + return Exceptions.RaiseTypeError("zero base classes "); } - // Ensure that the reflected type is appropriate for subclassing, - // disallowing subclassing of delegates, enums and array types. - - if (GetManagedObject(base_type) is ClassBase cb) + for (nint i = 0; i < baseClassCount; i++) { - try + var baseTypeIt = Runtime.PyTuple_GetItem(bases, (int)i); + + if (GetManagedObject(baseTypeIt) is ClassBase classBaseIt) { - if (!cb.CanSubclass()) + if (!classBaseIt.type.Valid) + { + return Exceptions.RaiseTypeError("Invalid type used as a super type."); + } + if (classBaseIt.type.Value.IsInterface) { - return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed"); + interfaces.Add(classBaseIt.type.Value); } + else + { + baseTypes.Add(classBaseIt); + } + } + else + { + return Exceptions.RaiseTypeError("Non .NET type used as super class for meta type. This is not supported."); } - catch (SerializationException) + } + // if the base type count is 0, there might still be interfaces to implement. + if (baseTypes.Count == 0) + { + baseTypes.Add(new ClassBase(typeof(object))); + } + + // Multiple inheritance is not supported, unless the other types are interfaces + if (baseTypes.Count > 1) + { + var types = string.Join(", ", baseTypes.Select(baseType => baseType.type.Value)); + return Exceptions.RaiseTypeError($"Multiple inheritance with managed classes cannot be used. Types: {types} "); + } + + // check if the list of interfaces contains no duplicates. + if (interfaces.Distinct().Count() != interfaces.Count) + { + // generate a string containing the problematic types. + var duplicateTypes = interfaces.GroupBy(type => type) + .Where(typeGroup => typeGroup.Count() > 1) + .Select(typeGroup => typeGroup.Key); + var duplicateTypesString = string.Join(", ", duplicateTypes); + + return Exceptions.RaiseTypeError($"An interface can only be implemented once. Duplicate types: {duplicateTypesString}"); + } + + var cb = baseTypes[0]; + try + { + if (!cb.CanSubclass()) { - return Exceptions.RaiseTypeError($"Underlying C# Base class {cb.type} has been deleted"); + return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed"); } } + catch (SerializationException) + { + return Exceptions.RaiseTypeError($"Underlying C# Base class {cb.type} has been deleted"); + } BorrowedReference slots = Runtime.PyDict_GetItem(dict, PyIdentifier.__slots__); if (slots != null) @@ -130,10 +172,12 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, using var clsDict = new PyDict(dict); if (clsDict.HasKey("__assembly__") || clsDict.HasKey("__namespace__")) { - return TypeManager.CreateSubType(name, base_type, clsDict); + return TypeManager.CreateSubType(name, baseTypes[0], interfaces, clsDict); } } + var base_type = Runtime.PyTuple_GetItem(bases, 0); + // otherwise just create a basic type without reflecting back into the managed side. IntPtr func = Util.ReadIntPtr(Runtime.PyTypeType, TypeOffset.tp_new); NewReference type = NativeCall.Call_3(func, tp, args, kw); diff --git a/src/runtime/Types/ReflectedClrType.cs b/src/runtime/Types/ReflectedClrType.cs index d3d89bdb8..3d0aa7e99 100644 --- a/src/runtime/Types/ReflectedClrType.cs +++ b/src/runtime/Types/ReflectedClrType.cs @@ -68,7 +68,7 @@ internal void Restore(ClassBase cb) TypeManager.InitializeClass(this, cb, cb.type.Value); } - internal static NewReference CreateSubclass(ClassBase baseClass, + internal static NewReference CreateSubclass(ClassBase baseClass, IList interfaces, string name, string? assembly, string? ns, BorrowedReference dict) { @@ -76,6 +76,7 @@ internal static NewReference CreateSubclass(ClassBase baseClass, { Type subType = ClassDerivedObject.CreateDerivedType(name, baseClass.type.Value, + interfaces, dict, ns, assembly); diff --git a/src/testing/classtest.cs b/src/testing/classtest.cs index 68c0d8c55..993afdfc9 100644 --- a/src/testing/classtest.cs +++ b/src/testing/classtest.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; namespace Python.Test @@ -59,4 +60,15 @@ public ClassCtorTest2(string v) internal class InternalClass { } + + public class SimpleClass + { + public static void TestObject(object obj) + { + if ((!(obj is ISayHello1 && obj is SimpleClass))) + { + throw new Exception("Expected ISayHello and SimpleClass instance"); + } + } + } } diff --git a/tests/test_subclass.py b/tests/test_subclass.py index 504b82548..c6ab7650f 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -9,7 +9,7 @@ import System import pytest from Python.Test import (IInterfaceTest, SubClassTest, EventArgsTest, - FunctionsTest, IGenericInterface, GenericVirtualMethodTest) + FunctionsTest, IGenericInterface, GenericVirtualMethodTest, SimpleClass, ISayHello1) from System.Collections.Generic import List @@ -338,4 +338,9 @@ class OverloadingSubclass2(OverloadingSubclass): obj = OverloadingSubclass2() assert obj.VirtMethod[int](5) == 5 - +def test_implement_interface_and_class(): + class DualSubClass0(ISayHello1, SimpleClass): + __namespace__ = "Test" + def SayHello(self): + return "hello" + obj = DualSubClass0() From 131b466dcaa9cf9135f3b1859c8a41ee229cace8 Mon Sep 17 00:00:00 2001 From: legomanww Date: Sun, 12 Mar 2023 14:01:52 -0700 Subject: [PATCH 031/194] Fix `GetBuffer` throwing `ArgumentOutOfRangeException` (#2120) * fix: incorrect length for buffer copy * test: add PyBuffer test for strides * docs: update `CHANGELOG` and `AUTHORS` --- AUTHORS.md | 1 + CHANGELOG.md | 1 + src/embed_tests/TestPyBuffer.cs | 36 +++++++++++++++++++++++++++++ src/runtime/PythonTypes/PyBuffer.cs | 8 +++---- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 92f1a4a97..577e898aa 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -85,3 +85,4 @@ - ([@alxnull](https://github.com/alxnull)) - ([@gpetrou](https://github.com/gpetrou)) - Ehsan Iran-Nejad ([@eirannejad](https://github.com/eirannejad)) +- ([@legomanww](https://github.com/legomanww)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 818d90c52..b5a46ce04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Make a second call to `pythonnet.load` a no-op, as it was intended. - Added support for multiple inheritance when inheriting from a class and/or multiple interfaces. +- Fixed error occuring when calling `GetBuffer` for anything other than `PyBUF.SIMPLE` ## [3.0.1](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.1) - 2022-11-03 diff --git a/src/embed_tests/TestPyBuffer.cs b/src/embed_tests/TestPyBuffer.cs index a1bcc161d..1b4e28d12 100644 --- a/src/embed_tests/TestPyBuffer.cs +++ b/src/embed_tests/TestPyBuffer.cs @@ -110,6 +110,26 @@ public void Finalization() Assert.AreEqual(1, arr.Refcount); } + [Test] + public void MultidimensionalNumPyArray() + { + var ndarray = np.arange(24).reshape(1,2,3,4).T; + PyObject ndim = ndarray.ndim; + PyObject shape = ndarray.shape; + PyObject strides = ndarray.strides; + PyObject contiguous = ndarray.flags["C_CONTIGUOUS"]; + + using PyBuffer buf = ndarray.GetBuffer(PyBUF.STRIDED); + + Assert.Multiple(() => + { + Assert.That(buf.Dimensions, Is.EqualTo(ndim.As())); + Assert.That(buf.Shape, Is.EqualTo(shape.As())); + Assert.That(buf.Strides, Is.EqualTo(strides.As())); + Assert.That(buf.IsContiguous(BufferOrderStyle.C), Is.EqualTo(contiguous.As())); + }); + } + [MethodImpl(MethodImplOptions.NoInlining)] static void MakeBufAndLeak(PyObject bufProvider) { @@ -121,5 +141,21 @@ static PyObject ByteArrayFromAsciiString(string str) using var scope = Py.CreateScope(); return Runtime.Runtime.PyByteArray_FromStringAndSize(str).MoveToPyObject(); } + + dynamic np + { + get + { + try + { + return Py.Import("numpy"); + } + catch (PythonException) + { + Assert.Inconclusive("Numpy or dependency not installed"); + return null; + } + } + } } } diff --git a/src/runtime/PythonTypes/PyBuffer.cs b/src/runtime/PythonTypes/PyBuffer.cs index de0e7122f..120582494 100644 --- a/src/runtime/PythonTypes/PyBuffer.cs +++ b/src/runtime/PythonTypes/PyBuffer.cs @@ -11,7 +11,7 @@ public sealed class PyBuffer : IDisposable private PyObject _exporter; private Py_buffer _view; - unsafe internal PyBuffer(PyObject exporter, PyBUF flags) + internal PyBuffer(PyObject exporter, PyBUF flags) { _view = new Py_buffer(); @@ -25,17 +25,17 @@ unsafe internal PyBuffer(PyObject exporter, PyBUF flags) var intPtrBuf = new IntPtr[_view.ndim]; if (_view.shape != IntPtr.Zero) { - Marshal.Copy(_view.shape, intPtrBuf, 0, (int)_view.len * sizeof(IntPtr)); + Marshal.Copy(_view.shape, intPtrBuf, 0, _view.ndim); Shape = intPtrBuf.Select(x => (long)x).ToArray(); } if (_view.strides != IntPtr.Zero) { - Marshal.Copy(_view.strides, intPtrBuf, 0, (int)_view.len * sizeof(IntPtr)); + Marshal.Copy(_view.strides, intPtrBuf, 0, _view.ndim); Strides = intPtrBuf.Select(x => (long)x).ToArray(); } if (_view.suboffsets != IntPtr.Zero) { - Marshal.Copy(_view.suboffsets, intPtrBuf, 0, (int)_view.len * sizeof(IntPtr)); + Marshal.Copy(_view.suboffsets, intPtrBuf, 0, _view.ndim); SubOffsets = intPtrBuf.Select(x => (long)x).ToArray(); } } From a1cd73fa5bd93962737449feea3bd425f37c42ba Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 4 May 2023 08:29:39 +0200 Subject: [PATCH 032/194] Exclude .gitignore from binary distribution --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 52f1adb18..98a3b26fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ file = "version.txt" [tool.setuptools.packages.find] include = ["pythonnet*"] +exclude = [".gitignore"] [tool.pytest.ini_options] xfail_strict = true From dddadbe78ef0fba3e8ffde30c466f224dbbc9aea Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 7 Jul 2023 17:39:37 +0200 Subject: [PATCH 033/194] Adjust docs for importing --- doc/source/dotnet.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/dotnet.rst b/doc/source/dotnet.rst index 03d729ee5..43b7659ac 100644 --- a/doc/source/dotnet.rst +++ b/doc/source/dotnet.rst @@ -17,7 +17,7 @@ to: - Reference ``Python.Runtime.dll`` (e.g. via a ``PackageReference``) - Call ``PythonEngine.Initialize()`` to initialize Python -- Call ``PythonEngine.ImportModule(name)`` to import a module +- Call ``var mod = PyModule.Import(name)`` to import a module as ``mod`` The module you import can either start working with your managed app environment at the time its imported, or you can explicitly lookup and From 3b6500fbdc844096665a24ba07f5a56c11192e1b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 21 Jul 2023 08:47:21 +0200 Subject: [PATCH 034/194] Add documentation dependency to fix empty docs --- doc/requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/requirements.txt b/doc/requirements.txt index 64018840c..8ef3b7159 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -7,3 +7,6 @@ pygments>=2.7 # C# via doxygen breathe git+https://github.com/rogerbarton/sphinx-csharp.git + +# Dependency of pythonnet, needed for autodocs +clr_loader From 639023a5ca0a3375fd39d2fea8baa5e9888284d0 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 29 Aug 2023 17:18:19 +0200 Subject: [PATCH 035/194] Bump clr_loader dependency --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 98a3b26fd..bf488bb92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ license = {text = "MIT"} readme = "README.rst" dependencies = [ - "clr_loader>=0.2.2,<0.3.0" + "clr_loader>=0.2.6,<0.3.0" ] requires-python = ">=3.7, <3.12" From 6a4e04c1983b24888a1e8b04d75c28086594cb63 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 29 Aug 2023 17:22:48 +0200 Subject: [PATCH 036/194] Release 3.0.2 --- CHANGELOG.md | 12 ++++++++---- version.txt | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5a46ce04..4ba0b935c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,15 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Fixed -- Fixed error occuring when inheriting a class containing a virtual generic method. -- Make a second call to `pythonnet.load` a no-op, as it was intended. +## [3.0.2](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.2) - 2023-08-29 -- Added support for multiple inheritance when inheriting from a class and/or multiple interfaces. -- Fixed error occuring when calling `GetBuffer` for anything other than `PyBUF.SIMPLE` +### Fixed + +- Fixed error occuring when inheriting a class containing a virtual generic method +- Make a second call to `pythonnet.load` a no-op, as it was intended +- Added support for multiple inheritance when inheriting from a class and/or multiple interfaces +- Fixed error occuring when calling `GetBuffer` for anything other than `PyBUF.SIMPLE` +- Bumped `clr_loader` dependency to incorporate patches ## [3.0.1](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.1) - 2022-11-03 diff --git a/version.txt b/version.txt index 0f9d6b15d..b50214693 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.0-dev +3.0.2 From c6f12cf91dc2bb2110e102ae468d2fb002790217 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 29 Aug 2023 17:23:31 +0200 Subject: [PATCH 037/194] Reset version to dev --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index b50214693..0f9d6b15d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.2 +3.1.0-dev From 171cee4495f3498b1ff57f3d5290b16d52a95432 Mon Sep 17 00:00:00 2001 From: Omkar Borhade <91014820+OmkarBorhade98@users.noreply.github.com> Date: Sun, 3 Sep 2023 05:10:49 +0530 Subject: [PATCH 038/194] Change PyScope to PyModule in docs (#2231) PyScope data type not found. Py.CreateScope() returns the variable of type PyModule. --- doc/source/dotnet.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/dotnet.rst b/doc/source/dotnet.rst index 43b7659ac..ed2b741a7 100644 --- a/doc/source/dotnet.rst +++ b/doc/source/dotnet.rst @@ -110,7 +110,7 @@ Code executed from the scope will have access to the variable: using (Py.GIL()) { // create a Python scope - using (PyScope scope = Py.CreateScope()) + using (PyModule scope = Py.CreateScope()) { // convert the Person object to a PyObject PyObject pyPerson = person.ToPython(); From 22d07ddff8f449d295078ecfebe00fe0ff6e4c74 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Fri, 22 Sep 2023 15:19:16 -0500 Subject: [PATCH 039/194] custom repr for enum values (#2239) Examples: --------- Co-authored-by: Mohamed Koubaa --- CHANGELOG.md | 2 ++ src/runtime/Types/ClassObject.cs | 51 ++++++++++++++++++++++++++++++++ tests/test_enum.py | 10 +++++++ 3 files changed, 63 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ba0b935c..fb13982e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Added +- use enum name in repr + ### Changed ### Fixed diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs index cc42039e8..f0585ffa6 100644 --- a/src/runtime/Types/ClassObject.cs +++ b/src/runtime/Types/ClassObject.cs @@ -1,7 +1,9 @@ using System; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Runtime.Serialization; namespace Python.Runtime @@ -47,6 +49,55 @@ internal NewReference GetDocString() return Runtime.PyString_FromString(str); } + private static string ConvertFlags(Enum value) + { + Type primitiveType = value.GetType().GetEnumUnderlyingType(); + string format = "X" + (Marshal.SizeOf(primitiveType) * 2).ToString(CultureInfo.InvariantCulture); + var primitive = (IFormattable)Convert.ChangeType(value, primitiveType); + return "0x" + primitive.ToString(format, null); + + } + + private static string ConvertValue(Enum value) + { + Type primitiveType = value.GetType().GetEnumUnderlyingType(); + return Convert.ChangeType(value, primitiveType).ToString()!; + } + + /// + /// given an enum, write a __repr__ string formatted in the same + /// way as a python repr string. Something like: + /// '<Color.GREEN: 2>'; + /// with a binary value for [Flags] enums + /// + /// Instace of the enum object + /// + private static string GetEnumReprString(Enum inst) + { + var obType = inst.GetType(); + + string strValue2 = obType.IsFlagsEnum() ? ConvertFlags(inst) : ConvertValue(inst); + + var repr = $"<{obType.Name}.{inst}: {strValue2}>"; + return repr; + } + + /// + /// ClassObject __repr__ implementation. + /// + public new static NewReference tp_repr(BorrowedReference ob) + { + if (GetManagedObject(ob) is not CLRObject co) + { + return Exceptions.RaiseTypeError("invalid object"); + } + if (co.inst.GetType().IsEnum) + { + return Runtime.PyString_FromString(GetEnumReprString((Enum)co.inst)); + } + + return ClassBase.tp_repr(ob); + } /// /// Implements __new__ for reflected classes and value types. diff --git a/tests/test_enum.py b/tests/test_enum.py index f24f95b36..3d3edba10 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -143,6 +143,16 @@ def test_enum_undefined_value(): Test.FieldTest().EnumField = Test.ShortEnum(20, True) +def test_enum_repr(): + """Test enumeration repr.""" + from System import DayOfWeek + + assert repr(DayOfWeek.Monday) == "" + + assert repr(Test.FlagsEnum(7)) == "" + assert repr(Test.FlagsEnum(8)) == "" + + def test_enum_conversion(): """Test enumeration conversion.""" ob = Test.FieldTest() From a9e757f316e48f9e754fb2368cf9caaf1a9f9880 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 3 Oct 2023 22:50:02 -0700 Subject: [PATCH 040/194] fixed ARM CI --- .github/workflows/ARM.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ARM.yml b/.github/workflows/ARM.yml index 0492b7a3a..eef0e666d 100644 --- a/.github/workflows/ARM.yml +++ b/.github/workflows/ARM.yml @@ -27,25 +27,25 @@ jobs: - name: Install dependencies run: | - pip install -r requirements.txt - pip install pytest numpy # for tests + pip3.8 install -r requirements.txt + pip3.8 install pytest numpy # for tests - name: Build and Install run: | - pip install -v . + pip3.8 install -v . - name: Set Python DLL path (non Windows) run: | - echo PYTHONNET_PYDLL=$(python -m find_libpython) >> $GITHUB_ENV + 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: python -m pytest --runtime mono + run: python3.8 -m pytest --runtime mono - name: Python Tests (.NET Core) - run: python -m pytest --runtime coreclr + run: python3.8 -m pytest --runtime coreclr - name: Python tests run from .NET run: dotnet test src/python_tests_runner/ From 6aa92c19facfacc48703c4d7d4e871be048ea209 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 27 Sep 2023 12:57:00 +0200 Subject: [PATCH 041/194] Add type offsets for 3.12 and update interop generation --- src/runtime/Native/TypeOffset312.cs | 144 ++++++++++++++++++++++++++++ tools/geninterop/geninterop.py | 14 ++- 2 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 src/runtime/Native/TypeOffset312.cs diff --git a/src/runtime/Native/TypeOffset312.cs b/src/runtime/Native/TypeOffset312.cs new file mode 100644 index 000000000..8ba30e816 --- /dev/null +++ b/src/runtime/Native/TypeOffset312.cs @@ -0,0 +1,144 @@ + +// Auto-generated by geninterop.py. +// DO NOT MODIFY BY HAND. + +// Python 3.12: ABI flags: '' + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +using Python.Runtime.Native; + +namespace Python.Runtime +{ + [SuppressMessage("Style", "IDE1006:Naming Styles", + Justification = "Following CPython", + Scope = "type")] + + [StructLayout(LayoutKind.Sequential)] + internal class TypeOffset312 : GeneratedTypeOffsets, ITypeOffsets + { + public TypeOffset312() { } + // Auto-generated from PyHeapTypeObject in Python.h + public int ob_refcnt { get; private set; } + public int ob_type { get; private set; } + public int ob_size { get; private set; } + public int tp_name { get; private set; } + public int tp_basicsize { get; private set; } + public int tp_itemsize { get; private set; } + public int tp_dealloc { get; private set; } + public int tp_vectorcall_offset { get; private set; } + public int tp_getattr { get; private set; } + public int tp_setattr { get; private set; } + public int tp_as_async { get; private set; } + public int tp_repr { get; private set; } + public int tp_as_number { get; private set; } + public int tp_as_sequence { get; private set; } + public int tp_as_mapping { get; private set; } + public int tp_hash { get; private set; } + public int tp_call { get; private set; } + public int tp_str { get; private set; } + public int tp_getattro { get; private set; } + public int tp_setattro { get; private set; } + public int tp_as_buffer { get; private set; } + public int tp_flags { get; private set; } + public int tp_doc { get; private set; } + public int tp_traverse { get; private set; } + public int tp_clear { get; private set; } + public int tp_richcompare { get; private set; } + public int tp_weaklistoffset { get; private set; } + public int tp_iter { get; private set; } + public int tp_iternext { get; private set; } + public int tp_methods { get; private set; } + public int tp_members { get; private set; } + public int tp_getset { get; private set; } + public int tp_base { get; private set; } + public int tp_dict { get; private set; } + public int tp_descr_get { get; private set; } + public int tp_descr_set { get; private set; } + public int tp_dictoffset { get; private set; } + public int tp_init { get; private set; } + public int tp_alloc { get; private set; } + public int tp_new { get; private set; } + public int tp_free { get; private set; } + public int tp_is_gc { get; private set; } + public int tp_bases { get; private set; } + public int tp_mro { get; private set; } + public int tp_cache { get; private set; } + public int tp_subclasses { get; private set; } + public int tp_weaklist { get; private set; } + public int tp_del { get; private set; } + public int tp_version_tag { get; private set; } + public int tp_finalize { get; private set; } + public int tp_vectorcall { get; private set; } + public int tp_watched { get; private set; } + public int am_await { get; private set; } + public int am_aiter { get; private set; } + public int am_anext { get; private set; } + public int am_send { get; private set; } + public int nb_add { get; private set; } + public int nb_subtract { get; private set; } + public int nb_multiply { get; private set; } + public int nb_remainder { get; private set; } + public int nb_divmod { get; private set; } + public int nb_power { get; private set; } + public int nb_negative { get; private set; } + public int nb_positive { get; private set; } + public int nb_absolute { get; private set; } + public int nb_bool { get; private set; } + public int nb_invert { get; private set; } + public int nb_lshift { get; private set; } + public int nb_rshift { get; private set; } + public int nb_and { get; private set; } + public int nb_xor { get; private set; } + public int nb_or { get; private set; } + public int nb_int { get; private set; } + public int nb_reserved { get; private set; } + public int nb_float { get; private set; } + public int nb_inplace_add { get; private set; } + public int nb_inplace_subtract { get; private set; } + public int nb_inplace_multiply { get; private set; } + public int nb_inplace_remainder { get; private set; } + public int nb_inplace_power { get; private set; } + public int nb_inplace_lshift { get; private set; } + public int nb_inplace_rshift { get; private set; } + public int nb_inplace_and { get; private set; } + public int nb_inplace_xor { get; private set; } + public int nb_inplace_or { get; private set; } + public int nb_floor_divide { get; private set; } + public int nb_true_divide { get; private set; } + public int nb_inplace_floor_divide { get; private set; } + public int nb_inplace_true_divide { get; private set; } + public int nb_index { get; private set; } + public int nb_matrix_multiply { get; private set; } + public int nb_inplace_matrix_multiply { get; private set; } + public int mp_length { get; private set; } + public int mp_subscript { get; private set; } + public int mp_ass_subscript { get; private set; } + public int sq_length { get; private set; } + public int sq_concat { get; private set; } + public int sq_repeat { get; private set; } + public int sq_item { get; private set; } + public int was_sq_slice { get; private set; } + public int sq_ass_item { get; private set; } + public int was_sq_ass_slice { get; private set; } + public int sq_contains { get; private set; } + public int sq_inplace_concat { get; private set; } + public int sq_inplace_repeat { get; private set; } + public int bf_getbuffer { get; private set; } + public int bf_releasebuffer { get; private set; } + public int name { get; private set; } + public int ht_slots { get; private set; } + public int qualname { get; private set; } + public int ht_cached_keys { get; private set; } + public int ht_module { get; private set; } + public int _ht_tpname { get; private set; } + public int spec_cache_getitem { get; private set; } + public int getitem_version { get; private set; } + } +} + diff --git a/tools/geninterop/geninterop.py b/tools/geninterop/geninterop.py index 78e4d45c2..6d80bcfa6 100755 --- a/tools/geninterop/geninterop.py +++ b/tools/geninterop/geninterop.py @@ -76,6 +76,8 @@ def visit(self, node): self.visit_ptrdecl(node) elif isinstance(node, c_ast.IdentifierType): self.visit_identifier(node) + elif isinstance(node, c_ast.Union): + self.visit_union(node) def visit_ast(self, ast): for _name, node in ast.children(): @@ -119,6 +121,16 @@ def visit_identifier(self, identifier): type_name = " ".join(identifier.names) self._add_struct_member(type_name) + def visit_union(self, union): + # Treat the field as if it was just the first declaration for now. This + # is not really correct, but handles the one case that is relevant for + # us right now (ob_refcnt being "split" in Python 3.12) + if self._struct_members_stack and union.decls: + decl = union.decls[0] + self._struct_members_stack.pop(0) + self._struct_members_stack.insert(0, decl.name) + self.visit(decl) + def _add_struct_member(self, type_name): if not (self._struct_stack and self._struct_members_stack): return @@ -245,7 +257,7 @@ def gen_interop_head(writer, version, abi_flags): // Auto-generated by {filename}. // DO NOT MODIFY BY HAND. -// Python {".".join(version[:2])}: ABI flags: '{abi_flags}' +// Python {".".join(map(str, version[:2]))}: ABI flags: '{abi_flags}' // ReSharper disable InconsistentNaming // ReSharper disable IdentifierTypo From 93d4119205a97d3631896e2163f145cd28b24c20 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 27 Sep 2023 12:57:18 +0200 Subject: [PATCH 042/194] Drop unused custom incref/decref --- src/runtime/Runtime.cs | 46 ------------------------------------------ 1 file changed, 46 deletions(-) diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index beb577e45..c7a954885 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -598,23 +598,8 @@ internal static void CheckExceptionOccurred() [Obsolete("Use NewReference or PyObject constructor instead")] internal static unsafe void XIncref(BorrowedReference op) { -#if !CUSTOM_INCDEC_REF Py_IncRef(op); return; -#else - var p = (void*)op; - if ((void*)0 != p) - { - if (Is32Bit) - { - (*(int*)p)++; - } - else - { - (*(long*)p)++; - } - } -#endif } internal static unsafe void XDecref(StolenReference op) @@ -623,40 +608,9 @@ internal static unsafe void XDecref(StolenReference op) Debug.Assert(op == null || Refcount(new BorrowedReference(op.Pointer)) > 0); Debug.Assert(_isInitialized || Py_IsInitialized() != 0 || _Py_IsFinalizing() != false); #endif -#if !CUSTOM_INCDEC_REF if (op == null) return; Py_DecRef(op.AnalyzerWorkaround()); return; -#else - var p = (void*)op; - if ((void*)0 != p) - { - if (Is32Bit) - { - --(*(int*)p); - } - else - { - --(*(long*)p); - } - if ((*(int*)p) == 0) - { - // PyObject_HEAD: struct _typeobject *ob_type - void* t = Is32Bit - ? (void*)(*((uint*)p + 1)) - : (void*)(*((ulong*)p + 1)); - // PyTypeObject: destructor tp_dealloc - void* f = Is32Bit - ? (void*)(*((uint*)t + 6)) - : (void*)(*((ulong*)t + 6)); - if ((void*)0 == f) - { - return; - } - NativeCall.Void_Call_1(new IntPtr(f), op); - } - } -#endif } [Pure] From 7a31d38755c71d0385bf28e758ab9cf96a2db481 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 27 Sep 2023 13:00:47 +0200 Subject: [PATCH 043/194] Add 3.12 to CI and metadata --- .github/workflows/main.yml | 2 +- pyproject.toml | 3 ++- src/runtime/PythonEngine.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 93963c70a..664dac9e6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ jobs: fail-fast: false matrix: os: [windows, ubuntu, macos] - python: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12.0-rc.3"] platform: [x64, x86] exclude: - os: ubuntu diff --git a/pyproject.toml b/pyproject.toml index bf488bb92..4ece5f3a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "clr_loader>=0.2.6,<0.3.0" ] -requires-python = ">=3.7, <3.12" +requires-python = ">=3.7, <3.13" classifiers = [ "Development Status :: 5 - Production/Stable", @@ -26,6 +26,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Operating System :: MacOS :: MacOS X", diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index 4ed45b9e9..2c4c6c088 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, 11, int.MaxValue, int.MaxValue); + public static Version MaxSupportedVersion => new(3, 12, int.MaxValue, int.MaxValue); public static bool IsSupportedVersion(Version version) => version >= MinSupportedVersion && version <= MaxSupportedVersion; public static string Version From d057724e5167d7088a3dc77310a37c38473e318d Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 27 Sep 2023 13:01:59 +0200 Subject: [PATCH 044/194] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb13982e6..753d151c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Added -- use enum name in repr +- Use enum name in `repr` +- Support for Python 3.12 ### Changed From 8dfe4080d642397a7efcb52b8d3aa69da1675713 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 27 Sep 2023 14:09:44 +0200 Subject: [PATCH 045/194] Remove deprecated function call --- src/runtime/Converter.cs | 6 ++---- src/runtime/Runtime.Delegates.cs | 4 ++-- src/runtime/Runtime.cs | 3 ++- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index 73bbd4a3a..412f3b711 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -686,10 +686,8 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec { if (Runtime.PyUnicode_GetLength(value) == 1) { - IntPtr unicodePtr = Runtime.PyUnicode_AsUnicode(value); - Char[] buff = new Char[1]; - Marshal.Copy(unicodePtr, buff, 0, 1); - result = buff[0]; + int chr = Runtime.PyUnicode_ReadChar(value, 0); + result = (Char)chr; return true; } goto type_error; diff --git a/src/runtime/Runtime.Delegates.cs b/src/runtime/Runtime.Delegates.cs index 0b6b75872..6490c3fe5 100644 --- a/src/runtime/Runtime.Delegates.cs +++ b/src/runtime/Runtime.Delegates.cs @@ -164,8 +164,8 @@ static Delegates() PyUnicode_AsUTF8 = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_AsUTF8), GetUnmanagedDll(_PythonDll)); PyUnicode_DecodeUTF16 = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_DecodeUTF16), GetUnmanagedDll(_PythonDll)); PyUnicode_GetLength = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_GetLength), GetUnmanagedDll(_PythonDll)); - PyUnicode_AsUnicode = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_AsUnicode), GetUnmanagedDll(_PythonDll)); PyUnicode_AsUTF16String = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_AsUTF16String), GetUnmanagedDll(_PythonDll)); + PyUnicode_ReadChar = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_ReadChar), GetUnmanagedDll(_PythonDll)); PyUnicode_FromOrdinal = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_FromOrdinal), GetUnmanagedDll(_PythonDll)); PyUnicode_InternFromString = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_InternFromString), GetUnmanagedDll(_PythonDll)); PyUnicode_Compare = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyUnicode_Compare), GetUnmanagedDll(_PythonDll)); @@ -441,7 +441,7 @@ static Delegates() internal static delegate* unmanaged[Cdecl] PyUnicode_AsUTF8 { get; } internal static delegate* unmanaged[Cdecl] PyUnicode_DecodeUTF16 { get; } internal static delegate* unmanaged[Cdecl] PyUnicode_GetLength { get; } - internal static delegate* unmanaged[Cdecl] PyUnicode_AsUnicode { get; } + internal static delegate* unmanaged[Cdecl] PyUnicode_ReadChar { get; } internal static delegate* unmanaged[Cdecl] PyUnicode_AsUTF16String { get; } internal static delegate* unmanaged[Cdecl] PyUnicode_FromOrdinal { get; } internal static delegate* unmanaged[Cdecl] PyUnicode_InternFromString { get; } diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index c7a954885..eafe7f72c 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -1290,9 +1290,10 @@ internal static IntPtr PyBytes_AsString(BorrowedReference ob) internal static nint PyUnicode_GetLength(BorrowedReference ob) => Delegates.PyUnicode_GetLength(ob); - internal static IntPtr PyUnicode_AsUnicode(BorrowedReference ob) => Delegates.PyUnicode_AsUnicode(ob); internal static NewReference PyUnicode_AsUTF16String(BorrowedReference ob) => Delegates.PyUnicode_AsUTF16String(ob); + internal static int PyUnicode_ReadChar(BorrowedReference ob, nint index) => Delegates.PyUnicode_ReadChar(ob, index); + internal static NewReference PyUnicode_FromOrdinal(int c) => Delegates.PyUnicode_FromOrdinal(c); From 080d1bd59f2b231db2081474cf126e46aa039505 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 27 Sep 2023 16:26:39 +0200 Subject: [PATCH 046/194] For now skip over "new style" weakrefs in clear --- src/runtime/Runtime.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index eafe7f72c..4e1c6156a 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -939,7 +939,7 @@ internal static BorrowedReference PyObject_GetWeakRefList(BorrowedReference ob) Debug.Assert(ob != null); var type = PyObject_TYPE(ob); int offset = Util.ReadInt32(type, TypeOffset.tp_weaklistoffset); - if (offset == 0) return BorrowedReference.Null; + if (offset <= 0) return BorrowedReference.Null; Debug.Assert(offset > 0); return Util.ReadRef(ob, offset); } From fb494705652af998a4f346aeb88ebff836c72eb4 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 2 Oct 2023 21:06:16 +0000 Subject: [PATCH 047/194] Ignore test-case on Python 3.12 --- src/embed_tests/Codecs.cs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index 9b764d43f..c8b8ecb6e 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -371,11 +371,23 @@ public void FloatDerivedDecoded() [Test] public void ExceptionDecodedNoInstance() { - PyObjectConversions.RegisterDecoder(new InstancelessExceptionDecoder()); - using var scope = Py.CreateScope(); - var error = Assert.Throws(() => PythonEngine.Exec( - $"[].__iter__().__next__()")); - Assert.AreEqual(TestExceptionMessage, error.Message); + if (Runtime.PyVersion < new Version(3, 12)) + { + PyObjectConversions.RegisterDecoder(new InstancelessExceptionDecoder()); + using var scope = Py.CreateScope(); + + var error = Assert.Throws(() => + PythonEngine.Exec($"[].__iter__().__next__()") + ); + Assert.AreEqual(TestExceptionMessage, error.Message); + } + else + { + Assert.Ignore( + "This test does not work for Python 3.12, see " + + "https://github.com/python/cpython/issues/101578" + ); + } } public static void AcceptsDateTime(DateTime v) {} From 293f8b1d810db1e63253b950a4b4844b35c38bab Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 3 Oct 2023 15:26:47 +0200 Subject: [PATCH 048/194] Python 3.12 has been released, use final version --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 664dac9e6..c4af10c68 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ jobs: fail-fast: false matrix: os: [windows, ubuntu, macos] - python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12.0-rc.3"] + python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] platform: [x64, x86] exclude: - os: ubuntu From 04670ea1c8a761944e19ac89acd18a43a82cc352 Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 6 Oct 2023 03:57:00 -0700 Subject: [PATCH 049/194] only run docs CI/main CI for corresponding changes (#2257) --- .github/workflows/ARM.yml | 8 ++++++++ .github/workflows/docs.yml | 10 +++++++++- .github/workflows/main.yml | 8 ++++++++ pythonnet.sln | 1 + 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ARM.yml b/.github/workflows/ARM.yml index eef0e666d..d4a5cb008 100644 --- a/.github/workflows/ARM.yml +++ b/.github/workflows/ARM.yml @@ -4,7 +4,15 @@ on: push: branches: - master + paths-ignore: + - .github/workflows/main.yml + - .github/workflows/nuget-preview.yml + - 'doc/**' pull_request: + paths-ignore: + - .github/workflows/main.yml + - .github/workflows/nuget-preview.yml + - 'doc/**' jobs: build-test-arm: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5b782c8b4..65a6ef69c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,6 +1,14 @@ name: Documentation -on: [push, pull_request] +on: + push: + paths: + - 'doc/**' + - '.github/workflows/docs.yml' + pull_request: + paths: + - 'doc/**' + - '.github/workflows/docs.yml' jobs: build: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c4af10c68..17d7bb4b2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,7 +4,15 @@ on: push: branches: - master + paths-ignore: + - .github/workflows/ARM.yml + - .github/workflows/nuget-preview.yml + - 'doc/**' pull_request: + paths-ignore: + - .github/workflows/ARM.yml + - .github/workflows/nuget-preview.yml + - 'doc/**' jobs: build-test: diff --git a/pythonnet.sln b/pythonnet.sln index d1a47892e..ce45270e3 100644 --- a/pythonnet.sln +++ b/pythonnet.sln @@ -27,6 +27,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{D301657F-5EAF-4534-B280-B858D651B2E5}" ProjectSection(SolutionItems) = preProject .github\workflows\ARM.yml = .github\workflows\ARM.yml + .github\workflows\docs.yml = .github\workflows\docs.yml .github\workflows\main.yml = .github\workflows\main.yml .github\workflows\nuget-preview.yml = .github\workflows\nuget-preview.yml EndProjectSection From 5a4a986f9e2b9f4f505db68809f45a63c39b1418 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 6 Oct 2023 12:59:57 +0200 Subject: [PATCH 050/194] Revert "only run docs CI/main CI for corresponding changes (#2257)" (#2260) This reverts commit 04670ea1c8a761944e19ac89acd18a43a82cc352. --- .github/workflows/ARM.yml | 8 -------- .github/workflows/docs.yml | 10 +--------- .github/workflows/main.yml | 8 -------- pythonnet.sln | 1 - 4 files changed, 1 insertion(+), 26 deletions(-) diff --git a/.github/workflows/ARM.yml b/.github/workflows/ARM.yml index d4a5cb008..eef0e666d 100644 --- a/.github/workflows/ARM.yml +++ b/.github/workflows/ARM.yml @@ -4,15 +4,7 @@ on: push: branches: - master - paths-ignore: - - .github/workflows/main.yml - - .github/workflows/nuget-preview.yml - - 'doc/**' pull_request: - paths-ignore: - - .github/workflows/main.yml - - .github/workflows/nuget-preview.yml - - 'doc/**' jobs: build-test-arm: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 65a6ef69c..5b782c8b4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,14 +1,6 @@ name: Documentation -on: - push: - paths: - - 'doc/**' - - '.github/workflows/docs.yml' - pull_request: - paths: - - 'doc/**' - - '.github/workflows/docs.yml' +on: [push, pull_request] jobs: build: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 17d7bb4b2..c4af10c68 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,15 +4,7 @@ on: push: branches: - master - paths-ignore: - - .github/workflows/ARM.yml - - .github/workflows/nuget-preview.yml - - 'doc/**' pull_request: - paths-ignore: - - .github/workflows/ARM.yml - - .github/workflows/nuget-preview.yml - - 'doc/**' jobs: build-test: diff --git a/pythonnet.sln b/pythonnet.sln index ce45270e3..d1a47892e 100644 --- a/pythonnet.sln +++ b/pythonnet.sln @@ -27,7 +27,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{D301657F-5EAF-4534-B280-B858D651B2E5}" ProjectSection(SolutionItems) = preProject .github\workflows\ARM.yml = .github\workflows\ARM.yml - .github\workflows\docs.yml = .github\workflows\docs.yml .github\workflows\main.yml = .github\workflows\main.yml .github\workflows\nuget-preview.yml = .github\workflows\nuget-preview.yml EndProjectSection From 0a5a63ccc4ee1e104ab0aae75ede8aa3441a3481 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 11 Oct 2023 09:12:36 +0200 Subject: [PATCH 051/194] Release 3.0.3 --- CHANGELOG.md | 5 ++--- version.txt | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 753d151c9..411356775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,16 +5,15 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. -## [Unreleased][] +## [3.0.3](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.3) - 2023-10-11 ### Added -- Use enum name in `repr` - Support for Python 3.12 ### Changed -### Fixed +- Use enum name in `repr` ## [3.0.2](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.2) - 2023-08-29 diff --git a/version.txt b/version.txt index 0f9d6b15d..75a22a26a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.0-dev +3.0.3 From c05f5784f6d2c8bfbc16302b3fd2d8ae4ee39894 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 11 Oct 2023 09:32:09 +0200 Subject: [PATCH 052/194] Reset version to dev --- CHANGELOG.md | 11 ++++++++++- version.txt | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 411356775..fdab9bf64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. +## [Unreleased][] + +### Added + +### Changed + +### Fixed + + ## [3.0.3](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.3) - 2023-10-11 ### Added @@ -833,7 +842,7 @@ This version improves performance on benchmarks significantly compared to 2.3. [semantic versioning]: http://semver.org/ -[unreleased]: ../../compare/v2.3.0...HEAD +[unreleased]: ../../compare/v3.0.1...HEAD [2.3.0]: ../../compare/v2.2.2...v2.3.0 diff --git a/version.txt b/version.txt index 75a22a26a..0f9d6b15d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.3 +3.1.0-dev From 8e8c3f3c921720076b6eb7ee217a14d115240d9c Mon Sep 17 00:00:00 2001 From: bmello4688 Date: Mon, 16 Oct 2023 06:47:26 -0400 Subject: [PATCH 053/194] Allow setting of the python module file (#2044) Allow the setting of the python module file in order to create a virtual package structure --------- Co-authored-by: Benedikt Reinartz --- src/embed_tests/Modules.cs | 70 ++++++++++++++++++++++++++--- src/runtime/PythonTypes/PyModule.cs | 13 +++++- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/embed_tests/Modules.cs b/src/embed_tests/Modules.cs index a88ab8552..6cab4dd07 100644 --- a/src/embed_tests/Modules.cs +++ b/src/embed_tests/Modules.cs @@ -51,7 +51,7 @@ public void TestEval() ps.Set("a", 1); var result = ps.Eval("a + 2"); Assert.AreEqual(3, result); - } + } } /// @@ -169,6 +169,62 @@ public void TestScopeClass() } } + /// + /// Create a class in the scope, the class can read variables in the scope. + /// Its methods can write the variables with the help of 'global' keyword. + /// + [Test] + public void TestCreateVirtualPackageStructure() + { + using (Py.GIL()) + { + using var _p1 = PyModule.FromString("test", ""); + // Sub-module + using var _p2 = PyModule.FromString("test.scope", + "class Class1():\n" + + " def __init__(self, value):\n" + + " self.value = value\n" + + " def call(self, arg):\n" + + " return self.value + bb + arg\n" + // use scope variables + " def update(self, arg):\n" + + " global bb\n" + + " bb = self.value + arg\n", // update scope variable + "test" + ); + + dynamic ps2 = Py.Import("test.scope"); + ps2.bb = 100; + + dynamic obj1 = ps2.Class1(20); + var result = obj1.call(10).As(); + Assert.AreEqual(130, result); + + obj1.update(10); + result = ps2.Get("bb"); + Assert.AreEqual(30, result); + } + } + + /// + /// Test setting the file attribute via a FromString parameter + /// + [Test] + public void TestCreateModuleWithFilename() + { + using var _gil = Py.GIL(); + + using var mod = PyModule.FromString("mod", ""); + using var modWithoutName = PyModule.FromString("mod_without_name", "", " "); + using var modNullName = PyModule.FromString("mod_null_name", "", null); + + using var modWithName = PyModule.FromString("mod_with_name", "", "some_filename"); + + Assert.AreEqual("none", mod.Get("__file__")); + Assert.AreEqual("none", modWithoutName.Get("__file__")); + Assert.AreEqual("none", modNullName.Get("__file__")); + Assert.AreEqual("some_filename", modWithName.Get("__file__")); + } + /// /// Import a python module into the session. /// Equivalent to the Python "import" statement. @@ -194,7 +250,7 @@ public void TestImportModule() } /// - /// Create a scope and import variables from a scope, + /// Create a scope and import variables from a scope, /// exec Python statements in the scope then discard it. /// [Test] @@ -218,7 +274,7 @@ public void TestImportScope() } /// - /// Create a scope and import variables from a scope, + /// Create a scope and import variables from a scope, /// exec Python statements in the scope then discard it. /// [Test] @@ -241,7 +297,7 @@ public void TestImportAllFromScope() } /// - /// Create a scope and import variables from a scope, + /// Create a scope and import variables from a scope, /// call the function imported. /// [Test] @@ -286,7 +342,7 @@ public void TestImportScopeFunction() public void TestVariables() { using (Py.GIL()) - { + { (ps.Variables() as dynamic)["ee"] = new PyInt(200); var a0 = ps.Get("ee"); Assert.AreEqual(200, a0); @@ -326,8 +382,8 @@ public void TestThread() _ps.res = 0; _ps.bb = 100; _ps.th_cnt = 0; - //add function to the scope - //can be call many times, more efficient than ast + //add function to the scope + //can be call many times, more efficient than ast ps.Exec( "import threading\n"+ "lock = threading.Lock()\n"+ diff --git a/src/runtime/PythonTypes/PyModule.cs b/src/runtime/PythonTypes/PyModule.cs index 4549678ed..243f77ecc 100644 --- a/src/runtime/PythonTypes/PyModule.cs +++ b/src/runtime/PythonTypes/PyModule.cs @@ -82,7 +82,18 @@ public PyModule Reload() public static PyModule FromString(string name, string code) { - using NewReference c = Runtime.Py_CompileString(code, "none", (int)RunFlagType.File); + return FromString(name, code, ""); + } + + public static PyModule FromString(string name, string code, string file) + { + // Force valid value + if (string.IsNullOrWhiteSpace(file)) + { + file = "none"; + } + + using NewReference c = Runtime.Py_CompileString(code, file, (int)RunFlagType.File); NewReference m = Runtime.PyImport_ExecCodeModule(name, c.BorrowOrThrow()); return new PyModule(m.StealOrThrow()); } From eef67db7ff800c550b509fd4aa0eeedd86647801 Mon Sep 17 00:00:00 2001 From: Jake Date: Tue, 7 Nov 2023 22:27:54 +0900 Subject: [PATCH 054/194] To fix memory access exception when iteration breaks in the middle of the list before reaching end. --- .../CollectionWrappers/IterableWrapper.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/runtime/CollectionWrappers/IterableWrapper.cs b/src/runtime/CollectionWrappers/IterableWrapper.cs index d30e584d3..849e3997b 100644 --- a/src/runtime/CollectionWrappers/IterableWrapper.cs +++ b/src/runtime/CollectionWrappers/IterableWrapper.cs @@ -24,18 +24,22 @@ public IEnumerator GetEnumerator() { iterObject = PyIter.GetIter(pyObject); } - - using var _ = iterObject; - while (true) + try { - using var GIL = Py.GIL(); - - if (!iterObject.MoveNext()) + while (true) { - iterObject.Dispose(); - break; + using var _ = Py.GIL(); + if (!iterObject.MoveNext()) + { + break; + } + yield return iterObject.Current.As()!; } - yield return iterObject.Current.As()!; + } + finally + { + using var _ = Py.GIL(); + iterObject.Dispose(); } } } From 87fc365a5fad5c2a6ab43ca12abd140fe0085443 Mon Sep 17 00:00:00 2001 From: Gert Dreyer Date: Fri, 23 Feb 2024 09:27:01 +0200 Subject: [PATCH 055/194] Tests for operators on type CS type with codec --- src/embed_tests/TestOperator.cs | 230 ++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/src/embed_tests/TestOperator.cs b/src/embed_tests/TestOperator.cs index a5713274a..1d66903ca 100644 --- a/src/embed_tests/TestOperator.cs +++ b/src/embed_tests/TestOperator.cs @@ -15,6 +15,7 @@ public class TestOperator public void SetUp() { PythonEngine.Initialize(); + OwnIntCodec.Setup(); } [OneTimeTearDown] @@ -23,6 +24,120 @@ public void Dispose() PythonEngine.Shutdown(); } + // Mock Integer class to test math ops on non-native dotnet types + public struct OwnInt + { + private int _value; + + public int Num => _value; + + public OwnInt() + { + _value = 0; + } + + public OwnInt(int value) + { + _value = value; + } + + public static OwnInt operator -(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value - p2._value); + } + + public static OwnInt operator +(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value + p2._value); + } + + public static OwnInt operator *(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value * p2._value); + } + + public static OwnInt operator /(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value / p2._value); + } + + public static OwnInt operator %(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value % p2._value); + } + + public static OwnInt operator ^(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value ^ p2._value); + } + + public static bool operator <(OwnInt p1, OwnInt p2) + { + return p1._value < p2._value; + } + + public static bool operator >(OwnInt p1, OwnInt p2) + { + return p1._value > p2._value; + } + + public static bool operator ==(OwnInt p1, OwnInt p2) + { + return p1._value == p2._value; + } + + public static bool operator !=(OwnInt p1, OwnInt p2) + { + return p1._value != p2._value; + } + + public static OwnInt operator |(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value | p2._value); + } + + public static OwnInt operator &(OwnInt p1, OwnInt p2) + { + return new OwnInt(p1._value & p2._value); + } + + public static bool operator <=(OwnInt p1, OwnInt p2) + { + return p1._value <= p2._value; + } + + public static bool operator >=(OwnInt p1, OwnInt p2) + { + return p1._value >= p2._value; + } + } + + // Codec for mock class above. + public class OwnIntCodec : IPyObjectDecoder + { + public static void Setup() + { + PyObjectConversions.RegisterDecoder(new OwnIntCodec()); + } + + public bool CanDecode(PyType objectType, Type targetType) + { + return objectType.Name == "int" && targetType == typeof(OwnInt); + } + + public bool TryDecode(PyObject pyObj, out T? value) + { + if (pyObj.PyType.Name != "int" || typeof(T) != typeof(OwnInt)) + { + value = default(T); + return false; + } + + value = (T)(object)new OwnInt(pyObj.As()); + return true; + } + } + public class OperableObject { public int Num { get; set; } @@ -524,6 +639,121 @@ public void ShiftOperatorOverloads() c = a >> b.Num assert c.Num == a.Num >> b.Num +"); + } + + [Test] + public void ReverseOperatorWithCodec() + { + string name = string.Format("{0}.{1}", + typeof(OwnInt).DeclaringType.Name, + typeof(OwnInt).Name); + string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; + + PythonEngine.Exec($@" +from {module} import * +cls = {name} +a = 2 +b = cls(10) + +c = a + b +assert c.Num == a + b.Num + +c = a - b +assert c.Num == a - b.Num + +c = a * b +assert c.Num == a * b.Num + +c = a / b +assert c.Num == a // b.Num + +c = a % b +assert c.Num == a % b.Num + +c = a & b +assert c.Num == a & b.Num + +c = a | b +assert c.Num == a | b.Num + +c = a ^ b +assert c.Num == a ^ b.Num + +c = a == b +assert c == (a == b.Num) + +c = a != b +assert c == (a != b.Num) + +c = a <= b +assert c == (a <= b.Num) + +c = a >= b +assert c == (a >= b.Num) + +c = a < b +assert c == (a < b.Num) + +c = a > b +assert c == (a > b.Num) +"); + } + + [Test] + public void ForwardOperatorWithCodec() + { + string name = string.Format("{0}.{1}", + typeof(OwnInt).DeclaringType.Name, + typeof(OwnInt).Name); + string module = MethodBase.GetCurrentMethod().DeclaringType.Namespace; + + PythonEngine.Exec($@" +from {module} import * +cls = {name} +a = cls(2) +b = 10 +c = a + b +assert c.Num == a.Num + b + +c = a - b +assert c.Num == a.Num - b + +c = a * b +assert c.Num == a.Num * b + +c = a / b +assert c.Num == a.Num // b + +c = a % b +assert c.Num == a.Num % b + +c = a & b +assert c.Num == a.Num & b + +c = a | b +assert c.Num == a.Num | b + +c = a ^ b +assert c.Num == a.Num ^ b + +c = a == b +assert c == (a.Num == b) + +c = a != b +assert c == (a.Num != b) + +c = a <= b +assert c == (a.Num <= b) + +c = a >= b +assert c == (a.Num >= b) + +c = a < b +assert c == (a.Num < b) + +c = a > b +assert c == (a.Num > b) "); } } From 01d6772b05e88cf7b1472aad751fcde5aa8008c5 Mon Sep 17 00:00:00 2001 From: Gert Dreyer Date: Wed, 21 Feb 2024 22:07:18 +0200 Subject: [PATCH 056/194] Bugfix: RecursionError when reverse/righthand operations invoked. e.g. __rsub__, __rmul__ --- src/runtime/ClassManager.cs | 2 +- src/runtime/MethodBinder.cs | 40 +++++++++++++++++------------ src/runtime/Types/MethodBinding.cs | 3 ++- src/runtime/Types/MethodObject.cs | 18 ++++++------- src/runtime/Types/OperatorMethod.cs | 18 +++++-------- 5 files changed, 42 insertions(+), 39 deletions(-) diff --git a/src/runtime/ClassManager.cs b/src/runtime/ClassManager.cs index 79ab20e82..25f8639ab 100644 --- a/src/runtime/ClassManager.cs +++ b/src/runtime/ClassManager.cs @@ -546,7 +546,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl) ci.members[pyName] = new MethodObject(type, name, forwardMethods).AllocObject(); // Only methods where only the right operand is the declaring type. if (reverseMethods.Length > 0) - ci.members[pyNameReverse] = new MethodObject(type, name, reverseMethods).AllocObject(); + ci.members[pyNameReverse] = new MethodObject(type, name, reverseMethods, reverse_args: true).AllocObject(); } } diff --git a/src/runtime/MethodBinder.cs b/src/runtime/MethodBinder.cs index 07ed4fe22..18ef573d0 100644 --- a/src/runtime/MethodBinder.cs +++ b/src/runtime/MethodBinder.cs @@ -28,17 +28,22 @@ internal class MethodBinder [NonSerialized] public bool init = false; + public const bool DefaultAllowThreads = true; public bool allow_threads = DefaultAllowThreads; - internal MethodBinder() + public bool args_reversed = false; + + internal MethodBinder(bool reverse_args = false) { list = new List(); + args_reversed = reverse_args; } - internal MethodBinder(MethodInfo mi) + internal MethodBinder(MethodInfo mi, bool reverse_args = false) { list = new List { new MaybeMethodBase(mi) }; + args_reversed = reverse_args; } public int Count @@ -271,10 +276,11 @@ internal static int ArgPrecedence(Type t) /// The Python target of the method invocation. /// The Python arguments. /// The Python keyword arguments. + /// Reverse arguments of methods. Used for methods such as __radd__, __rsub__, __rmod__ etc /// A Binding if successful. Otherwise null. - internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw) + internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, bool reverse_args = false) { - return Bind(inst, args, kw, null, null); + return Bind(inst, args, kw, null, null, reverse_args); } /// @@ -287,10 +293,11 @@ internal static int ArgPrecedence(Type t) /// The Python arguments. /// The Python keyword arguments. /// If not null, only bind to that method. + /// Reverse arguments of methods. Used for methods such as __radd__, __rsub__, __rmod__ etc /// A Binding if successful. Otherwise null. - internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info) + internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, bool reverse_args = false) { - return Bind(inst, args, kw, info, null); + return Bind(inst, args, kw, info, null, reverse_args); } private readonly struct MatchedMethod @@ -334,8 +341,9 @@ public MismatchedMethod(Exception exception, MethodBase mb) /// The Python keyword arguments. /// If not null, only bind to that method. /// If not null, additionally attempt to bind to the generic methods in this array by inferring generic type parameters. + /// Reverse arguments of methods. Used for methods such as __radd__, __rsub__, __rmod__ etc /// A Binding if successful. Otherwise null. - internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo) + internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo, bool reverse_args = false) { // loop to find match, return invoker w/ or w/o error var kwargDict = new Dictionary(); @@ -363,10 +371,10 @@ public MismatchedMethod(Exception exception, MethodBase mb) _methods = GetMethods(); } - return Bind(inst, args, kwargDict, _methods, matchGenerics: true); + return Bind(inst, args, kwargDict, _methods, matchGenerics: true, reverse_args); } - static Binding? Bind(BorrowedReference inst, BorrowedReference args, Dictionary kwargDict, MethodBase[] methods, bool matchGenerics) + private static Binding? Bind(BorrowedReference inst, BorrowedReference args, Dictionary kwargDict, MethodBase[] methods, bool matchGenerics, bool reversed = false) { var pynargs = (int)Runtime.PyTuple_Size(args); var isGeneric = false; @@ -386,7 +394,7 @@ public MismatchedMethod(Exception exception, MethodBase mb) // Binary operator methods will have 2 CLR args but only one Python arg // (unary operators will have 1 less each), since Python operator methods are bound. isOperator = isOperator && pynargs == pi.Length - 1; - bool isReverse = isOperator && OperatorMethod.IsReverse((MethodInfo)mi); // Only cast if isOperator. + bool isReverse = isOperator && reversed; // Only cast if isOperator. if (isReverse && OperatorMethod.IsComparisonOp((MethodInfo)mi)) continue; // Comparison operators in Python have no reverse mode. if (!MatchesArgumentCount(pynargs, pi, kwargDict, out bool paramsArray, out ArrayList? defaultArgList, out int kwargsMatched, out int defaultsNeeded) && !isOperator) @@ -809,14 +817,14 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa return match; } - internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw) + internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, bool reverse_args = false) { - return Invoke(inst, args, kw, null, null); + return Invoke(inst, args, kw, null, null, reverse_args); } - internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info) + internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, bool reverse_args = false) { - return Invoke(inst, args, kw, info, null); + return Invoke(inst, args, kw, info, null, reverse_args = false); } protected static void AppendArgumentTypes(StringBuilder to, BorrowedReference args) @@ -852,7 +860,7 @@ protected static void AppendArgumentTypes(StringBuilder to, BorrowedReference ar to.Append(')'); } - internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo) + internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo, bool reverse_args = false) { // No valid methods, nothing to bind. if (GetMethods().Length == 0) @@ -865,7 +873,7 @@ internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference a return Exceptions.RaiseTypeError(msg.ToString()); } - Binding? binding = Bind(inst, args, kw, info, methodinfo); + Binding? binding = Bind(inst, args, kw, info, methodinfo, reverse_args); object result; IntPtr ts = IntPtr.Zero; diff --git a/src/runtime/Types/MethodBinding.cs b/src/runtime/Types/MethodBinding.cs index 334d705a6..2f943f3fb 100644 --- a/src/runtime/Types/MethodBinding.cs +++ b/src/runtime/Types/MethodBinding.cs @@ -237,7 +237,8 @@ public static NewReference tp_call(BorrowedReference ob, BorrowedReference args, } } } - return self.m.Invoke(target is null ? BorrowedReference.Null : target, args, kw, self.info.UnsafeValue); + + return self.m.Invoke(target is null ? BorrowedReference.Null : target, args, kw, self.info.UnsafeValue, self.m.binder.args_reversed); } finally { diff --git a/src/runtime/Types/MethodObject.cs b/src/runtime/Types/MethodObject.cs index 05198da76..1943ed884 100644 --- a/src/runtime/Types/MethodObject.cs +++ b/src/runtime/Types/MethodObject.cs @@ -19,20 +19,20 @@ internal class MethodObject : ExtensionType { [NonSerialized] private MethodBase[]? _info = null; + private readonly List infoList; internal string name; internal readonly MethodBinder binder; internal bool is_static = false; - internal PyString? doc; internal MaybeType type; - public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads) + public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads, bool reverse_args = false) { this.type = type; this.name = name; this.infoList = new List(); - binder = new MethodBinder(); + binder = new MethodBinder(reverse_args); foreach (MethodBase item in info) { this.infoList.Add(item); @@ -45,8 +45,8 @@ public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_t binder.allow_threads = allow_threads; } - public MethodObject(MaybeType type, string name, MethodBase[] info) - : this(type, name, info, allow_threads: AllowThreads(info)) + public MethodObject(MaybeType type, string name, MethodBase[] info, bool reverse_args = false) + : this(type, name, info, allow_threads: AllowThreads(info), reverse_args) { } @@ -67,14 +67,14 @@ internal MethodBase[] info } } - public virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw) + public virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, bool reverse_args = false) { - return Invoke(inst, args, kw, null); + return Invoke(inst, args, kw, null, reverse_args); } - public virtual NewReference Invoke(BorrowedReference target, BorrowedReference args, BorrowedReference kw, MethodBase? info) + public virtual NewReference Invoke(BorrowedReference target, BorrowedReference args, BorrowedReference kw, MethodBase? info, bool reverse_args = false) { - return binder.Invoke(target, args, kw, info, this.info); + return binder.Invoke(target, args, kw, info, this.info, reverse_args); } /// diff --git a/src/runtime/Types/OperatorMethod.cs b/src/runtime/Types/OperatorMethod.cs index e3cc23370..a2ca73982 100644 --- a/src/runtime/Types/OperatorMethod.cs +++ b/src/runtime/Types/OperatorMethod.cs @@ -177,17 +177,14 @@ public static string ReversePyMethodName(string pyName) } /// - /// Check if the method is performing a reverse operation. + /// Check if the method should have a reversed operation. /// /// The operator method. /// - public static bool IsReverse(MethodBase method) + public static bool HaveReverse(MethodBase method) { - Type primaryType = method.IsOpsHelper() - ? method.DeclaringType.GetGenericArguments()[0] - : method.DeclaringType; - Type leftOperandType = method.GetParameters()[0].ParameterType; - return leftOperandType != primaryType; + var pi = method.GetParameters(); + return OpMethodMap.ContainsKey(method.Name) && pi.Length == 2; } public static void FilterMethods(MethodBase[] methods, out MethodBase[] forwardMethods, out MethodBase[] reverseMethods) @@ -196,14 +193,11 @@ public static void FilterMethods(MethodBase[] methods, out MethodBase[] forwardM var reverseMethodsList = new List(); foreach (var method in methods) { - if (IsReverse(method)) + forwardMethodsList.Add(method); + if (HaveReverse(method)) { reverseMethodsList.Add(method); - } else - { - forwardMethodsList.Add(method); } - } forwardMethods = forwardMethodsList.ToArray(); reverseMethods = reverseMethodsList.ToArray(); From d3bde9d6d39e5bcf5990d7dba43cda42321287d2 Mon Sep 17 00:00:00 2001 From: Gert Dreyer Date: Fri, 23 Feb 2024 15:33:44 +0200 Subject: [PATCH 057/194] Update Authors and Changelog --- AUTHORS.md | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 577e898aa..18435671c 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -86,3 +86,4 @@ - ([@gpetrou](https://github.com/gpetrou)) - Ehsan Iran-Nejad ([@eirannejad](https://github.com/eirannejad)) - ([@legomanww](https://github.com/legomanww)) +- ([@gertdreyer](https://github.com/gertdreyer)) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdab9bf64..5b545045f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Fixed +- Fixed RecursionError for reverse operators on C# operable types from python. See #2240 ## [3.0.3](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.3) - 2023-10-11 From 1542cc96ae1e69f672da85f14ff51ed6a430b6f8 Mon Sep 17 00:00:00 2001 From: Gert Dreyer Date: Mon, 26 Feb 2024 13:11:41 +0200 Subject: [PATCH 058/194] Normalized names. Added HashCode and Equals to testing objects --- src/embed_tests/TestOperator.cs | 19 +++++++----- src/runtime/ClassManager.cs | 2 +- src/runtime/MethodBinder.cs | 50 ++++++++++++++++-------------- src/runtime/Types/MethodBinding.cs | 2 +- src/runtime/Types/MethodObject.cs | 16 +++++----- 5 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/embed_tests/TestOperator.cs b/src/embed_tests/TestOperator.cs index 1d66903ca..1ec3268ac 100644 --- a/src/embed_tests/TestOperator.cs +++ b/src/embed_tests/TestOperator.cs @@ -41,6 +41,17 @@ public OwnInt(int value) _value = value; } + public override int GetHashCode() + { + return unchecked(65535 + _value.GetHashCode()); + } + + public override bool Equals(object obj) + { + return obj is OwnInt @object && + Num == @object.Num; + } + public static OwnInt operator -(OwnInt p1, OwnInt p2) { return new OwnInt(p1._value - p2._value); @@ -125,14 +136,8 @@ public bool CanDecode(PyType objectType, Type targetType) return objectType.Name == "int" && targetType == typeof(OwnInt); } - public bool TryDecode(PyObject pyObj, out T? value) + public bool TryDecode(PyObject pyObj, out T value) { - if (pyObj.PyType.Name != "int" || typeof(T) != typeof(OwnInt)) - { - value = default(T); - return false; - } - value = (T)(object)new OwnInt(pyObj.As()); return true; } diff --git a/src/runtime/ClassManager.cs b/src/runtime/ClassManager.cs index 25f8639ab..ecb6055a8 100644 --- a/src/runtime/ClassManager.cs +++ b/src/runtime/ClassManager.cs @@ -546,7 +546,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl) ci.members[pyName] = new MethodObject(type, name, forwardMethods).AllocObject(); // Only methods where only the right operand is the declaring type. if (reverseMethods.Length > 0) - ci.members[pyNameReverse] = new MethodObject(type, name, reverseMethods, reverse_args: true).AllocObject(); + ci.members[pyNameReverse] = new MethodObject(type, name, reverseMethods, argsReversed: true).AllocObject(); } } diff --git a/src/runtime/MethodBinder.cs b/src/runtime/MethodBinder.cs index 18ef573d0..836e1da3e 100644 --- a/src/runtime/MethodBinder.cs +++ b/src/runtime/MethodBinder.cs @@ -32,18 +32,18 @@ internal class MethodBinder public const bool DefaultAllowThreads = true; public bool allow_threads = DefaultAllowThreads; - public bool args_reversed = false; + public bool argsReversed = false; - internal MethodBinder(bool reverse_args = false) + internal MethodBinder(bool argsReversed = false) { list = new List(); - args_reversed = reverse_args; + this.argsReversed = argsReversed; } - internal MethodBinder(MethodInfo mi, bool reverse_args = false) + internal MethodBinder(MethodInfo mi, bool argsReversed = false) { list = new List { new MaybeMethodBase(mi) }; - args_reversed = reverse_args; + this.argsReversed = argsReversed; } public int Count @@ -276,11 +276,11 @@ internal static int ArgPrecedence(Type t) /// The Python target of the method invocation. /// The Python arguments. /// The Python keyword arguments. - /// Reverse arguments of methods. Used for methods such as __radd__, __rsub__, __rmod__ etc + /// Reverse arguments of methods. Used for methods such as __radd__, __rsub__, __rmod__ etc /// A Binding if successful. Otherwise null. - internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, bool reverse_args = false) + internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, bool argsReversed = false) { - return Bind(inst, args, kw, null, null, reverse_args); + return Bind(inst, args, kw, null, null, argsReversed); } /// @@ -293,11 +293,11 @@ internal static int ArgPrecedence(Type t) /// The Python arguments. /// The Python keyword arguments. /// If not null, only bind to that method. - /// Reverse arguments of methods. Used for methods such as __radd__, __rsub__, __rmod__ etc + /// Reverse arguments of methods. Used for methods such as __radd__, __rsub__, __rmod__ etc /// A Binding if successful. Otherwise null. - internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, bool reverse_args = false) + internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, bool argsReversed = false) { - return Bind(inst, args, kw, info, null, reverse_args); + return Bind(inst, args, kw, info, null, argsReversed); } private readonly struct MatchedMethod @@ -341,9 +341,9 @@ public MismatchedMethod(Exception exception, MethodBase mb) /// The Python keyword arguments. /// If not null, only bind to that method. /// If not null, additionally attempt to bind to the generic methods in this array by inferring generic type parameters. - /// Reverse arguments of methods. Used for methods such as __radd__, __rsub__, __rmod__ etc + /// Reverse arguments of methods. Used for methods such as __radd__, __rsub__, __rmod__ etc /// A Binding if successful. Otherwise null. - internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo, bool reverse_args = false) + internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo, bool argsReversed = false) { // loop to find match, return invoker w/ or w/o error var kwargDict = new Dictionary(); @@ -371,10 +371,10 @@ public MismatchedMethod(Exception exception, MethodBase mb) _methods = GetMethods(); } - return Bind(inst, args, kwargDict, _methods, matchGenerics: true, reverse_args); + return Bind(inst, args, kwargDict, _methods, matchGenerics: true, argsReversed); } - private static Binding? Bind(BorrowedReference inst, BorrowedReference args, Dictionary kwargDict, MethodBase[] methods, bool matchGenerics, bool reversed = false) + private static Binding? Bind(BorrowedReference inst, BorrowedReference args, Dictionary kwargDict, MethodBase[] methods, bool matchGenerics, bool argsReversed = false) { var pynargs = (int)Runtime.PyTuple_Size(args); var isGeneric = false; @@ -394,7 +394,7 @@ public MismatchedMethod(Exception exception, MethodBase mb) // Binary operator methods will have 2 CLR args but only one Python arg // (unary operators will have 1 less each), since Python operator methods are bound. isOperator = isOperator && pynargs == pi.Length - 1; - bool isReverse = isOperator && reversed; // Only cast if isOperator. + bool isReverse = isOperator && argsReversed; // Only cast if isOperator. if (isReverse && OperatorMethod.IsComparisonOp((MethodInfo)mi)) continue; // Comparison operators in Python have no reverse mode. if (!MatchesArgumentCount(pynargs, pi, kwargDict, out bool paramsArray, out ArrayList? defaultArgList, out int kwargsMatched, out int defaultsNeeded) && !isOperator) @@ -402,12 +402,14 @@ public MismatchedMethod(Exception exception, MethodBase mb) continue; } // Preprocessing pi to remove either the first or second argument. - if (isOperator && !isReverse) { + if (isOperator && !isReverse) + { // The first Python arg is the right operand, while the bound instance is the left. // We need to skip the first (left operand) CLR argument. pi = pi.Skip(1).ToArray(); } - else if (isOperator && isReverse) { + else if (isOperator && isReverse) + { // The first Python arg is the left operand. // We need to take the first CLR argument. pi = pi.Take(1).ToArray(); @@ -817,14 +819,14 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa return match; } - internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, bool reverse_args = false) + internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, bool argsReversed = false) { - return Invoke(inst, args, kw, null, null, reverse_args); + return Invoke(inst, args, kw, null, null, argsReversed); } - internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, bool reverse_args = false) + internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, bool argsReversed = false) { - return Invoke(inst, args, kw, info, null, reverse_args = false); + return Invoke(inst, args, kw, info, null, argsReversed = false); } protected static void AppendArgumentTypes(StringBuilder to, BorrowedReference args) @@ -860,7 +862,7 @@ protected static void AppendArgumentTypes(StringBuilder to, BorrowedReference ar to.Append(')'); } - internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo, bool reverse_args = false) + internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo, bool argsReversed = false) { // No valid methods, nothing to bind. if (GetMethods().Length == 0) @@ -873,7 +875,7 @@ internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference a return Exceptions.RaiseTypeError(msg.ToString()); } - Binding? binding = Bind(inst, args, kw, info, methodinfo, reverse_args); + Binding? binding = Bind(inst, args, kw, info, methodinfo, argsReversed); object result; IntPtr ts = IntPtr.Zero; diff --git a/src/runtime/Types/MethodBinding.cs b/src/runtime/Types/MethodBinding.cs index 2f943f3fb..79607d1ae 100644 --- a/src/runtime/Types/MethodBinding.cs +++ b/src/runtime/Types/MethodBinding.cs @@ -238,7 +238,7 @@ public static NewReference tp_call(BorrowedReference ob, BorrowedReference args, } } - return self.m.Invoke(target is null ? BorrowedReference.Null : target, args, kw, self.info.UnsafeValue, self.m.binder.args_reversed); + return self.m.Invoke(target is null ? BorrowedReference.Null : target, args, kw, self.info.UnsafeValue, self.m.binder.argsReversed); } finally { diff --git a/src/runtime/Types/MethodObject.cs b/src/runtime/Types/MethodObject.cs index 1943ed884..4bc21458b 100644 --- a/src/runtime/Types/MethodObject.cs +++ b/src/runtime/Types/MethodObject.cs @@ -27,12 +27,12 @@ internal class MethodObject : ExtensionType internal PyString? doc; internal MaybeType type; - public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads, bool reverse_args = false) + public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads, bool argsReversed = false) { this.type = type; this.name = name; this.infoList = new List(); - binder = new MethodBinder(reverse_args); + binder = new MethodBinder(argsReversed); foreach (MethodBase item in info) { this.infoList.Add(item); @@ -45,8 +45,8 @@ public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_t binder.allow_threads = allow_threads; } - public MethodObject(MaybeType type, string name, MethodBase[] info, bool reverse_args = false) - : this(type, name, info, allow_threads: AllowThreads(info), reverse_args) + public MethodObject(MaybeType type, string name, MethodBase[] info, bool argsReversed = false) + : this(type, name, info, allow_threads: AllowThreads(info), argsReversed) { } @@ -67,14 +67,14 @@ internal MethodBase[] info } } - public virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, bool reverse_args = false) + public virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, bool argsReversed = false) { - return Invoke(inst, args, kw, null, reverse_args); + return Invoke(inst, args, kw, null, argsReversed); } - public virtual NewReference Invoke(BorrowedReference target, BorrowedReference args, BorrowedReference kw, MethodBase? info, bool reverse_args = false) + public virtual NewReference Invoke(BorrowedReference target, BorrowedReference args, BorrowedReference kw, MethodBase? info, bool argsReversed = false) { - return binder.Invoke(target, args, kw, info, this.info, reverse_args); + return binder.Invoke(target, args, kw, info, this.info, argsReversed); } /// From 71ca0634696be47ac52066097802d5a53a716ede Mon Sep 17 00:00:00 2001 From: Gert Dreyer Date: Mon, 26 Feb 2024 20:49:55 +0200 Subject: [PATCH 059/194] Cleanup Codec/Argument Reversing Passing. --- src/embed_tests/TestOperator.cs | 4 ++-- src/runtime/MethodBinder.cs | 31 +++++++++++++----------------- src/runtime/Types/MethodBinding.cs | 2 +- src/runtime/Types/MethodObject.cs | 10 +++++----- 4 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/embed_tests/TestOperator.cs b/src/embed_tests/TestOperator.cs index 1ec3268ac..6bfb81bdb 100644 --- a/src/embed_tests/TestOperator.cs +++ b/src/embed_tests/TestOperator.cs @@ -25,9 +25,9 @@ public void Dispose() } // Mock Integer class to test math ops on non-native dotnet types - public struct OwnInt + public readonly struct OwnInt { - private int _value; + private readonly int _value; public int Num => _value; diff --git a/src/runtime/MethodBinder.cs b/src/runtime/MethodBinder.cs index 836e1da3e..9a5515c8e 100644 --- a/src/runtime/MethodBinder.cs +++ b/src/runtime/MethodBinder.cs @@ -34,16 +34,14 @@ internal class MethodBinder public bool argsReversed = false; - internal MethodBinder(bool argsReversed = false) + internal MethodBinder() { list = new List(); - this.argsReversed = argsReversed; } - internal MethodBinder(MethodInfo mi, bool argsReversed = false) + internal MethodBinder(MethodInfo mi) { list = new List { new MaybeMethodBase(mi) }; - this.argsReversed = argsReversed; } public int Count @@ -276,11 +274,10 @@ internal static int ArgPrecedence(Type t) /// The Python target of the method invocation. /// The Python arguments. /// The Python keyword arguments. - /// Reverse arguments of methods. Used for methods such as __radd__, __rsub__, __rmod__ etc /// A Binding if successful. Otherwise null. - internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, bool argsReversed = false) + internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw) { - return Bind(inst, args, kw, null, null, argsReversed); + return Bind(inst, args, kw, null, null); } /// @@ -293,11 +290,10 @@ internal static int ArgPrecedence(Type t) /// The Python arguments. /// The Python keyword arguments. /// If not null, only bind to that method. - /// Reverse arguments of methods. Used for methods such as __radd__, __rsub__, __rmod__ etc /// A Binding if successful. Otherwise null. - internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, bool argsReversed = false) + internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info) { - return Bind(inst, args, kw, info, null, argsReversed); + return Bind(inst, args, kw, info, null); } private readonly struct MatchedMethod @@ -341,9 +337,8 @@ public MismatchedMethod(Exception exception, MethodBase mb) /// The Python keyword arguments. /// If not null, only bind to that method. /// If not null, additionally attempt to bind to the generic methods in this array by inferring generic type parameters. - /// Reverse arguments of methods. Used for methods such as __radd__, __rsub__, __rmod__ etc /// A Binding if successful. Otherwise null. - internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo, bool argsReversed = false) + internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo) { // loop to find match, return invoker w/ or w/o error var kwargDict = new Dictionary(); @@ -819,14 +814,14 @@ static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] pa return match; } - internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, bool argsReversed = false) + internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw) { - return Invoke(inst, args, kw, null, null, argsReversed); + return Invoke(inst, args, kw, null, null); } - internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, bool argsReversed = false) + internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info) { - return Invoke(inst, args, kw, info, null, argsReversed = false); + return Invoke(inst, args, kw, info, null); } protected static void AppendArgumentTypes(StringBuilder to, BorrowedReference args) @@ -862,7 +857,7 @@ protected static void AppendArgumentTypes(StringBuilder to, BorrowedReference ar to.Append(')'); } - internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo, bool argsReversed = false) + internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo) { // No valid methods, nothing to bind. if (GetMethods().Length == 0) @@ -875,7 +870,7 @@ internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference a return Exceptions.RaiseTypeError(msg.ToString()); } - Binding? binding = Bind(inst, args, kw, info, methodinfo, argsReversed); + Binding? binding = Bind(inst, args, kw, info, methodinfo); object result; IntPtr ts = IntPtr.Zero; diff --git a/src/runtime/Types/MethodBinding.cs b/src/runtime/Types/MethodBinding.cs index 79607d1ae..bfe22b0f3 100644 --- a/src/runtime/Types/MethodBinding.cs +++ b/src/runtime/Types/MethodBinding.cs @@ -238,7 +238,7 @@ public static NewReference tp_call(BorrowedReference ob, BorrowedReference args, } } - return self.m.Invoke(target is null ? BorrowedReference.Null : target, args, kw, self.info.UnsafeValue, self.m.binder.argsReversed); + return self.m.Invoke(target is null ? BorrowedReference.Null : target, args, kw, self.info.UnsafeValue); } finally { diff --git a/src/runtime/Types/MethodObject.cs b/src/runtime/Types/MethodObject.cs index 4bc21458b..12484d301 100644 --- a/src/runtime/Types/MethodObject.cs +++ b/src/runtime/Types/MethodObject.cs @@ -32,7 +32,7 @@ public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_t this.type = type; this.name = name; this.infoList = new List(); - binder = new MethodBinder(argsReversed); + binder = new MethodBinder() { argsReversed = argsReversed }; foreach (MethodBase item in info) { this.infoList.Add(item); @@ -67,14 +67,14 @@ internal MethodBase[] info } } - public virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, bool argsReversed = false) + public virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw) { - return Invoke(inst, args, kw, null, argsReversed); + return Invoke(inst, args, kw, null); } - public virtual NewReference Invoke(BorrowedReference target, BorrowedReference args, BorrowedReference kw, MethodBase? info, bool argsReversed = false) + public virtual NewReference Invoke(BorrowedReference target, BorrowedReference args, BorrowedReference kw, MethodBase? info) { - return binder.Invoke(target, args, kw, info, this.info, argsReversed); + return binder.Invoke(target, args, kw, info, this.info); } /// From 9d18a243f507e18143ec97c743a0dfe76fdce67d Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 27 Feb 2024 23:36:05 -0800 Subject: [PATCH 060/194] Added `ToPythonAs()` extension method to allow for explicit conversion using a specific type (#2330) fixes https://github.com/pythonnet/pythonnet/issues/2311 --- CHANGELOG.md | 3 +++ src/embed_tests/TestConverter.cs | 9 +++++++++ src/runtime/Converter.cs | 11 +++++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b545045f..83f9d4bd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Added +- Added `ToPythonAs()` extension method to allow for explicit conversion using a specific type. ([#2311][i2311]) + ### Changed ### Fixed @@ -960,3 +962,4 @@ This version improves performance on benchmarks significantly compared to 2.3. [i238]: https://github.com/pythonnet/pythonnet/issues/238 [i1481]: https://github.com/pythonnet/pythonnet/issues/1481 [i1672]: https://github.com/pythonnet/pythonnet/pull/1672 +[i2311]: https://github.com/pythonnet/pythonnet/issues/2311 diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 0686d528b..a59b9c97b 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -185,6 +185,15 @@ public void RawPyObjectProxy() Assert.AreEqual(pyObject.DangerousGetAddressOrNull(), proxiedHandle); } + [Test] + public void GenericToPython() + { + int i = 42; + var pyObject = i.ToPythonAs(); + var type = pyObject.GetPythonType(); + Assert.AreEqual(nameof(IConvertible), type.Name); + } + // regression for https://github.com/pythonnet/pythonnet/issues/451 [Test] public void CanGetListFromDerivedClass() diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index 412f3b711..50b33e60e 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -133,7 +133,8 @@ internal static NewReference ToPython(object? value, Type type) if (EncodableByUser(type, value)) { var encoded = PyObjectConversions.TryEncode(value, type); - if (encoded != null) { + if (encoded != null) + { return new NewReference(encoded); } } @@ -334,7 +335,7 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType, if (obType.IsGenericType && obType.GetGenericTypeDefinition() == typeof(Nullable<>)) { - if( value == Runtime.PyNone ) + if (value == Runtime.PyNone) { result = null; return true; @@ -980,5 +981,11 @@ public static PyObject ToPython(this object? o) if (o is null) return Runtime.None; return Converter.ToPython(o, o.GetType()).MoveToPyObject(); } + + public static PyObject ToPythonAs(this T? o) + { + if (o is null) return Runtime.None; + return Converter.ToPython(o, typeof(T)).MoveToPyObject(); + } } } From 563e3695f284b0269c73048875b7d2031832993a Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Thu, 15 Feb 2024 13:07:51 -0800 Subject: [PATCH 061/194] IComparable and IEquatable implementations for PyInt, PyFloat, and PyString for primitive .NET types --- CHANGELOG.md | 3 + src/embed_tests/TestPyFloat.cs | 27 ++++ src/embed_tests/TestPyInt.cs | 70 +++++++++ src/embed_tests/TestPyString.cs | 19 +++ .../PythonTypes/PyFloat.IComparable.cs | 34 +++++ src/runtime/PythonTypes/PyFloat.cs | 4 +- src/runtime/PythonTypes/PyInt.IComparable.cs | 136 ++++++++++++++++++ src/runtime/PythonTypes/PyInt.cs | 2 +- src/runtime/PythonTypes/PyObject.cs | 19 ++- src/runtime/PythonTypes/PyString.cs | 21 ++- 10 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 src/runtime/PythonTypes/PyFloat.IComparable.cs create mode 100644 src/runtime/PythonTypes/PyInt.IComparable.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 83f9d4bd1..e6cc52d72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added `ToPythonAs()` extension method to allow for explicit conversion using a specific type. ([#2311][i2311]) +- Added `IComparable` and `IEquatable` implementations to `PyInt`, `PyFloat`, and `PyString` + to compare with primitive .NET types like `long`. + ### Changed ### Fixed diff --git a/src/embed_tests/TestPyFloat.cs b/src/embed_tests/TestPyFloat.cs index 36531cb6a..89e29e5fd 100644 --- a/src/embed_tests/TestPyFloat.cs +++ b/src/embed_tests/TestPyFloat.cs @@ -126,5 +126,32 @@ public void AsFloatBad() StringAssert.StartsWith("could not convert string to float", ex.Message); Assert.IsNull(a); } + + [Test] + public void CompareTo() + { + var v = new PyFloat(42); + + Assert.AreEqual(0, v.CompareTo(42f)); + Assert.AreEqual(0, v.CompareTo(42d)); + + Assert.AreEqual(1, v.CompareTo(41f)); + Assert.AreEqual(1, v.CompareTo(41d)); + + Assert.AreEqual(-1, v.CompareTo(43f)); + Assert.AreEqual(-1, v.CompareTo(43d)); + } + + [Test] + public void Equals() + { + var v = new PyFloat(42); + + Assert.IsTrue(v.Equals(42f)); + Assert.IsTrue(v.Equals(42d)); + + Assert.IsFalse(v.Equals(41f)); + Assert.IsFalse(v.Equals(41d)); + } } } diff --git a/src/embed_tests/TestPyInt.cs b/src/embed_tests/TestPyInt.cs index c147e074b..d2767e664 100644 --- a/src/embed_tests/TestPyInt.cs +++ b/src/embed_tests/TestPyInt.cs @@ -210,6 +210,76 @@ public void ToBigInteger() CollectionAssert.AreEqual(expected, actual); } + [Test] + public void CompareTo() + { + var v = new PyInt(42); + + #region Signed + Assert.AreEqual(0, v.CompareTo(42L)); + Assert.AreEqual(0, v.CompareTo(42)); + Assert.AreEqual(0, v.CompareTo((short)42)); + Assert.AreEqual(0, v.CompareTo((sbyte)42)); + + Assert.AreEqual(1, v.CompareTo(41L)); + Assert.AreEqual(1, v.CompareTo(41)); + Assert.AreEqual(1, v.CompareTo((short)41)); + Assert.AreEqual(1, v.CompareTo((sbyte)41)); + + Assert.AreEqual(-1, v.CompareTo(43L)); + Assert.AreEqual(-1, v.CompareTo(43)); + Assert.AreEqual(-1, v.CompareTo((short)43)); + Assert.AreEqual(-1, v.CompareTo((sbyte)43)); + #endregion Signed + + #region Unsigned + Assert.AreEqual(0, v.CompareTo(42UL)); + Assert.AreEqual(0, v.CompareTo(42U)); + Assert.AreEqual(0, v.CompareTo((ushort)42)); + Assert.AreEqual(0, v.CompareTo((byte)42)); + + Assert.AreEqual(1, v.CompareTo(41UL)); + Assert.AreEqual(1, v.CompareTo(41U)); + Assert.AreEqual(1, v.CompareTo((ushort)41)); + Assert.AreEqual(1, v.CompareTo((byte)41)); + + Assert.AreEqual(-1, v.CompareTo(43UL)); + Assert.AreEqual(-1, v.CompareTo(43U)); + Assert.AreEqual(-1, v.CompareTo((ushort)43)); + Assert.AreEqual(-1, v.CompareTo((byte)43)); + #endregion Unsigned + } + + [Test] + public void Equals() + { + var v = new PyInt(42); + + #region Signed + Assert.True(v.Equals(42L)); + Assert.True(v.Equals(42)); + Assert.True(v.Equals((short)42)); + Assert.True(v.Equals((sbyte)42)); + + Assert.False(v.Equals(41L)); + Assert.False(v.Equals(41)); + Assert.False(v.Equals((short)41)); + Assert.False(v.Equals((sbyte)41)); + #endregion Signed + + #region Unsigned + Assert.True(v.Equals(42UL)); + Assert.True(v.Equals(42U)); + Assert.True(v.Equals((ushort)42)); + Assert.True(v.Equals((byte)42)); + + Assert.False(v.Equals(41UL)); + Assert.False(v.Equals(41U)); + Assert.False(v.Equals((ushort)41)); + Assert.False(v.Equals((byte)41)); + #endregion Unsigned + } + [Test] public void ToBigIntegerLarge() { diff --git a/src/embed_tests/TestPyString.cs b/src/embed_tests/TestPyString.cs index b12e08c23..35c6339ee 100644 --- a/src/embed_tests/TestPyString.cs +++ b/src/embed_tests/TestPyString.cs @@ -112,5 +112,24 @@ public void TestUnicodeSurrogate() Assert.AreEqual(4, actual.Length()); Assert.AreEqual(expected, actual.ToString()); } + + [Test] + public void CompareTo() + { + var a = new PyString("foo"); + + Assert.AreEqual(0, a.CompareTo("foo")); + Assert.AreEqual("foo".CompareTo("bar"), a.CompareTo("bar")); + Assert.AreEqual("foo".CompareTo("foz"), a.CompareTo("foz")); + } + + [Test] + public void Equals() + { + var a = new PyString("foo"); + + Assert.True(a.Equals("foo")); + Assert.False(a.Equals("bar")); + } } } diff --git a/src/runtime/PythonTypes/PyFloat.IComparable.cs b/src/runtime/PythonTypes/PyFloat.IComparable.cs new file mode 100644 index 000000000..c12fc283a --- /dev/null +++ b/src/runtime/PythonTypes/PyFloat.IComparable.cs @@ -0,0 +1,34 @@ +using System; + +namespace Python.Runtime; + +partial class PyFloat : IComparable, IComparable + , IEquatable, IEquatable + , IComparable, IEquatable +{ + public override bool Equals(object o) + { + using var _ = Py.GIL(); + return o switch + { + double f64 => this.Equals(f64), + float f32 => this.Equals(f32), + _ => base.Equals(o), + }; + } + + public int CompareTo(double other) => this.ToDouble().CompareTo(other); + + public int CompareTo(float other) => this.ToDouble().CompareTo(other); + + public bool Equals(double other) => this.ToDouble().Equals(other); + + public bool Equals(float other) => this.ToDouble().Equals(other); + + public int CompareTo(PyFloat? other) + { + return other is null ? 1 : this.CompareTo(other.BorrowNullable()); + } + + public bool Equals(PyFloat? other) => base.Equals(other); +} diff --git a/src/runtime/PythonTypes/PyFloat.cs b/src/runtime/PythonTypes/PyFloat.cs index c09ec93ba..50621d5c2 100644 --- a/src/runtime/PythonTypes/PyFloat.cs +++ b/src/runtime/PythonTypes/PyFloat.cs @@ -8,7 +8,7 @@ namespace Python.Runtime /// PY3: https://docs.python.org/3/c-api/float.html /// for details. /// - public class PyFloat : PyNumber + public partial class PyFloat : PyNumber { internal PyFloat(in StolenReference ptr) : base(ptr) { @@ -100,6 +100,8 @@ public static PyFloat AsFloat(PyObject value) return new PyFloat(op.Steal()); } + public double ToDouble() => Runtime.PyFloat_AsDouble(obj); + public override TypeCode GetTypeCode() => TypeCode.Double; } } diff --git a/src/runtime/PythonTypes/PyInt.IComparable.cs b/src/runtime/PythonTypes/PyInt.IComparable.cs new file mode 100644 index 000000000..a96f02e10 --- /dev/null +++ b/src/runtime/PythonTypes/PyInt.IComparable.cs @@ -0,0 +1,136 @@ +using System; + +namespace Python.Runtime; + +partial class PyInt : IComparable, IComparable, IComparable, IComparable + , IComparable, IComparable, IComparable, IComparable + , IEquatable, IEquatable, IEquatable, IEquatable + , IEquatable, IEquatable, IEquatable, IEquatable + , IComparable, IEquatable +{ + public override bool Equals(object o) + { + using var _ = Py.GIL(); + return o switch + { + long i64 => this.Equals(i64), + int i32 => this.Equals(i32), + short i16 => this.Equals(i16), + sbyte i8 => this.Equals(i8), + + ulong u64 => this.Equals(u64), + uint u32 => this.Equals(u32), + ushort u16 => this.Equals(u16), + byte u8 => this.Equals(u8), + + _ => base.Equals(o), + }; + } + + #region Signed + public int CompareTo(long other) + { + using var pyOther = Runtime.PyInt_FromInt64(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public int CompareTo(int other) + { + using var pyOther = Runtime.PyInt_FromInt32(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public int CompareTo(short other) + { + using var pyOther = Runtime.PyInt_FromInt32(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public int CompareTo(sbyte other) + { + using var pyOther = Runtime.PyInt_FromInt32(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public bool Equals(long other) + { + using var pyOther = Runtime.PyInt_FromInt64(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + + public bool Equals(int other) + { + using var pyOther = Runtime.PyInt_FromInt32(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + + public bool Equals(short other) + { + using var pyOther = Runtime.PyInt_FromInt32(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + + public bool Equals(sbyte other) + { + using var pyOther = Runtime.PyInt_FromInt32(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + #endregion Signed + + #region Unsigned + public int CompareTo(ulong other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public int CompareTo(uint other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public int CompareTo(ushort other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public int CompareTo(byte other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.CompareTo(pyOther.BorrowOrThrow()); + } + + public bool Equals(ulong other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + + public bool Equals(uint other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + + public bool Equals(ushort other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + + public bool Equals(byte other) + { + using var pyOther = Runtime.PyLong_FromUnsignedLongLong(other); + return this.Equals(pyOther.BorrowOrThrow()); + } + #endregion Unsigned + + public int CompareTo(PyInt? other) + { + return other is null ? 1 : this.CompareTo(other.BorrowNullable()); + } + + public bool Equals(PyInt? other) => base.Equals(other); +} diff --git a/src/runtime/PythonTypes/PyInt.cs b/src/runtime/PythonTypes/PyInt.cs index e71462b74..0d00f5a13 100644 --- a/src/runtime/PythonTypes/PyInt.cs +++ b/src/runtime/PythonTypes/PyInt.cs @@ -9,7 +9,7 @@ namespace Python.Runtime /// Represents a Python integer object. /// See the documentation at https://docs.python.org/3/c-api/long.html /// - public class PyInt : PyNumber, IFormattable + public partial class PyInt : PyNumber, IFormattable { internal PyInt(in StolenReference ptr) : base(ptr) { diff --git a/src/runtime/PythonTypes/PyObject.cs b/src/runtime/PythonTypes/PyObject.cs index bda2d9c02..cf0c2a03f 100644 --- a/src/runtime/PythonTypes/PyObject.cs +++ b/src/runtime/PythonTypes/PyObject.cs @@ -1136,6 +1136,23 @@ public long Refcount } } + internal int CompareTo(BorrowedReference other) + { + int greater = Runtime.PyObject_RichCompareBool(this.Reference, other, Runtime.Py_GT); + Debug.Assert(greater != -1); + if (greater > 0) + return 1; + int less = Runtime.PyObject_RichCompareBool(this.Reference, other, Runtime.Py_LT); + Debug.Assert(less != -1); + return less > 0 ? -1 : 0; + } + + internal bool Equals(BorrowedReference other) + { + int equal = Runtime.PyObject_RichCompareBool(this.Reference, other, Runtime.Py_EQ); + Debug.Assert(equal != -1); + return equal > 0; + } public override bool TryGetMember(GetMemberBinder binder, out object? result) { @@ -1325,7 +1342,7 @@ private bool TryCompare(PyObject arg, int op, out object @out) } return true; } - + public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result) { using var _ = Py.GIL(); diff --git a/src/runtime/PythonTypes/PyString.cs b/src/runtime/PythonTypes/PyString.cs index d54397fcf..6fed25c3e 100644 --- a/src/runtime/PythonTypes/PyString.cs +++ b/src/runtime/PythonTypes/PyString.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Runtime.Serialization; namespace Python.Runtime @@ -13,7 +14,7 @@ namespace Python.Runtime /// 2011-01-29: ...Then why does the string constructor call PyUnicode_FromUnicode()??? /// [Serializable] - public class PyString : PySequence + public class PyString : PySequence, IComparable, IEquatable { internal PyString(in StolenReference reference) : base(reference) { } internal PyString(BorrowedReference reference) : base(reference) { } @@ -61,5 +62,23 @@ public static bool IsStringType(PyObject value) } public override TypeCode GetTypeCode() => TypeCode.String; + + internal string ToStringUnderGIL() + { + string? result = Runtime.GetManagedString(this.Reference); + Debug.Assert(result is not null); + return result!; + } + + public bool Equals(string? other) + => this.ToStringUnderGIL().Equals(other, StringComparison.CurrentCulture); + public int CompareTo(string? other) + => string.Compare(this.ToStringUnderGIL(), other, StringComparison.CurrentCulture); + + public override string ToString() + { + using var _ = Py.GIL(); + return this.ToStringUnderGIL(); + } } } From 6a8a97d0fc78ec11c754f8d8746c672cacefc586 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 5 May 2024 20:42:02 +0200 Subject: [PATCH 062/194] Fix CI by using macos-13 explicitly, adjust OS config (#2373) --- .github/workflows/main.yml | 48 +++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c4af10c68..3396b83cc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,25 +9,35 @@ on: jobs: build-test: name: Build and Test - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os.instance }} timeout-minutes: 15 strategy: fail-fast: false matrix: - os: [windows, ubuntu, macos] - python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] - platform: [x64, x86] - exclude: - - os: ubuntu - platform: x86 - - os: macos + os: + - category: windows platform: x86 + instance: windows-latest + + - category: windows + platform: x64 + instance: windows-latest + + - category: ubuntu + platform: x64 + instance: ubuntu-latest + + - category: macos + platform: x64 + instance: macos-13 + + python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - name: Set Environment on macOS uses: maxim-lobanov/setup-xamarin@v1 - if: ${{ matrix.os == 'macos' }} + if: ${{ matrix.os.category == 'macos' }} with: mono-version: latest @@ -43,7 +53,7 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - architecture: ${{ matrix.platform }} + architecture: ${{ matrix.os.platform }} - name: Install dependencies run: | @@ -55,42 +65,42 @@ jobs: pip install -v . - name: Set Python DLL path and PYTHONHOME (non Windows) - if: ${{ matrix.os != '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 Python DLL path and PYTHONHOME (Windows) - if: ${{ matrix.os == '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: Embedding tests - run: dotnet test --runtime any-${{ matrix.platform }} --logger "console;verbosity=detailed" src/embed_tests/ + run: dotnet test --runtime any-${{ matrix.os.platform }} --logger "console;verbosity=detailed" src/embed_tests/ env: MONO_THREADS_SUSPEND: preemptive # https://github.com/mono/mono/issues/21466 - name: Python Tests (Mono) - if: ${{ matrix.os != 'windows' }} + if: ${{ matrix.os.category != 'windows' }} run: pytest --runtime mono # TODO: Run these tests on Windows x86 - name: Python Tests (.NET Core) - if: ${{ matrix.platform == 'x64' }} + if: ${{ matrix.os.platform == 'x64' }} run: pytest --runtime coreclr - name: Python Tests (.NET Framework) - if: ${{ matrix.os == 'windows' }} + if: ${{ matrix.os.category == 'windows' }} run: pytest --runtime netfx - name: Python tests run from .NET - run: dotnet test --runtime any-${{ matrix.platform }} src/python_tests_runner/ + run: dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/ - name: Perf tests - if: ${{ (matrix.python == '3.8') && (matrix.platform == 'x64') }} + 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.platform }} --logger "console;verbosity=detailed" src/perf_tests/ + dotnet test --configuration Release --runtime any-${{ matrix.os.platform }} --logger "console;verbosity=detailed" src/perf_tests/ # TODO: Run mono tests on Windows? From 195cde67fffd06521f3bcb2294e60cad4ec506d6 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 10 May 2024 19:28:38 +0200 Subject: [PATCH 063/194] Use non-BOM encodings (#2370) * Use non-BOM encodings The documentation of the used `PyUnicode_DecodeUTF16` states that not passing `*byteorder` or passing a 0 results in the first two bytes, if they are the BOM (U+FEFF, zero-width no-break space), to be interpreted and skipped, which is incorrect when we convert a known "non BOM" string, which all strings from C# are. --- src/embed_tests/TestPyType.cs | 2 +- src/runtime/Loader.cs | 6 ++-- src/runtime/Native/CustomMarshaler.cs | 2 +- src/runtime/Native/NativeTypeSpec.cs | 2 +- src/runtime/PythonTypes/PyType.cs | 2 +- src/runtime/Runtime.cs | 46 ++++++++++++++------------- src/runtime/Util/Encodings.cs | 10 ++++++ tests/test_conversion.py | 3 ++ 8 files changed, 44 insertions(+), 29 deletions(-) create mode 100644 src/runtime/Util/Encodings.cs diff --git a/src/embed_tests/TestPyType.cs b/src/embed_tests/TestPyType.cs index 34645747d..0470070c3 100644 --- a/src/embed_tests/TestPyType.cs +++ b/src/embed_tests/TestPyType.cs @@ -28,7 +28,7 @@ public void CanCreateHeapType() const string name = "nÁmæ"; const string docStr = "dÁcæ"; - using var doc = new StrPtr(docStr, Encoding.UTF8); + using var doc = new StrPtr(docStr, Encodings.UTF8); var spec = new TypeSpec( name: name, basicSize: Util.ReadInt32(Runtime.Runtime.PyBaseObjectType, TypeOffset.tp_basicsize), diff --git a/src/runtime/Loader.cs b/src/runtime/Loader.cs index 516b9ab9c..c0e964abc 100644 --- a/src/runtime/Loader.cs +++ b/src/runtime/Loader.cs @@ -12,7 +12,7 @@ public unsafe static int Initialize(IntPtr data, int size) { try { - var dllPath = Encoding.UTF8.GetString((byte*)data.ToPointer(), size); + var dllPath = Encodings.UTF8.GetString((byte*)data.ToPointer(), size); if (!string.IsNullOrEmpty(dllPath)) { @@ -33,7 +33,7 @@ public unsafe static int Initialize(IntPtr data, int size) ); return 1; } - + return 0; } @@ -41,7 +41,7 @@ public unsafe static int Shutdown(IntPtr data, int size) { try { - var command = Encoding.UTF8.GetString((byte*)data.ToPointer(), size); + var command = Encodings.UTF8.GetString((byte*)data.ToPointer(), size); if (command == "full_shutdown") { diff --git a/src/runtime/Native/CustomMarshaler.cs b/src/runtime/Native/CustomMarshaler.cs index 62c027150..299af3a33 100644 --- a/src/runtime/Native/CustomMarshaler.cs +++ b/src/runtime/Native/CustomMarshaler.cs @@ -42,7 +42,7 @@ public int GetNativeDataSize() internal class UcsMarshaler : MarshalerBase { internal static readonly int _UCS = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 2 : 4; - internal static readonly Encoding PyEncoding = _UCS == 2 ? Encoding.Unicode : Encoding.UTF32; + internal static readonly Encoding PyEncoding = _UCS == 2 ? Encodings.UTF16 : Encodings.UTF32; private static readonly MarshalerBase Instance = new UcsMarshaler(); public override IntPtr MarshalManagedToNative(object managedObj) diff --git a/src/runtime/Native/NativeTypeSpec.cs b/src/runtime/Native/NativeTypeSpec.cs index 8b84df536..50019a148 100644 --- a/src/runtime/Native/NativeTypeSpec.cs +++ b/src/runtime/Native/NativeTypeSpec.cs @@ -17,7 +17,7 @@ public NativeTypeSpec(TypeSpec spec) { if (spec is null) throw new ArgumentNullException(nameof(spec)); - this.Name = new StrPtr(spec.Name, Encoding.UTF8); + this.Name = new StrPtr(spec.Name, Encodings.UTF8); this.BasicSize = spec.BasicSize; this.ItemSize = spec.ItemSize; this.Flags = (int)spec.Flags; diff --git a/src/runtime/PythonTypes/PyType.cs b/src/runtime/PythonTypes/PyType.cs index af796a5c5..28bda5d3e 100644 --- a/src/runtime/PythonTypes/PyType.cs +++ b/src/runtime/PythonTypes/PyType.cs @@ -53,7 +53,7 @@ public string Name { RawPointer = Util.ReadIntPtr(this, TypeOffset.tp_name), }; - return namePtr.ToString(System.Text.Encoding.UTF8)!; + return namePtr.ToString(Encodings.UTF8)!; } } diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 4e1c6156a..2f9e18f65 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -795,13 +795,13 @@ public static int Py_Main(int argc, string[] argv) internal static int PyRun_SimpleString(string code) { - using var codePtr = new StrPtr(code, Encoding.UTF8); + using var codePtr = new StrPtr(code, Encodings.UTF8); return Delegates.PyRun_SimpleStringFlags(codePtr, Utf8String); } internal static NewReference PyRun_String(string code, RunFlagType st, BorrowedReference globals, BorrowedReference locals) { - using var codePtr = new StrPtr(code, Encoding.UTF8); + using var codePtr = new StrPtr(code, Encodings.UTF8); return Delegates.PyRun_StringFlags(codePtr, st, globals, locals, Utf8String); } @@ -813,14 +813,14 @@ internal static NewReference PyRun_String(string code, RunFlagType st, BorrowedR /// internal static NewReference Py_CompileString(string str, string file, int start) { - using var strPtr = new StrPtr(str, Encoding.UTF8); + using var strPtr = new StrPtr(str, Encodings.UTF8); using var fileObj = new PyString(file); return Delegates.Py_CompileStringObject(strPtr, fileObj, start, Utf8String, -1); } internal static NewReference PyImport_ExecCodeModule(string name, BorrowedReference code) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name, Encodings.UTF8); return Delegates.PyImport_ExecCodeModule(namePtr, code); } @@ -867,13 +867,13 @@ internal static bool PyObject_IsIterable(BorrowedReference ob) internal static int PyObject_HasAttrString(BorrowedReference pointer, string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name, Encodings.UTF8); return Delegates.PyObject_HasAttrString(pointer, namePtr); } internal static NewReference PyObject_GetAttrString(BorrowedReference pointer, string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name, Encodings.UTF8); return Delegates.PyObject_GetAttrString(pointer, namePtr); } @@ -884,12 +884,12 @@ internal static NewReference PyObject_GetAttrString(BorrowedReference pointer, S internal static int PyObject_DelAttr(BorrowedReference @object, BorrowedReference name) => Delegates.PyObject_SetAttr(@object, name, null); internal static int PyObject_DelAttrString(BorrowedReference @object, string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name, Encodings.UTF8); return Delegates.PyObject_SetAttrString(@object, namePtr, null); } internal static int PyObject_SetAttrString(BorrowedReference @object, string name, BorrowedReference value) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name, Encodings.UTF8); return Delegates.PyObject_SetAttrString(@object, namePtr, value); } @@ -1071,7 +1071,7 @@ internal static bool PyBool_CheckExact(BorrowedReference ob) internal static NewReference PyLong_FromString(string value, int radix) { - using var valPtr = new StrPtr(value, Encoding.UTF8); + using var valPtr = new StrPtr(value, Encodings.UTF8); return Delegates.PyLong_FromString(valPtr, IntPtr.Zero, radix); } @@ -1252,12 +1252,14 @@ internal static bool PyString_CheckExact(BorrowedReference ob) internal static NewReference PyString_FromString(string value) { + int byteorder = BitConverter.IsLittleEndian ? -1 : 1; + int* byteorderPtr = &byteorder; fixed(char* ptr = value) return Delegates.PyUnicode_DecodeUTF16( (IntPtr)ptr, value.Length * sizeof(Char), IntPtr.Zero, - IntPtr.Zero + (IntPtr)byteorderPtr ); } @@ -1272,7 +1274,7 @@ internal static NewReference EmptyPyBytes() internal static NewReference PyByteArray_FromStringAndSize(IntPtr strPtr, nint len) => Delegates.PyByteArray_FromStringAndSize(strPtr, len); internal static NewReference PyByteArray_FromStringAndSize(string s) { - using var ptr = new StrPtr(s, Encoding.UTF8); + using var ptr = new StrPtr(s, Encodings.UTF8); return PyByteArray_FromStringAndSize(ptr.RawPointer, checked((nint)ptr.ByteCount)); } @@ -1300,7 +1302,7 @@ internal static IntPtr PyBytes_AsString(BorrowedReference ob) internal static NewReference PyUnicode_InternFromString(string s) { - using var ptr = new StrPtr(s, Encoding.UTF8); + using var ptr = new StrPtr(s, Encodings.UTF8); return Delegates.PyUnicode_InternFromString(ptr); } @@ -1375,7 +1377,7 @@ internal static bool PyDict_Check(BorrowedReference ob) internal static BorrowedReference PyDict_GetItemString(BorrowedReference pointer, string key) { - using var keyStr = new StrPtr(key, Encoding.UTF8); + using var keyStr = new StrPtr(key, Encodings.UTF8); return Delegates.PyDict_GetItemString(pointer, keyStr); } @@ -1391,7 +1393,7 @@ internal static BorrowedReference PyDict_GetItemString(BorrowedReference pointer /// internal static int PyDict_SetItemString(BorrowedReference dict, string key, BorrowedReference value) { - using var keyPtr = new StrPtr(key, Encoding.UTF8); + using var keyPtr = new StrPtr(key, Encodings.UTF8); return Delegates.PyDict_SetItemString(dict, keyPtr, value); } @@ -1400,7 +1402,7 @@ internal static int PyDict_SetItemString(BorrowedReference dict, string key, Bor internal static int PyDict_DelItemString(BorrowedReference pointer, string key) { - using var keyPtr = new StrPtr(key, Encoding.UTF8); + using var keyPtr = new StrPtr(key, Encodings.UTF8); return Delegates.PyDict_DelItemString(pointer, keyPtr); } @@ -1515,7 +1517,7 @@ internal static bool PyIter_Check(BorrowedReference ob) internal static NewReference PyModule_New(string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name, Encodings.UTF8); return Delegates.PyModule_New(namePtr); } @@ -1529,7 +1531,7 @@ internal static NewReference PyModule_New(string name) /// Return -1 on error, 0 on success. internal static int PyModule_AddObject(BorrowedReference module, string name, StolenReference value) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name, Encodings.UTF8); IntPtr valueAddr = value.DangerousGetAddressOrNull(); int res = Delegates.PyModule_AddObject(module, namePtr, valueAddr); // We can't just exit here because the reference is stolen only on success. @@ -1547,7 +1549,7 @@ internal static int PyModule_AddObject(BorrowedReference module, string name, St internal static NewReference PyImport_ImportModule(string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name, Encodings.UTF8); return Delegates.PyImport_ImportModule(namePtr); } @@ -1556,7 +1558,7 @@ internal static NewReference PyImport_ImportModule(string name) internal static BorrowedReference PyImport_AddModule(string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name, Encodings.UTF8); return Delegates.PyImport_AddModule(namePtr); } @@ -1584,13 +1586,13 @@ internal static void PySys_SetArgvEx(int argc, string[] argv, int updatepath) internal static BorrowedReference PySys_GetObject(string name) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name, Encodings.UTF8); return Delegates.PySys_GetObject(namePtr); } internal static int PySys_SetObject(string name, BorrowedReference ob) { - using var namePtr = new StrPtr(name, Encoding.UTF8); + using var namePtr = new StrPtr(name, Encodings.UTF8); return Delegates.PySys_SetObject(namePtr, ob); } @@ -1689,7 +1691,7 @@ internal static IntPtr PyMem_Malloc(long size) internal static void PyErr_SetString(BorrowedReference ob, string message) { - using var msgPtr = new StrPtr(message, Encoding.UTF8); + using var msgPtr = new StrPtr(message, Encodings.UTF8); Delegates.PyErr_SetString(ob, msgPtr); } diff --git a/src/runtime/Util/Encodings.cs b/src/runtime/Util/Encodings.cs new file mode 100644 index 000000000..d5a0c6ff8 --- /dev/null +++ b/src/runtime/Util/Encodings.cs @@ -0,0 +1,10 @@ +using System; +using System.Text; + +namespace Python.Runtime; + +static class Encodings { + public static System.Text.Encoding UTF8 = new UTF8Encoding(false, true); + public static System.Text.Encoding UTF16 = new UnicodeEncoding(!BitConverter.IsLittleEndian, false, true); + public static System.Text.Encoding UTF32 = new UTF32Encoding(!BitConverter.IsLittleEndian, false, true); +} diff --git a/tests/test_conversion.py b/tests/test_conversion.py index bb686dd52..dd70f900a 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -510,6 +510,9 @@ def test_string_conversion(): ob.StringField = System.String(u'\uffff\uffff') assert ob.StringField == u'\uffff\uffff' + ob.StringField = System.String("\ufeffbom") + assert ob.StringField == "\ufeffbom" + ob.StringField = None assert ob.StringField is None From 32051cb3c7c2edffa031569043eac5aecaa573a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Bourbonnais?= <6788684+BadSingleton@users.noreply.github.com> Date: Fri, 10 May 2024 13:35:55 -0400 Subject: [PATCH 064/194] Expose serialization api (#2336) * Expose an API for users to specify their own formatter Adds post-serialization and pre-deserialization hooks for additional customization. * Add API for capsuling data when serializing * Add NoopFormatter and fall back to it if BinaryFormatter is not available --------- Co-authored-by: Benedikt Reinartz --- CHANGELOG.md | 3 + .../StateSerialization/NoopFormatter.cs | 14 ++ src/runtime/StateSerialization/RuntimeData.cs | 138 +++++++++++++++++- tests/domain_tests/TestRunner.cs | 117 +++++++++++++++ tests/domain_tests/test_domain_reload.py | 3 + 5 files changed, 269 insertions(+), 6 deletions(-) create mode 100644 src/runtime/StateSerialization/NoopFormatter.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index e6cc52d72..adef224e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. to compare with primitive .NET types like `long`. ### Changed +- Added a `FormatterFactory` member in RuntimeData to create formatters with parameters. For compatibility, the `FormatterType` member is still present and has precedence when defining both `FormatterFactory` and `FormatterType` +- Added a post-serialization and a pre-deserialization step callbacks to extend (de)serialization process +- Added an API to stash serialized data on Python capsules ### Fixed diff --git a/src/runtime/StateSerialization/NoopFormatter.cs b/src/runtime/StateSerialization/NoopFormatter.cs new file mode 100644 index 000000000..f05b7ebb2 --- /dev/null +++ b/src/runtime/StateSerialization/NoopFormatter.cs @@ -0,0 +1,14 @@ +using System; +using System.IO; +using System.Runtime.Serialization; + +namespace Python.Runtime; + +public class NoopFormatter : IFormatter { + public object Deserialize(Stream s) => throw new NotImplementedException(); + public void Serialize(Stream s, object o) {} + + public SerializationBinder? Binder { get; set; } + public StreamingContext Context { get; set; } + public ISurrogateSelector? SurrogateSelector { get; set; } +} diff --git a/src/runtime/StateSerialization/RuntimeData.cs b/src/runtime/StateSerialization/RuntimeData.cs index 204e15b5b..8eda9ce0b 100644 --- a/src/runtime/StateSerialization/RuntimeData.cs +++ b/src/runtime/StateSerialization/RuntimeData.cs @@ -1,7 +1,5 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; @@ -17,7 +15,34 @@ namespace Python.Runtime { public static class RuntimeData { - private static Type? _formatterType; + + public readonly static Func DefaultFormatterFactory = () => + { + try + { + return new BinaryFormatter(); + } + catch + { + return new NoopFormatter(); + } + }; + + private static Func _formatterFactory { get; set; } = DefaultFormatterFactory; + + public static Func FormatterFactory + { + get => _formatterFactory; + set + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + _formatterFactory = value; + } + } + + private static Type? _formatterType = null; public static Type? FormatterType { get => _formatterType; @@ -31,6 +56,14 @@ public static Type? FormatterType } } + /// + /// Callback called as a last step in the serialization process + /// + public static Action? PostStashHook { get; set; } = null; + /// + /// Callback called as the first step in the deserialization process + /// + public static Action? PreRestoreHook { get; set; } = null; public static ICLRObjectStorer? WrappersStorer { get; set; } /// @@ -74,6 +107,7 @@ internal static void Stash() using NewReference capsule = PyCapsule_New(mem, IntPtr.Zero, IntPtr.Zero); int res = PySys_SetObject("clr_data", capsule.BorrowOrThrow()); PythonException.ThrowIfIsNotZero(res); + PostStashHook?.Invoke(); } internal static void RestoreRuntimeData() @@ -90,6 +124,7 @@ internal static void RestoreRuntimeData() private static void RestoreRuntimeDataImpl() { + PreRestoreHook?.Invoke(); BorrowedReference capsule = PySys_GetObject("clr_data"); if (capsule.IsNull) { @@ -250,11 +285,102 @@ private static void RestoreRuntimeDataObjects(SharedObjectsState storage) } } + static readonly string serialization_key_namepsace = "pythonnet_serialization_"; + /// + /// Removes the serialization capsule from the `sys` module object. + /// + /// + /// The serialization data must have been set with StashSerializationData + /// + /// The name given to the capsule on the `sys` module object + public static void FreeSerializationData(string key) + { + key = serialization_key_namepsace + key; + BorrowedReference oldCapsule = PySys_GetObject(key); + if (!oldCapsule.IsNull) + { + IntPtr oldData = PyCapsule_GetPointer(oldCapsule, IntPtr.Zero); + Marshal.FreeHGlobal(oldData); + PyCapsule_SetPointer(oldCapsule, IntPtr.Zero); + PySys_SetObject(key, null); + } + } + + /// + /// Stores the data in the argument in a Python capsule and stores + /// the capsule on the `sys` module object with the name . + /// + /// + /// No checks on pre-existing names on the `sys` module object are made. + /// + /// The name given to the capsule on the `sys` module object + /// A MemoryStream that contains the data to be placed in the capsule + public static void StashSerializationData(string key, MemoryStream stream) + { + if (stream.TryGetBuffer(out var data)) + { + IntPtr mem = Marshal.AllocHGlobal(IntPtr.Size + data.Count); + + // store the length of the buffer first + Marshal.WriteIntPtr(mem, (IntPtr)data.Count); + Marshal.Copy(data.Array, data.Offset, mem + IntPtr.Size, data.Count); + + try + { + using NewReference capsule = PyCapsule_New(mem, IntPtr.Zero, IntPtr.Zero); + int res = PySys_SetObject(key, capsule.BorrowOrThrow()); + PythonException.ThrowIfIsNotZero(res); + } + catch + { + Marshal.FreeHGlobal(mem); + } + } + else + { + throw new NotImplementedException($"{nameof(stream)} must be exposable"); + } + + } + + static byte[] emptyBuffer = new byte[0]; + /// + /// Retreives the previously stored data on a Python capsule. + /// Throws if the object corresponding to the parameter + /// on the `sys` module object is not a capsule. + /// + /// The name given to the capsule on the `sys` module object + /// A MemoryStream containing the previously saved serialization data. + /// The stream is empty if no name matches the key. + public static MemoryStream GetSerializationData(string key) + { + BorrowedReference capsule = PySys_GetObject(key); + if (capsule.IsNull) + { + // nothing to do. + return new MemoryStream(emptyBuffer, writable:false); + } + var ptr = PyCapsule_GetPointer(capsule, IntPtr.Zero); + if (ptr == IntPtr.Zero) + { + // The PyCapsule API returns NULL on error; NULL cannot be stored + // as a capsule's value + PythonException.ThrowIfIsNull(null); + } + var len = (int)Marshal.ReadIntPtr(ptr); + byte[] buffer = new byte[len]; + Marshal.Copy(ptr+IntPtr.Size, buffer, 0, len); + return new MemoryStream(buffer, writable:false); + } + internal static IFormatter CreateFormatter() { - return FormatterType != null ? - (IFormatter)Activator.CreateInstance(FormatterType) - : new BinaryFormatter(); + + if (FormatterType != null) + { + return (IFormatter)Activator.CreateInstance(FormatterType); + } + return FormatterFactory(); } } } diff --git a/tests/domain_tests/TestRunner.cs b/tests/domain_tests/TestRunner.cs index 4f6a3ea28..bbee81b3d 100644 --- a/tests/domain_tests/TestRunner.cs +++ b/tests/domain_tests/TestRunner.cs @@ -1132,6 +1132,66 @@ import System ", }, + new TestCase + { + Name = "test_serialize_unserializable_object", + DotNetBefore = @" + namespace TestNamespace + { + public class NotSerializableTextWriter : System.IO.TextWriter + { + override public System.Text.Encoding Encoding { get { return System.Text.Encoding.ASCII;} } + } + [System.Serializable] + public static class SerializableWriter + { + private static System.IO.TextWriter _writer = null; + public static System.IO.TextWriter Writer {get { return _writer; }} + public static void CreateInternalWriter() + { + _writer = System.IO.TextWriter.Synchronized(new NotSerializableTextWriter()); + } + } + } +", + DotNetAfter = @" + namespace TestNamespace + { + public class NotSerializableTextWriter : System.IO.TextWriter + { + override public System.Text.Encoding Encoding { get { return System.Text.Encoding.ASCII;} } + } + [System.Serializable] + public static class SerializableWriter + { + private static System.IO.TextWriter _writer = null; + public static System.IO.TextWriter Writer {get { return _writer; }} + public static void CreateInternalWriter() + { + _writer = System.IO.TextWriter.Synchronized(new NotSerializableTextWriter()); + } + } + } + ", + PythonCode = @" +import sys + +def before_reload(): + import clr + import System + clr.AddReference('DomainTests') + import TestNamespace + TestNamespace.SerializableWriter.CreateInternalWriter(); + sys.__obj = TestNamespace.SerializableWriter.Writer + sys.__obj.WriteLine('test') + +def after_reload(): + import clr + import System + sys.__obj.WriteLine('test') + + ", + } }; /// @@ -1142,7 +1202,59 @@ import System const string CaseRunnerTemplate = @" using System; using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; using Python.Runtime; + +namespace Serialization +{{ + // Classes in this namespace is mostly useful for test_serialize_unserializable_object + class NotSerializableSerializer : ISerializationSurrogate + {{ + public NotSerializableSerializer() + {{ + }} + public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) + {{ + info.AddValue(""notSerialized_tp"", obj.GetType()); + }} + public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) + {{ + if (info == null) + {{ + return null; + }} + Type typeObj = info.GetValue(""notSerialized_tp"", typeof(Type)) as Type; + if (typeObj == null) + {{ + return null; + }} + + obj = Activator.CreateInstance(typeObj); + return obj; + }} + }} + class NonSerializableSelector : SurrogateSelector + {{ + public override ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector) + {{ + if (type == null) + {{ + throw new ArgumentNullException(); + }} + selector = (ISurrogateSelector)this; + if (type.IsSerializable) + {{ + return null; // use whichever default + }} + else + {{ + return (ISerializationSurrogate)(new NotSerializableSerializer()); + }} + }} + }} +}} + namespace CaseRunner {{ class CaseRunner @@ -1151,6 +1263,11 @@ public static int Main() {{ try {{ + RuntimeData.FormatterFactory = () => + {{ + return new BinaryFormatter(){{SurrogateSelector = new Serialization.NonSerializableSelector()}}; + }}; + PythonEngine.Initialize(); using (Py.GIL()) {{ diff --git a/tests/domain_tests/test_domain_reload.py b/tests/domain_tests/test_domain_reload.py index 8999e481b..1e5e8e81b 100644 --- a/tests/domain_tests/test_domain_reload.py +++ b/tests/domain_tests/test_domain_reload.py @@ -88,3 +88,6 @@ def test_nested_type(): def test_import_after_reload(): _run_test("import_after_reload") + +def test_import_after_reload(): + _run_test("test_serialize_unserializable_object") \ No newline at end of file From b112885d19091f0e5fe1e7609236d6093fbd5a0b Mon Sep 17 00:00:00 2001 From: Victor Date: Sat, 11 May 2024 00:34:26 -0700 Subject: [PATCH 065/194] handle bad paths in sys.path (#2383) fixes #2376 --- CHANGELOG.md | 1 + src/runtime/AssemblyManager.cs | 7 +++++++ src/runtime/Exceptions.cs | 4 ++-- tests/test_module.py | 14 ++++++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adef224e0..23184258d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Fixed - Fixed RecursionError for reverse operators on C# operable types from python. See #2240 +- Fixed probing for assemblies in `sys.path` failing when a path in `sys.path` has invalid characters. See #2376 ## [3.0.3](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.3) - 2023-10-11 diff --git a/src/runtime/AssemblyManager.cs b/src/runtime/AssemblyManager.cs index a8bbd1f6c..82658bf50 100644 --- a/src/runtime/AssemblyManager.cs +++ b/src/runtime/AssemblyManager.cs @@ -200,6 +200,13 @@ static IEnumerable FindAssemblyCandidates(string name) } else { + int invalidCharIndex = head.IndexOfAny(Path.GetInvalidPathChars()); + if (invalidCharIndex >= 0) + { + using var importWarning = Runtime.PyObject_GetAttrString(Exceptions.exceptions_module, "ImportWarning"); + Exceptions.warn($"Path entry '{head}' has invalid char at position {invalidCharIndex}", importWarning.BorrowOrThrow()); + continue; + } path = Path.Combine(head, name); } diff --git a/src/runtime/Exceptions.cs b/src/runtime/Exceptions.cs index da095e030..85e56eace 100644 --- a/src/runtime/Exceptions.cs +++ b/src/runtime/Exceptions.cs @@ -270,7 +270,7 @@ public static void warn(string message, BorrowedReference exception, int stackle } using var warn = Runtime.PyObject_GetAttrString(warnings_module.obj, "warn"); - Exceptions.ErrorCheck(warn.Borrow()); + warn.BorrowOrThrow(); using var argsTemp = Runtime.PyTuple_New(3); BorrowedReference args = argsTemp.BorrowOrThrow(); @@ -283,7 +283,7 @@ public static void warn(string message, BorrowedReference exception, int stackle Runtime.PyTuple_SetItem(args, 2, level.StealOrThrow()); using var result = Runtime.PyObject_CallObject(warn.Borrow(), args); - Exceptions.ErrorCheck(result.Borrow()); + result.BorrowOrThrow(); } public static void warn(string message, BorrowedReference exception) diff --git a/tests/test_module.py b/tests/test_module.py index ddfa7bb36..0c20dcfc0 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -344,6 +344,20 @@ def test_clr_add_reference(): with pytest.raises(FileNotFoundException): AddReference("somethingtotallysilly") + +def test_clr_add_reference_bad_path(): + import sys + from clr import AddReference + from System.IO import FileNotFoundException + bad_path = "hello\0world" + sys.path.append(bad_path) + try: + with pytest.raises(FileNotFoundException): + AddReference("test_clr_add_reference_bad_path") + finally: + sys.path.remove(bad_path) + + def test_clr_get_clr_type(): """Test clr.GetClrType().""" from clr import GetClrType From 4e5afdf973e29f1ae50413aa0cc092f2a03df68f Mon Sep 17 00:00:00 2001 From: Frank Witscher Date: Mon, 13 May 2024 16:25:11 +0200 Subject: [PATCH 066/194] Fix access violation exception on shutdown (#1977) When nulling the GC handles on shutdown the reference count of all objects pointed to by the IntPtr in the `CLRObject.reflectedObjects` are zero. This caused an exception in some scenarios because `Runtime.PyObject_TYPE(reflectedClrObject)` is called while the reference counter is at zero. After `TypeManager.RemoveTypes();` is called in the `Runtime.Shutdown()` method, reference count decrements to zero do not invoke `ClassBase.tp_clear` for managed objects anymore which normally is responsible for removing references from `CLRObject.reflectedObjects`. Collecting objects referenced in `CLRObject.reflectedObjects` only after leads to an unstable state in which the reference count for these object addresses is zero while still maintaining them to be used for further pseudo-cleanup. In that time, the memory could have been reclaimed already which leads to the exception. --- AUTHORS.md | 1 + CHANGELOG.md | 2 ++ src/runtime/Runtime.cs | 5 +++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 18435671c..6aa4a6010 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -38,6 +38,7 @@ - Dmitriy Se ([@dmitriyse](https://github.com/dmitriyse)) - Félix Bourbonnais ([@BadSingleton](https://github.com/BadSingleton)) - Florian Treurniet ([@ftreurni](https://github.com/ftreurni)) +- Frank Witscher ([@Frawak](https://github.com/Frawak)) - He-chien Tsai ([@t3476](https://github.com/t3476)) - Inna Wiesel ([@inna-w](https://github.com/inna-w)) - Ivan Cronyn ([@cronan](https://github.com/cronan)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23184258d..7d2faa1b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Fixed RecursionError for reverse operators on C# operable types from python. See #2240 - Fixed probing for assemblies in `sys.path` failing when a path in `sys.path` has invalid characters. See #2376 +- Fixed possible access violation exception on shutdown. See ([#1977][i1977]) ## [3.0.3](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.3) - 2023-10-11 @@ -970,3 +971,4 @@ This version improves performance on benchmarks significantly compared to 2.3. [i1481]: https://github.com/pythonnet/pythonnet/issues/1481 [i1672]: https://github.com/pythonnet/pythonnet/pull/1672 [i2311]: https://github.com/pythonnet/pythonnet/issues/2311 +[i1977]: https://github.com/pythonnet/pythonnet/issues/1977 diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 2f9e18f65..a65fea66f 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -278,6 +278,8 @@ internal static void Shutdown() ClearClrModules(); RemoveClrRootModule(); + TryCollectingGarbage(MaxCollectRetriesOnShutdown, forceBreakLoops: true); + NullGCHandles(ExtensionType.loadedExtensions); ClassManager.RemoveClasses(); TypeManager.RemoveTypes(); @@ -295,8 +297,7 @@ internal static void Shutdown() PyObjectConversions.Reset(); PyGC_Collect(); - bool everythingSeemsCollected = TryCollectingGarbage(MaxCollectRetriesOnShutdown, - forceBreakLoops: true); + bool everythingSeemsCollected = TryCollectingGarbage(MaxCollectRetriesOnShutdown); Debug.Assert(everythingSeemsCollected); Finalizer.Shutdown(); From 6f0f6713e8f55a24ea7803584c5490eca0518739 Mon Sep 17 00:00:00 2001 From: Frank Witscher Date: Mon, 13 May 2024 22:38:59 +0200 Subject: [PATCH 067/194] Restrict first garbage collection Otherwise, collecting all at this earlier point results in corrupt memory for derived types. --- src/runtime/Finalizer.cs | 8 ++++---- src/runtime/Runtime.cs | 10 +++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/runtime/Finalizer.cs b/src/runtime/Finalizer.cs index 713564f08..5b5ecfcfc 100644 --- a/src/runtime/Finalizer.cs +++ b/src/runtime/Finalizer.cs @@ -191,7 +191,7 @@ internal static void Shutdown() Instance.started = false; } - internal nint DisposeAll() + internal nint DisposeAll(bool disposeObj = true, bool disposeDerived = true, bool disposeBuffer = true) { if (_objQueue.IsEmpty && _derivedQueue.IsEmpty && _bufferQueue.IsEmpty) return 0; @@ -216,7 +216,7 @@ internal nint DisposeAll() try { - while (!_objQueue.IsEmpty) + if (disposeObj) while (!_objQueue.IsEmpty) { if (!_objQueue.TryDequeue(out var obj)) continue; @@ -240,7 +240,7 @@ internal nint DisposeAll() } } - while (!_derivedQueue.IsEmpty) + if (disposeDerived) while (!_derivedQueue.IsEmpty) { if (!_derivedQueue.TryDequeue(out var derived)) continue; @@ -258,7 +258,7 @@ internal nint DisposeAll() collected++; } - while (!_bufferQueue.IsEmpty) + if (disposeBuffer) while (!_bufferQueue.IsEmpty) { if (!_bufferQueue.TryDequeue(out var buffer)) continue; diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index a65fea66f..b3820270c 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -278,7 +278,8 @@ internal static void Shutdown() ClearClrModules(); RemoveClrRootModule(); - TryCollectingGarbage(MaxCollectRetriesOnShutdown, forceBreakLoops: true); + TryCollectingGarbage(MaxCollectRetriesOnShutdown, forceBreakLoops: true, + obj: true, derived: false, buffer: false); NullGCHandles(ExtensionType.loadedExtensions); ClassManager.RemoveClasses(); @@ -329,7 +330,8 @@ internal static void Shutdown() const int MaxCollectRetriesOnShutdown = 20; internal static int _collected; - static bool TryCollectingGarbage(int runs, bool forceBreakLoops) + static bool TryCollectingGarbage(int runs, bool forceBreakLoops, + bool obj = true, bool derived = true, bool buffer = true) { if (runs <= 0) throw new ArgumentOutOfRangeException(nameof(runs)); @@ -342,7 +344,9 @@ static bool TryCollectingGarbage(int runs, bool forceBreakLoops) GC.Collect(); GC.WaitForPendingFinalizers(); pyCollected += PyGC_Collect(); - pyCollected += Finalizer.Instance.DisposeAll(); + pyCollected += Finalizer.Instance.DisposeAll(disposeObj: obj, + disposeDerived: derived, + disposeBuffer: buffer); } if (Volatile.Read(ref _collected) == 0 && pyCollected == 0) { From f82aeea80cd463996d3ab376c94a57a8d2d7e774 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 9 Jun 2024 01:32:00 +0200 Subject: [PATCH 068/194] Simplify UTF8 StrPtr usage (#2374) * Use non-BOM encodings * Copy potential BOM to the output of PyString_FromString The documentation of the used `PyUnicode_DecodeUTF16` states that not passing `*byteorder` or passing a 0 results in the first two bytes, if they are the BOM (U+FEFF, zero-width no-break space), to be interpreted and skipped, which is incorrect when we convert a known "non BOM" string, which all strings from C# are. * Default to UTF8 for StrPtr --- src/embed_tests/TestPyType.cs | 3 +- src/runtime/Native/NativeTypeSpec.cs | 2 +- src/runtime/Native/StrPtr.cs | 2 ++ src/runtime/Runtime.cs | 43 ++++++++++++++-------------- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/embed_tests/TestPyType.cs b/src/embed_tests/TestPyType.cs index 0470070c3..d98dfda2e 100644 --- a/src/embed_tests/TestPyType.cs +++ b/src/embed_tests/TestPyType.cs @@ -28,7 +28,8 @@ public void CanCreateHeapType() const string name = "nÁmæ"; const string docStr = "dÁcæ"; - using var doc = new StrPtr(docStr, Encodings.UTF8); + using var doc = new StrPtr(docStr); + var spec = new TypeSpec( name: name, basicSize: Util.ReadInt32(Runtime.Runtime.PyBaseObjectType, TypeOffset.tp_basicsize), diff --git a/src/runtime/Native/NativeTypeSpec.cs b/src/runtime/Native/NativeTypeSpec.cs index 50019a148..90e07afd7 100644 --- a/src/runtime/Native/NativeTypeSpec.cs +++ b/src/runtime/Native/NativeTypeSpec.cs @@ -17,7 +17,7 @@ public NativeTypeSpec(TypeSpec spec) { if (spec is null) throw new ArgumentNullException(nameof(spec)); - this.Name = new StrPtr(spec.Name, Encodings.UTF8); + this.Name = new StrPtr(spec.Name); this.BasicSize = spec.BasicSize; this.ItemSize = spec.ItemSize; this.Flags = (int)spec.Flags; diff --git a/src/runtime/Native/StrPtr.cs b/src/runtime/Native/StrPtr.cs index 4f73be9b5..c9f4db660 100644 --- a/src/runtime/Native/StrPtr.cs +++ b/src/runtime/Native/StrPtr.cs @@ -10,6 +10,8 @@ struct StrPtr : IDisposable public IntPtr RawPointer { get; set; } unsafe byte* Bytes => (byte*)this.RawPointer; + public unsafe StrPtr(string value) : this(value, Encodings.UTF8) {} + public unsafe StrPtr(string value, Encoding encoding) { if (value is null) throw new ArgumentNullException(nameof(value)); diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 2f9e18f65..a26ad67a9 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -795,13 +795,13 @@ public static int Py_Main(int argc, string[] argv) internal static int PyRun_SimpleString(string code) { - using var codePtr = new StrPtr(code, Encodings.UTF8); + using var codePtr = new StrPtr(code); return Delegates.PyRun_SimpleStringFlags(codePtr, Utf8String); } internal static NewReference PyRun_String(string code, RunFlagType st, BorrowedReference globals, BorrowedReference locals) { - using var codePtr = new StrPtr(code, Encodings.UTF8); + using var codePtr = new StrPtr(code); return Delegates.PyRun_StringFlags(codePtr, st, globals, locals, Utf8String); } @@ -813,14 +813,15 @@ internal static NewReference PyRun_String(string code, RunFlagType st, BorrowedR /// internal static NewReference Py_CompileString(string str, string file, int start) { - using var strPtr = new StrPtr(str, Encodings.UTF8); + using var strPtr = new StrPtr(str); + using var fileObj = new PyString(file); return Delegates.Py_CompileStringObject(strPtr, fileObj, start, Utf8String, -1); } internal static NewReference PyImport_ExecCodeModule(string name, BorrowedReference code) { - using var namePtr = new StrPtr(name, Encodings.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyImport_ExecCodeModule(namePtr, code); } @@ -867,13 +868,13 @@ internal static bool PyObject_IsIterable(BorrowedReference ob) internal static int PyObject_HasAttrString(BorrowedReference pointer, string name) { - using var namePtr = new StrPtr(name, Encodings.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyObject_HasAttrString(pointer, namePtr); } internal static NewReference PyObject_GetAttrString(BorrowedReference pointer, string name) { - using var namePtr = new StrPtr(name, Encodings.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyObject_GetAttrString(pointer, namePtr); } @@ -884,12 +885,12 @@ internal static NewReference PyObject_GetAttrString(BorrowedReference pointer, S internal static int PyObject_DelAttr(BorrowedReference @object, BorrowedReference name) => Delegates.PyObject_SetAttr(@object, name, null); internal static int PyObject_DelAttrString(BorrowedReference @object, string name) { - using var namePtr = new StrPtr(name, Encodings.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyObject_SetAttrString(@object, namePtr, null); } internal static int PyObject_SetAttrString(BorrowedReference @object, string name, BorrowedReference value) { - using var namePtr = new StrPtr(name, Encodings.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyObject_SetAttrString(@object, namePtr, value); } @@ -1071,7 +1072,7 @@ internal static bool PyBool_CheckExact(BorrowedReference ob) internal static NewReference PyLong_FromString(string value, int radix) { - using var valPtr = new StrPtr(value, Encodings.UTF8); + using var valPtr = new StrPtr(value); return Delegates.PyLong_FromString(valPtr, IntPtr.Zero, radix); } @@ -1274,7 +1275,7 @@ internal static NewReference EmptyPyBytes() internal static NewReference PyByteArray_FromStringAndSize(IntPtr strPtr, nint len) => Delegates.PyByteArray_FromStringAndSize(strPtr, len); internal static NewReference PyByteArray_FromStringAndSize(string s) { - using var ptr = new StrPtr(s, Encodings.UTF8); + using var ptr = new StrPtr(s); return PyByteArray_FromStringAndSize(ptr.RawPointer, checked((nint)ptr.ByteCount)); } @@ -1302,7 +1303,7 @@ internal static IntPtr PyBytes_AsString(BorrowedReference ob) internal static NewReference PyUnicode_InternFromString(string s) { - using var ptr = new StrPtr(s, Encodings.UTF8); + using var ptr = new StrPtr(s); return Delegates.PyUnicode_InternFromString(ptr); } @@ -1377,7 +1378,7 @@ internal static bool PyDict_Check(BorrowedReference ob) internal static BorrowedReference PyDict_GetItemString(BorrowedReference pointer, string key) { - using var keyStr = new StrPtr(key, Encodings.UTF8); + using var keyStr = new StrPtr(key); return Delegates.PyDict_GetItemString(pointer, keyStr); } @@ -1393,7 +1394,7 @@ internal static BorrowedReference PyDict_GetItemString(BorrowedReference pointer /// internal static int PyDict_SetItemString(BorrowedReference dict, string key, BorrowedReference value) { - using var keyPtr = new StrPtr(key, Encodings.UTF8); + using var keyPtr = new StrPtr(key); return Delegates.PyDict_SetItemString(dict, keyPtr, value); } @@ -1402,7 +1403,7 @@ internal static int PyDict_SetItemString(BorrowedReference dict, string key, Bor internal static int PyDict_DelItemString(BorrowedReference pointer, string key) { - using var keyPtr = new StrPtr(key, Encodings.UTF8); + using var keyPtr = new StrPtr(key); return Delegates.PyDict_DelItemString(pointer, keyPtr); } @@ -1517,7 +1518,7 @@ internal static bool PyIter_Check(BorrowedReference ob) internal static NewReference PyModule_New(string name) { - using var namePtr = new StrPtr(name, Encodings.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyModule_New(namePtr); } @@ -1531,7 +1532,7 @@ internal static NewReference PyModule_New(string name) /// Return -1 on error, 0 on success. internal static int PyModule_AddObject(BorrowedReference module, string name, StolenReference value) { - using var namePtr = new StrPtr(name, Encodings.UTF8); + using var namePtr = new StrPtr(name); IntPtr valueAddr = value.DangerousGetAddressOrNull(); int res = Delegates.PyModule_AddObject(module, namePtr, valueAddr); // We can't just exit here because the reference is stolen only on success. @@ -1549,7 +1550,7 @@ internal static int PyModule_AddObject(BorrowedReference module, string name, St internal static NewReference PyImport_ImportModule(string name) { - using var namePtr = new StrPtr(name, Encodings.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyImport_ImportModule(namePtr); } @@ -1558,7 +1559,7 @@ internal static NewReference PyImport_ImportModule(string name) internal static BorrowedReference PyImport_AddModule(string name) { - using var namePtr = new StrPtr(name, Encodings.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PyImport_AddModule(namePtr); } @@ -1586,13 +1587,13 @@ internal static void PySys_SetArgvEx(int argc, string[] argv, int updatepath) internal static BorrowedReference PySys_GetObject(string name) { - using var namePtr = new StrPtr(name, Encodings.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PySys_GetObject(namePtr); } internal static int PySys_SetObject(string name, BorrowedReference ob) { - using var namePtr = new StrPtr(name, Encodings.UTF8); + using var namePtr = new StrPtr(name); return Delegates.PySys_SetObject(namePtr, ob); } @@ -1691,7 +1692,7 @@ internal static IntPtr PyMem_Malloc(long size) internal static void PyErr_SetString(BorrowedReference ob, string message) { - using var msgPtr = new StrPtr(message, Encodings.UTF8); + using var msgPtr = new StrPtr(message); Delegates.PyErr_SetString(ob, msgPtr); } From 9ebfbde35eef742e39b80d4fe2464bdece4e34be Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 1 Jul 2024 23:13:03 -0700 Subject: [PATCH 069/194] Fix crash when event does not have `Add` method and improve message for some other internal errors (#2409) * not all events have Add method fixes https://github.com/pythonnet/pythonnet/discussions/2405 * give users some idea of why we might be unable to reflect .NET types to Python for them * mentioned event Add method crash fix in changelog --- CHANGELOG.md | 1 + src/runtime/ClassManager.cs | 4 ++- src/runtime/InternalPythonnetException.cs | 9 +++++++ src/runtime/Types/ReflectedClrType.cs | 31 ++++++++++++++--------- 4 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 src/runtime/InternalPythonnetException.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 23184258d..829180f40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Fixed - Fixed RecursionError for reverse operators on C# operable types from python. See #2240 +- Fixed crash when .NET event has no `AddMethod` - Fixed probing for assemblies in `sys.path` failing when a path in `sys.path` has invalid characters. See #2376 ## [3.0.3](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.3) - 2023-10-11 diff --git a/src/runtime/ClassManager.cs b/src/runtime/ClassManager.cs index ecb6055a8..d743bc006 100644 --- a/src/runtime/ClassManager.cs +++ b/src/runtime/ClassManager.cs @@ -290,11 +290,13 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p internal static bool ShouldBindMethod(MethodBase mb) { + if (mb is null) throw new ArgumentNullException(nameof(mb)); return (mb.IsPublic || mb.IsFamily || mb.IsFamilyOrAssembly); } internal static bool ShouldBindField(FieldInfo fi) { + if (fi is null) throw new ArgumentNullException(nameof(fi)); return (fi.IsPublic || fi.IsFamily || fi.IsFamilyOrAssembly); } @@ -326,7 +328,7 @@ internal static bool ShouldBindProperty(PropertyInfo pi) internal static bool ShouldBindEvent(EventInfo ei) { - return ShouldBindMethod(ei.GetAddMethod(true)); + return ei.GetAddMethod(true) is { } add && ShouldBindMethod(add); } private static ClassInfo GetClassInfo(Type type, ClassBase impl) diff --git a/src/runtime/InternalPythonnetException.cs b/src/runtime/InternalPythonnetException.cs new file mode 100644 index 000000000..d0ea1bece --- /dev/null +++ b/src/runtime/InternalPythonnetException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Python.Runtime; + +public class InternalPythonnetException : Exception +{ + public InternalPythonnetException(string message, Exception innerException) + : base(message, innerException) { } +} diff --git a/src/runtime/Types/ReflectedClrType.cs b/src/runtime/Types/ReflectedClrType.cs index 3d0aa7e99..df9b26c29 100644 --- a/src/runtime/Types/ReflectedClrType.cs +++ b/src/runtime/Types/ReflectedClrType.cs @@ -30,22 +30,29 @@ public static ReflectedClrType GetOrCreate(Type type) return pyType; } - // Ensure, that matching Python type exists first. - // It is required for self-referential classes - // (e.g. with members, that refer to the same class) - pyType = AllocateClass(type); - ClassManager.cache.Add(type, pyType); + try + { + // Ensure, that matching Python type exists first. + // It is required for self-referential classes + // (e.g. with members, that refer to the same class) + pyType = AllocateClass(type); + ClassManager.cache.Add(type, pyType); - var impl = ClassManager.CreateClass(type); + var impl = ClassManager.CreateClass(type); - TypeManager.InitializeClassCore(type, pyType, impl); + TypeManager.InitializeClassCore(type, pyType, impl); - ClassManager.InitClassBase(type, impl, pyType); + ClassManager.InitClassBase(type, impl, pyType); - // Now we force initialize the Python type object to reflect the given - // managed type, filling the Python type slots with thunks that - // point to the managed methods providing the implementation. - TypeManager.InitializeClass(pyType, impl, type); + // Now we force initialize the Python type object to reflect the given + // managed type, filling the Python type slots with thunks that + // point to the managed methods providing the implementation. + TypeManager.InitializeClass(pyType, impl, type); + } + catch (Exception e) + { + throw new InternalPythonnetException($"Failed to create Python type for {type.FullName}", e); + } return pyType; } From c99cdf3efef20451b96417d9422e21b8bcbf2cf4 Mon Sep 17 00:00:00 2001 From: Frank Witscher Date: Mon, 8 Jul 2024 09:49:16 +0200 Subject: [PATCH 070/194] Throw exception trying to add a reflected object after the hashset is cleared --- src/runtime/Runtime.cs | 2 ++ src/runtime/Types/ClrObject.cs | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index b3820270c..3b9b0ce48 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -158,6 +158,7 @@ internal static void Initialize(bool initSigs = false) ClassManager.Reset(); ClassDerivedObject.Reset(); TypeManager.Initialize(); + CLRObject.creationBlocked = false; _typesInitialized = true; // Initialize modules that depend on the runtime class. @@ -356,6 +357,7 @@ static bool TryCollectingGarbage(int runs, bool forceBreakLoops, { NullGCHandles(CLRObject.reflectedObjects); CLRObject.reflectedObjects.Clear(); + CLRObject.creationBlocked = true; } } return false; diff --git a/src/runtime/Types/ClrObject.cs b/src/runtime/Types/ClrObject.cs index 4cf9062cb..afa136414 100644 --- a/src/runtime/Types/ClrObject.cs +++ b/src/runtime/Types/ClrObject.cs @@ -11,10 +11,15 @@ internal sealed class CLRObject : ManagedType { internal readonly object inst; + internal static bool creationBlocked = false; + // "borrowed" references internal static readonly HashSet reflectedObjects = new(); static NewReference Create(object ob, BorrowedReference tp) { + if (creationBlocked) + throw new InvalidOperationException("Reflected objects should not be created anymore."); + Debug.Assert(tp != null); var py = Runtime.PyType_GenericAlloc(tp, 0); @@ -61,6 +66,9 @@ internal static void Restore(object ob, BorrowedReference pyHandle, Dictionary? context) { + if (creationBlocked) + throw new InvalidOperationException("Reflected objects should not be loaded anymore."); + base.OnLoad(ob, context); GCHandle gc = GCHandle.Alloc(this); SetGCHandle(ob, gc); From 6cdd6d7d7b7c50781390c5e59978cdc90967ef97 Mon Sep 17 00:00:00 2001 From: Frank Witscher Date: Mon, 5 Aug 2024 09:46:07 +0200 Subject: [PATCH 071/194] Move reflected object creation block Otherwise if you have a Python object that needs to temporarily create a .NET object in its destructor (for instance write log summary to a file), its destructor will fail if it happens to be freed on the second iteration of loop breaking. --- src/runtime/Runtime.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 3b9b0ce48..4fa4f5957 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -281,6 +281,7 @@ internal static void Shutdown() TryCollectingGarbage(MaxCollectRetriesOnShutdown, forceBreakLoops: true, obj: true, derived: false, buffer: false); + CLRObject.creationBlocked = true; NullGCHandles(ExtensionType.loadedExtensions); ClassManager.RemoveClasses(); @@ -357,7 +358,6 @@ static bool TryCollectingGarbage(int runs, bool forceBreakLoops, { NullGCHandles(CLRObject.reflectedObjects); CLRObject.reflectedObjects.Clear(); - CLRObject.creationBlocked = true; } } return false; From 6690310376b0b499be39bab59f15a54f31be196a Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 19 Sep 2024 19:21:33 +0200 Subject: [PATCH 072/194] Bump to 3.0.4 --- CHANGELOG.md | 20 ++++++++++++-------- version.txt | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1355bd692..66f66670e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,25 +5,29 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. -## [Unreleased][] +## [3.0.4](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.4) - 2024-09-19 ### Added -- Added `ToPythonAs()` extension method to allow for explicit conversion using a specific type. ([#2311][i2311]) - -- Added `IComparable` and `IEquatable` implementations to `PyInt`, `PyFloat`, and `PyString` - to compare with primitive .NET types like `long`. +- Added `ToPythonAs()` extension method to allow for explicit conversion + using a specific type. ([#2311][i2311]) +- Added `IComparable` and `IEquatable` implementations to `PyInt`, `PyFloat`, + and `PyString` to compare with primitive .NET types like `long`. ### Changed -- Added a `FormatterFactory` member in RuntimeData to create formatters with parameters. For compatibility, the `FormatterType` member is still present and has precedence when defining both `FormatterFactory` and `FormatterType` -- Added a post-serialization and a pre-deserialization step callbacks to extend (de)serialization process +- Added a `FormatterFactory` member in RuntimeData to create formatters with + parameters. For compatibility, the `FormatterType` member is still present + and has precedence when defining both `FormatterFactory` and `FormatterType` +- Added a post-serialization and a pre-deserialization step callbacks to + extend (de)serialization process - Added an API to stash serialized data on Python capsules ### Fixed - Fixed RecursionError for reverse operators on C# operable types from python. See #2240 - Fixed crash when .NET event has no `AddMethod` -- Fixed probing for assemblies in `sys.path` failing when a path in `sys.path` has invalid characters. See #2376 +- Fixed probing for assemblies in `sys.path` failing when a path in `sys.path` + has invalid characters. See #2376 - Fixed possible access violation exception on shutdown. See ([#1977][i1977]) ## [3.0.3](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.3) - 2023-10-11 diff --git a/version.txt b/version.txt index 0f9d6b15d..b0f2dcb32 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.0-dev +3.0.4 From 5c50b206617aebb8960d40fff7395a657eed6857 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 19 Sep 2024 19:25:21 +0200 Subject: [PATCH 073/194] Back to dev --- CHANGELOG.md | 8 ++++++++ version.txt | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66f66670e..29b4c4a68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. +## Unreleased + +### Added +### Changed +### Fixed + + ## [3.0.4](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.4) - 2024-09-19 ### Added @@ -15,6 +22,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. and `PyString` to compare with primitive .NET types like `long`. ### Changed + - Added a `FormatterFactory` member in RuntimeData to create formatters with parameters. For compatibility, the `FormatterType` member is still present and has precedence when defining both `FormatterFactory` and `FormatterType` diff --git a/version.txt b/version.txt index b0f2dcb32..0f9d6b15d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.4 +3.1.0-dev From de7a1d2d2aa505a4be5e4dee9368e514b6963bef Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 1 Oct 2024 12:10:08 +0200 Subject: [PATCH 074/194] Drop console application (#2456) --- pythonnet.sln | 2 - src/console/Console.csproj | 27 ------------ src/console/python-clear.ico | Bin 270398 -> 0 bytes src/console/python-clear.png | Bin 21008 -> 0 bytes src/console/pythonconsole.cs | 82 ----------------------------------- 5 files changed, 111 deletions(-) delete mode 100644 src/console/Console.csproj delete mode 100644 src/console/python-clear.ico delete mode 100644 src/console/python-clear.png delete mode 100644 src/console/pythonconsole.cs diff --git a/pythonnet.sln b/pythonnet.sln index d1a47892e..5bf4a2dbf 100644 --- a/pythonnet.sln +++ b/pythonnet.sln @@ -4,8 +4,6 @@ VisualStudioVersion = 17.0.31912.275 MinimumVisualStudioVersion = 15.0.26124.0 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.Runtime", "src\runtime\Python.Runtime.csproj", "{4E8C8FE2-0FB8-4517-B2D9-5FB2D5FC849B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Console", "src\console\Console.csproj", "{E6B01706-00BA-4144-9029-186AC42FBE9A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.EmbeddingTest", "src\embed_tests\Python.EmbeddingTest.csproj", "{819E089B-4770-400E-93C6-4F7A35F0EA12}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.Test", "src\testing\Python.Test.csproj", "{14EF9518-5BB7-4F83-8686-015BD2CC788E}" diff --git a/src/console/Console.csproj b/src/console/Console.csproj deleted file mode 100644 index bcbc1292b..000000000 --- a/src/console/Console.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - net472;net6.0 - x64;x86 - Exe - nPython - Python.Runtime - nPython - python-clear.ico - - - - - - Python.Runtime.dll - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - diff --git a/src/console/python-clear.ico b/src/console/python-clear.ico deleted file mode 100644 index b37050cce6b7bf79e540b30ed0c27beef0365794..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 270398 zcmeF4XP8|_m9G2V=l;0={IA_XQn#|0dl@nYh5-YE!PuBQ17?N{GX$G-O&F7GGRDRP zXJg}}oO4#^9Ho}jtt`ty&JnN)Pu=fZRjYRGefB=*bW+^Rv!-^R?mnk=eBN(;YgO&4 zii-b%|Fi$Aub5Z=zbY#6#XLU1hyN#sdVYof{ck%2|NrlQ|GUW}e-HA{K>iuXKLhz^ zApZ>HpMm@{kbeg9&p`ee$Ug)5XCVI!AQ%^l*o_z92^TZQRn8zP~JdYCZ z0pBOSUwq&A{yq2HbLNkK{G;e6-(SAZxgMPS&wH*pf0xf8^AmFwndfJfpWDYCd(1ri z@WbZVv18`Qkt62Np+n}tfdgj${{4BBcn|nK9Xxo@96o&5+<*W5=D`OaG><&;i0J3D z&ps>fGv9B%@A+pi*MXDq8DRe5=Z{{MdH(6ApEi#^`lx(X$vZh89v(Kkcked4cI`4d zckVQUgM(&ZU?7h&-UGf@e9wl4hRmKld(6Il`$R(@c;EqfpZR`MZ>a;h7sxf>L}~y( zZ~WZRlQP#IKYrXCJ$h7_-@A9O@J`PA`ua?7Z?Ea;=`r2i-KMLnD~~eY1HNAjzIS{N z`}_Mv52yw72lQ3+So8zzr{q0?Tmw#o258Rb=Z^WF{*<|id5X-p^!1w?dI!vZ_3bq8 z?;A4j=-*@J4vyFua3jB#>wS0}G2o&fkGq)DH)14uhs~T=jF{Q@Iu<>9&1}T1ZWmn` z8J#v}Vw~Br$INUGqIJm3V6Q0NnYPKrMvNPK%+w8t_1$LbI!2e7x|Y#trmX2OQ&ziJ)o$LfuEV@(U7PvX zrZ#iMwhptRt^gw28F*~a#vF`u1{nLyocvtr~}XdM(v6=8_QeG%U89U%eJ(e zRjpPFr~~v?tPfaYQG?l6WgV7lz}dy+iO^xP&(9Te413e`o+Be8X64RZ=A#35`dxx!~SR1e| zpci1zh4p}Hz=`-=La}4YJcGUQvQSJvK?wQ*q$o=kvU>>pikiFgm&T-9{ zx96~p-53)w7hlgEV&G?Y1+f#?2+@W?=mTSbVRd14{{bV>w;%oh(Q9KC##uf4Y;4%4p0krf`4j&3(+X#fb{~X3)Ugv10>e4CZGm%n%Y$zRs-k-oDS41Z8fu(wV3a3YnQq}y#Rd}HGuz( zsMGn}z?skHiO^quj`(@v=Zc@Nn}_z8m$Rl%a<93b+=F%Do}BM>A=gLnwZ>6gBlh5H z7jrSr-E-8&Zd_{&;Tkaqx*)L&BSZAykeNLg1TjC-z)&lGYuqR0EJ`tah{58%@ts(oY_v!f$BJY1`5ZtrAPh(%X*W3>7Ujo({ z!Z}$VzTd`Pj0}FQ*BIyea2nvX0AKGuYUV%>=7ukf4%ATx zL<6i2Oe6oI0Zs=tMLIAQIxuy;i**>;4_M=3b)W$&*b`{C{EG%a2bQ#$H>_+io7$}g zuohrHfOCXgH z4@B4eS^|#^yV#4zH1^;cF?SF2fw5b|X~Gb6K|?eFy0Fv6Y>cz12?Ig&QxgzU@h;hG-T^JFwC62-yxEO>UKnrH| z2jO+#keS(w@H#*(Ky>bh2cQNZ+8NXU)C8>v)&uYXjQU1s0K)Q54L~>#Fl`$&fD!Wn z1D5{{qFP@)s_iipv`+KbO)#o$chq+#v-w);`9>R#A25@vS z597Rji2V;E+%>;uj67uKLKh^4F=~h=JYeQP2YkeI;Fy^WJ+S;k2X?v`gbpBPX$%}e zjevk2%p zdY*=80CfPd&&FKnz+BM*Hx6TTIxuJNgGOQxM%9Ad&;-P6hG+nFzy)+*7Gu!G0JMP7 zf7HzEKN4vGwV)S4{xv$G1M~vo0rr7^M%)KTKY%`f8n6Skfbzc`8n89s-)Vs7pLM_n zhT|VTz;*( z3B(tO4nPa`GT;pu&;iCAhSPyLL(l^TG=MRCXAsbV*^B|`0izFIAc&bghs+E{H=@gV z0r-IF?FjPUsvZD3z#1T^1L~3g8Pmuy;I!51(B9hfzE44wc1A0W|(u@`=T9snWyQv<{U9I*UT1B8EQK+CXY zpEZEwe|P}$zdhtXp8qZXt_Kj~-)Vra0oV%!{|x4Ttpl6})UXbaKA_Wpg-zy;rVd#r z%=tjB4`v^S{hZN!4)UKp`}Bf2g7bY}*o$>Fz2u*3d$@*|zMt#%IIqw7{D6J!;|cd* zKjhvq{||@3x(2yF{HP7D0Zt1JVnm29!1w0KwHs9jFwTV*NbJMtbpU!W^01l15DlOX zxPTv+&DiY%en2!pbl?Hh1PJo4KA<07K=`KyAbOw!^a1by(>oCz)BsllPy@(6dV%$x zfBJv+{~Z6!{nGyn^FQlf#YTl^g#F*4WJIVfDX*w69oBp zF$)?n3tm7%G=MsA47~vK1Qe^M6|V zhy3GO^FR4VMEs);Q2yZoL<5!v9sqrSDtLfZxNnGafxJhEYqz)_Xf&UN{AbTTtze(% zKPC5Loey)r=b!8MgWNA`caBAKc%kQa+)KV!&V_m99^>KkmUCdM+pDW zfPFURU=;pE1E>QooCbLQ)dO%GdeF>bq-elQcmUzQmm0ucAj9#GIw0x+>;wCu|4-w8 zr};lv7w|dEJ$@7O6!+Z!n7IS*%{Q=a;geW6=7H8K97 z0ZUK=IR3w}xlQH)cyA_q0Q7Zv58&)$x?COSXJh{}&zgCC==s6>3;Vn;m-ptu`-|_F z+|RlFZ1%m+_qm?jyHM_rJdVd)9L9*4hwn)o!l-eO{M!)b$$iK^H9)v0`;LF*o};qA z_hBawPxhJn)%PR!`?WjB`_iX_=l6X* z_4C3!*><^JdH3TJ&;X6N7QEyTzJ>;9%sU88&^Ul=Xn@3ijEK1m=L6=BKoc~EsR4++ zF7}`v(AW)4KuG>qAHY5UVkYVU&wo$@c>Y=cGurl>der~1{O{)fUxBs3EAKx(>NU6v z;SJ`ZF0k`|&ik)%`9I)a*eCzXQ3Is#KYekN@X!5Nc&~Q84j^Ygi+w)M+W!LV%Q0K} ze%O11_ub>(Te%01_v3LbzO2v396#6U>6#tR*Rg-kUY^hSuFfa>%KK5U&hX6hI^aHh z+<5}=1<-QopaF9j!$GJHIQGf^>>>085VLl%4nQdX)PSG{ zn9+U2<$ufrY7a2Uf1~yOZ^1o6dmnv#%xmvWtMB1Q#(fX&!RP*DUWPRSzV`QX03rYE z0XY7h|5yI2(F448N2l=5Il!F%oc%2J`S_CKk6G`p`JemmX#N-eG3Up7a=0!($p3!c zUh{tn`^tQbdu3m=0D5qQ5&468&;lQaa2@jikDIyh0+RbB|I-7waQT1E$fL;rE@B?w z5%dBOF%JMAK>l|=Xe|Gz0mwgl0Dbpk4UqD0>wo3H9{vA%qSf z{o3cFe-GFf?(e7HPedZ$>+i()*PJgSJ-`!YE`zyW;{bht3+Dmm?0XFJ0WJa$P{96f z)Bz0eKg;F+S%dHZ0sk|4!9N3Q07CxjvHq`KdjGcn_Yc@3tbcgo=h`0p;|r!9^8t4L z-)cYs|E>oZ@o(nYjM9V?z_XiH+T>J zJluCTkL&MwZw~5zuG8guJ+9laeST#hBmKQZ8uy9Epbhh&2R;ttTJt|g@c`psUp&C0 zX7(_G`CoIt@-MYN>wkpsPw#K*f6qVrf6V`#sQ;1wWBfM{o6ocl$$I`Ng0 zqXzKouVfFwZ{s9x$k0Q!HN1N`r;9g_cZ{&ViLxYx&DfwlhZ{mb6EuJ=EO`|pu| z?7JcSJ{Ic%*!n-*#1AF{8xU-Ccuf7bg8`{aKo`+wvg zYXFk`YyYp)=KmP~$o;(Ux4GZmZ^u1&Cwi^z-lw0B@n5?NKEUUHt^o@9XZ?@-4-b&@ zpZ$5ywIKf9%tf3hF&FZcgo?oaZ63-UkK?7am1>z*6jcWa{W zw|(Ztzr=fhG5&4;&*y*6|L6SYIe(PCXLgtJ&oefd|G_`@)hy0G@6FRSx^BH*(gRQf zz<*iXC;fh@oDbjc`+UiMA9BCc`)Ta&dBl2t@BNwox%S8MKhyK?*q7e_5v==l>;Dt{ z|0DS4THDE5Ys0=>*#Bex_Z|Rq0M*M~{$~xqIe?u1oc}TNANT*-|MPqAu?OJS{yG2e zG(gtt2K@V4Uh}+;Me@DR`C98IbADFdC;#;OE{d^F@9%T}F82N%|LpyV_jmc9>wYBn zW9)ML>)c-}`v3I)$p5pk4>-@_(0%u~=gvgT|I?##&HnM{{uuG^*cblY{GaeI8sPeW zZVh1W|KlDv_x#!K$E?mh*asWuagx- zV^@!yf2e!!PShD3T(jGK{E1ZlYhC|8mH&l#{h#xm&3p2_nFar^{LA?{JVS&2pL=R^ zUtR96$$fXZ?-uvm;l3NZ|6cCLXAO|TKiN*^dSyT0zJUE?&UgKN;eI^qlY3=fdj8D) zuIC@i{a{~u|CayhWS`z&`ENu0&vk!I5&t)~+p~_j-iBvz=)Qa7x#y0*%U{@>LAI{(M~pYxyB0Hde@Z2reNnm8*flYj29r~G4&jRO98ZyxfuU#m+m zpuBTTFz>m?@IseK0mVaH` z3#k8>b2E8<9?#RmSvs}{$TK$J0hs?o4Nwn&`|$lex%2^A0|e{#lyx0t9bd?O#=KtY z+y{dV17UGoUMzsvvNe;)4p-QLwL zeE^=zd*HwUx$k}~8bHs_y;#V8?~w=0G~WNqd;c{5yWC&1Li2w(|HuBH<^N0h{D1BN z%JcxUI;{UM;9vYd&)#zWKZ}3fi?1~RJ%EPG`JVSA^GWVY<@_Z3n)B28{P6m*UY}<_ z^8GV~eaFAf`N?`;$G-Ia9RKZD_s?}dnD_JiztXd?g1SvovZgJ)}SUv2KO&3$$m&I9;;cvJ^u4A$x< zc@Ofv^!JqYa4eVmko~a7Uy6OX&qsTHyw4B5ANTrASN2)^)AtALOa5o=uioF~|7lyX z&-a#{<`p>edqI1*oX5v?0bCozo&fjbXK(P(p+mASH_zx}aF1>>&+qYP?&|C}^TK=n zwC;D_pSeHE{{jDG-_8A3Ey(#lTiBh5f9K3L@ZS;e&vUZLKhMt<4Zt}$t_BbdQ2x2c zrt+_QZw0yB-1I_@)xqmU= zuls%lb-(4G>=*K1#r&W1pVtGUs0Td%UC964$p7TOk2L`J=efB&Lz8D{@hmOuoozh; z*eCxZSO+k|y?EW;dm-Z)+@~_Hc|Xa1$bFGoU+&{q-|yG<@V=fh*hkM#@;>|h%6^B- z{bXPHZ$|Gg!T+@FdvLx7&idFw{+$M3Pw#0s-{ZBL`^-<8d(8Gu>j!u~Cxd#Rv8l7i ze6Oj;{3Fi&@_T)7zn@^wAFTs4_Y42RehUAX`_K8$z5i%@!KnE6H9&&@PyFaf?lqs<&ghZ&*w$|IhK+WQU+(oO`+X|+DeT)d z|G4*0{6G7CLH=K2YyT>&{jbXT&vXAMazAxqX6uld*^W5?=>ebzh<)qn0cQ6favlIZ z0C)g7M>FgLNG|~M0N7_+_uZc3*ymU@&!;i38c;0flYeEu>{>sXjD0!Zh!{Jxn4ZiCkV&9vLEALJ%6J2U$c_; z{vxpEkKz5l@-Mx=JpYd-_vJtPcQd192xnVk4gmbm>>9zDH|6m^n`djuIUCqxANgPQ z+Ea$Z+#X)*_md--_xV2Q^ELmwwS3ugd+uJI(RzOG`c-H+pc zS_AVx_$T{}Ez18+cfK#r_~qF??u;*ZfT@Tn>olAeV2{r!E^1}3f3Eil1H7a6=h;Ue zAjbbv-uKsTs&MaL)uPrs|Bo{F<@`JT+rU41fWm(_`0rs4;1K$Nhp|Q$XX5g#Y@VCT zv-5ahB5h`7gr0toKXd-)lf2|5cd#t3v-@ z_|N-)x&I%f2F_^RZ5XNi4;;zn-<_*fz(2J>bs%j_=6W(;ihJh%EWa=8hxhW3`?%*H z7yIb@k$r||Uu%D<|Dgfs0Zc{xKXo&M{3AU7S_iNeu>AlTy%*@mUjNwZTlV|rp5F-Z z0Co+4iSnZ^_sHj4}tF&3ZAR{DWP)=YFD|*Bqaf=aV(QvQOqUw70MGdt_hs z?bCd(Blhrget%{k*7vCAC;RmKq30hp`vL!2`%CW6=6?z_V9ExD@_*Z2>~D?y---O+ zRU-f6+|1eT4Ba_6PiKzqtF0WD%X><$=RJGk^_6?c`_O@qdu1Q|$L{0d9zM!G*Y$FK zkKBjslmE^Vd0+PUf$#UZpEW<(FXVr!@{bZI>Hls1_w2`ffb|2yzi0q;z=iAo zMf^hp$bK6C=>K{C7i#}6=YO;eoXtP4{LgUw&j9~3!T-#jgE#~0FnEIh??(?1XJYY; zOgS%`=jJ;8&E((J{%*9nAFP+lzRUkLtNVi7 z&;GyTpW||b2WZ`cc|dr8PRs#P z190}l%-%zy0kis?2H3_Z)rRkFk%nJu%PkYW__2?VO*@`vLpt`w9D79sgvX^?$(s6kH4c;{R>F zkNN&&-e>+#@~?CLGJ<>hfABB+{wxjnXAfWz<^Y@rxT0ae-+MSw`;nb(T%0Zc-bvv< zWIXL!8BdNO^SYjwqhsH5FZ`q4_c`BbfaZPN%R61}=W#VZ*Y@bzUhrQpy*_1MdwpEv z=R7|~_WL;BAF&VqnfF8XJ^$?cyCC<%e#pP}0B}z~YJZ7!tp5?}`$P7b{~0O#3;WFf zj(_n0il|IX=6F8|}~dRqe^|97wkK>qJZ$^YU3 zSO=g6K=Axr&dE!-d@eP7$bFLY@aq`!^!nVRBOY`!V*hZ#VbvV6f+>xnJ_W;~wk!J^S?gdS8#s z^OeW`ROWtVpJRf3`u`~#gS_v1|2F?;vhTcq#J}tPhxuRmm)c+Rf51Q20KxyCi#dSb z;>?!(vz^!c<$MOnKRv*7oMSt^74?5R<^VcI&5Xu!yL-w(Mx81*&_w#^# z1nqb^bFslc(BeAdUYSW$;h-Gx;yZy5@Kt9s54l7qAchL+<;T`z`-7 zddYq?$M18$%<+SJ_rZbOlpetJrlEj;XaIVE+5FoY!1C{EfUpi=PEX;zfb}rfr*SXrLk}GL!o9E$ z{)KzTKk9usugmrO>QVO#`<{RF`cd=iS$*7(k2L`I+{|Ng-vjP_%6RU%=R|Y44=T?# z3Hy_0lP2)rs#?hLMCBg7R@(bKeFyj#4*>p~F$ds!0K$Lg0nvb&^Z?!L0UWg+!1VxT zvLA>wcj5ukd;oNyDD%lYPu`XJ0s4L$G44_K&tQ;$n`0!!bYnS`&<}rEi0rxe1@WBUVPjl{x&b>~lz1;hhy1+R~?ty$J&Q*{% z2LEl;JL(?wkKU7dsMt*G_wn>$eY@`W>tRhH#IY<6mk34KU?uGmC z>3)3qX!j+YsP}vqf z-ck2hdr}9vHj}zYeVokv!{mC2>0ZeZl#=e*P&^O1v_nqhg zbR9w;;IPfJyq|{iY+PUO`1k#}0{+3fK9)5Gd7k;+b5H)I)=%)?8|40O{2d5opJP4N z_IvN&j=G;g{t;clo}9V|Up_|r5l+T?LA_Kyfsx<~Ix9i$%e->(`uQJR~49=-ryZ4JOa;0*Kt*>Nh*>};! z`M!XC?fW_Y>sk+)Ph(x2jola#ZA0eM*#FGNF7v5YL<^#MXJItqF%6u}^QnyZB-Y;g z*n#V0Y{z3h`pqY|Me&JkeI_-w_L@&HwlI2ZZ0<21-_#w&MqEpDVMJ``EQpV-?=T-* z-)>_aM#kDv#+o+svCL@2_iTJ=U5mMDbDP9(x3`<6%^jk@p$1bIs0Z|7WI}a--0-t{ zx~yF!_vA{sr;bt2`0vrZ-F2^L{(DsmRU0Qmcl9~>dejkbBz(3Bj zpN@0wtp@=A?cg6CV21Yq;GeZN`T)A##@=t|_}6*5kpC3k%VJ)*XYNOY?4#bF&QR_- zb}{t6K4^gV{)bIn8{?3vga5BSB6wfF+}n?F2iN-|wllEacN_QjMQpX< z*ZhU|{K(pWd;iZA-2dleBd#?z;2Kf8K8&4M|BG0QHGzyZQPiv%z*;~X)vNoB#43!8 zmA$5Vg^enVRm*!!)iOpmqRYloj7t!Uu|J5$!VatrYIo6w5wQR+#BCT|^kCHQ z>+f{qce`%3(RuR^Ovg<>Fzp&Qe&4h*Zuq|GSaGj-4AudBKMVNBnHBZh&;uavP;I{~U#T=6##{?R~uw|8>aymVNL~ z_BEQI0c5{vpP9yJL^N=J4{88{>}zbpHTa*(*uucNzc3vCo_lJ5a?jDRALrk(PyRL5 zQVVvP8fZZcv_QgXK+P&>fdu*Qi$&FPYJlnhH2|@c0sg%XbeVG(BNjmeG-7(7x`2_I zkQGUt2){1S3#$=@dSJDn^6mz6-oi$6)uz_iSqbEY9+qC2^)c&a_RLPjdO&kNxuRat z<5R!LeeaHqrsJ;bO#3yLoAz(M&vbs{J*M-U?=juqe6K{$x87%ZzwP3`-)H*%`~9)# z$JZMCUhlUN{2c^;xBFY~HC=cvK5qx2{fhUP*8g{jY59NeHqBpqw`u&^#irpa7n#=E zepZzKx~=_|{~ZDU;sKEVapon@znRg29zZAjKXU90)Y#Sop!O#Ja?cHW@~i{EHs|NG zcc+}|m}FhQW{wYech}QV>-z|~?{@iLxCi^48rB0a|JR}CFCn=f*YN*!&CLG@$N#j( z{g!?5&mj9d0uMm;5mOmn0}}j03#LE=d~CwCMxX<=%>Nqe!Fm`@18UZR`;dR?!0I4Y z_T!!aYCsSxpa&AF0nQJI20#avbenVG1 z4^Rys;9~`JKqAtBYIp$+)&=J-)leO9UVwf;0y;oFNQuYN z;s4#p%a_>P{|%e>ng4}-m&?h2$ov49*9ae1ouFn=KNx&2dJ1Zb@Q>Hz>ozg}2y%@9+Bl!oKI<`u}OD0j44UOGy3?Jb?Ot@c|facAm#5V(pPwGa9)9W5YvKC2T&Kl3)G@Vz?wk& zgscbH!=|^U252pyJf7&09)#@kZ=o*jY}#TPk?(hW`6AN@-k5t^7+=5GF!#5+{BLvr zd&Ik|_h(M;M{Xa02KaFNr?T%fLuvq5NAP(&!9R6|{-3_X=YQe9ME;*h{vFHGvrA)L z^&!mb$!qd1+$ZXNsr9wj7mX43V85N==KQ4Zk2-*TKNrG2{QopYBQ!ul^FMn4Hl~9A zsoNRO18`LC-8hB(Yiy<#xCnHhcH?dnMc@Iv7rBsH zzl8Gxq6N72o}eFn0S)aDPz!>9H>fHRnOc$5kOJ*+8lj#c)Pgc};FGJGeP2K|fb-<+ zv9Tw}Jba?rXT4ATQTDr+{@!f;%Ee~eI7H_}e-NI*g!uyl` zF!v8a0~nI~b%YjB9~koU&ijY?zYD&?*8dUz%>S(Ywq^xnAHYQ2yxweEx?9G%)s=sXH_R{-p-cT42~r zfd=?c?m12f`_O^4L9C$%*a_}6z`b|?u#Wzq53LKl4h)zoXn_y$0`vn}5$Hmw5yh@k zv?Hb$UK7%^!1WsG2c#}2^a5{R(r9*cbjrFdY5;rcoX6tcCMSAska|Piq5d3&7iqcq zhi1!{E;L&&yU=XE{6g{CQU165x1kpAKwj=bea@g?x3xcbMlQFxpZVW~%l(5G8R`k> z1;qb@{~is=|Mq!>f98JZO$*qk1~tI{Z~yYUOv|k$^1t%GRr%j-YXIp16yx940n!gd zJsz#aWi61wdGc}f_F->N*$=tLb$yrRKgPXy{(%2BuDn z-|-Ljy$4V)FcL9O{BZK>8a2-A8)_GqI*V;%g z0DS=M$0^5Yobxf^J;?F;^Tn8V?AIgrOTO2fuk7pSxo6&|?`NRj=fdWFh5u>4Pf2k^0#9w3N73u-s-HBp2*fa{G>)Sxz~5e*2&^|)5{Sr3H# ziv~~!0xh5?P(4r`SUCvh8RS1DLQgQ3>rxsL(+%e-LM=$uffO$gXu!qG8l@k=c>&H1 za$OC3Zzrk_pw4i9|Dgwtne9LPyxH)DcbJWc&EQ}1KiF?z{m`y+fHpbkE7&A8U*@VW0UQy?@js?ERI+ z|E3A#pE+BYR>pPI$AdAE-^Jetk1^j1^W7{Avmfve-_N=K82c{& zPi+AE@BlvO0X+Lw1Ey?4?$_8#A20^?=?NVFne3B)3F`r>p#?tH;#vayQwvlNlz)z^ zY*b;af*$zrnovR;()6O#V`&-^YJ}H=NDIUhhz`I9crQ>~1FpcDA+8nDI)HO-Y3tc0 z+5@QXr`|OD@(Q!|zb-KAKYu}-f8^N)Y5;lwlK;UVdF){C55XMWP5;mQ5B__<3GWZ~ zH3q~3XdMvvesFKuck_Q>-}e2jo^*kI))(wC(r=J`zCL}3@c-otOXvShy*RHP8n9&m zbKRH&z#dn1INzoYJpi6}&vS1h4}g8M*jEz|0N%KdK0ScW!zX=!=5-wdzLUK3Ysh<8l( z=m4U2b28uoHl;@mbf5-05Q`1?TEck&rvud(eXPSZL)lmU#ScIaR@P!#T z?XCIGw|o9K!T)dmqRaoN{~MqI*8g)3;3Ct8I-LDK%fIEd2R*g1j|B$T#Fb|l{e^3KR zFA)554o)<{_2md{n-#!qjzP?UCg9Lgvr8uFwCh{$B0ZDxf{kfS}(e z+{63VX?W)CeE&JW=qh*~K40Y}c>TtXs0Wzn1Dtc)zSQ5-zone<9K25LrWIZbQrV{# zfc;8D#f3K(yrw+Iy5*LA^#k4us3*X-1KfJSPIv&WpJy+S9)S1ROjaL2bN}JKHnZwKFNpYu2iTkD{S?m0@$uQ~sa$zosGt^N6j z?HpjhKIZ{yF$Yj_@vQ}~ufI2%vG&IAfd64r13yr+F(o$OF+}Bh@LzFd!D|Zt_uc6D zR}VlR5X73@;GLlwfN^y)0{%lSn8@p-mUvCb(gD;2@B+m(K<5Q)Kkx@w8^C*8xW<-! z0QTc~pDkIPXzwq%|CwjZ#_xQ>to+Q|%$m00$UW@!*QE_#_=b8)&&z*g?u2+BW#)_{MbfH>jSb60QM)8|C_&S>jP*2bs!cS@wEi_hXzzse5c^= z75?YnguVcL0%Kh~sy+Lq_)qG=WPF{_7}bL`9q?WtMFV7R!1(~38+84^f0*BB+Pk`C zFGt>6$94Byk2BHy^ZBSNyVfo+%Rl`#vjY6D{@lNqwWt9$u>MET|8E0h+mL6;KYIYp z@ZR(QEr>ScWbyytv`gjzY#*RUeE{;h?FBlIANT?0dwx&yK1b>KyWEf8x9o#|Xbf{d zUz6;!4@p15+|T}GIs8wB2bj7Ea{xHMo@ZIhnf5&67H8hk1JuC-)bUJ=7Th<(wKVLp z!2>Ytnmg3tTzkjfJVSFj##-j`a`~sXm;N4pCyoF2RNys{|J%SndH{8;h1`n=Kn+mw zmGuQIDfc>t{QLS}^8fyc%l|jq{2$|fX857KmiH9?jJggrsGKEU?^PzTTha4vxB0<-RG9j_N4|IZ=!uldRc%(73vRr3F; z&;ED71bUKg{<@-p6zEH6-^V?=$z)hiL9+jna72DEZ$4{^JPQl=$;!Pm?4@%~o$Kn$<^O$mrsiVX&$GTW5va{r9MaPt>43(0OtkvrOXFlZeYuZspXtN z+Pr}0o^?RPKKlYi`49bnPy=}8m3^5XIDz~Z)0IFQVmc7>0vQ^xnD-s%8X?pHF%Mw- z0LxlBuWc`(&HcLK9{@-F&paxj=S@Zzl|JShx0RO)U9$*W4 z0Kz|O00bFqLbR|4AT@x^(H+nL=5471-~(6#X!K%TfQ0ZKX@h>B?2~(8AJ0MdIlmzH z8Q^uAobM<5)FEmTYZLO{P>TO`iTqzK|88APEdPUBnUj}%9(nm%d|gp-Rg9;omuGnw zKkwKt$-nr1djBY~)m&w2}FLY9sT%ix~gu{lD5#7?*W_!PzMmow~pcqoGx_Bnt@0!$UeDO_L=wj{H!&Ee_`MD z{K@~OFT7JUswDqwyN#^@;Qu$l|C4_P&bH=xmvuXa;Q^5UaeutrH_!WLWj-DG9~!_l zH+Ib}M%Dmf9pJpY&+W|d0pkVNtm|D=e6_HCuVnq-iu~`m7v^y-c^|pI7E$q@J4VO9 zvacgFpuGG~58z@eMno+%p>|73Y{p|gpaYw1)L_)8+&F^2`@02yyVMX1$Uk&~v3?lL zGgJfIxDMk9iBV`us0A?{aQr7VK-LJje&FKb9^kD@n*2N<_tQ+<+it8LfI8BTdwUjq z{LPO4w@43=a{#M9XXgRds|P@iwH^TX0Wklg572nIWm5V8;8W%T!7DvL(g)BFbSHw^ zKrK+OK;Izj<2k4$^cBj!?fKcf&psr*$YywwjqoO1r?jI){-3g@+f0!j0M-C)!W|If<*rTo8Tp4^X@{Ti@u`OoP8EB_~*{cneTuU9M-4D#eII&&131%ir#;t_=Ua*gz#cg8 z0J1k0@0a7cS-p=&djZ;m3mFdce2jI?@rBHL4FKPn{I~S@e6RIB$6D6?m!#+Z+Mk2{ zOMYLV4c7l3iu^x3z?AI=OeLaZ{qMM+2=*=iCH%i<|77O=a`+E_CeVUF17fwns69Z` z2VBwCJt=ble?jfP;8Slm3qJN{L;jb1;(W6VJ;0TpvGV{@17IxxJ%G#uf;Z+|=>y=N zKH~E_uiG)d$KPSjZ^3hk=ZCgvzmL9y+QT|TazFka z&QWl#a{G-xD%$^-{6Aj)!Jo{#DgWSCeY|H{zn3-gcY-L3e=yH}ALE=mvU7iJ6Z(N! z{5#)2We3=IQHinQ;@b=Kf_|d*HunDx_^3qb-vKCJ(*K@AX#P54>@`5(1F zw*SAyRBsq@0sc`3_=xEOwc#X0$-2YoK!FC7(+AMGfWkiD4Go?0*`DY*z{ij5H~0Sg zKb!gg@n-Y@-VFY24{*8b0jy>pP(1)NVAB_{768m`1%KplJA!op!}kKft?da~El~E8 zBRQ8j12?vT`Bv#A;`amgHSd#shO*DUg}$HugzFVb^1r&vOj+CGYJh&M0~kc?vU^;@ z191O4*$<0*;&6Wq?wQeqduhZ61ib*RvsWL$F^zN41(){&)a+v9s_^Y>E!GyezN z^ICF$#ShYX{n@3awh=wSH2$UkA8{Y?Un>8jM^F~`)PU0L*MR-f{0Gb{|0jd>a$Yl4 z4+0G+RR_2}KvoT)YXN?^wWDZm3qAd4t`B!Mn0ti(H^Bp(Zx($59^jMbo8_N;EBXL$ zwetX23qZ!!hjl=x0bsHLVe0_x3F3Zy=5S%!&fTdOU_W0Y^a{MT^}d}~;2J~L7t|K| z46Z+-@1W;b-_N|yAphHLD3SkD`9}@FJ+5Uf5Y_>4FI-;(Py@6MD1-kD&XsxP-S+RJ znm(0(uGw#n^FOr->wJHnzQ&(*e_i9cO#UmuzcL?0p4WhOATN>z%uj! zRcGLk3^#P6a0xo~kze{frd7Yj>H9>WOc|MG0Fi#&L zeFd=39)o)RtzSaz;n-&_BEBDg5B_|TuR{J`gZ$5V!1c)g8=3#v0|5Wf0NMLK z=>f1t&iVlGk6u7sGjczJb$~_+_l2xSylW3n$5Q;0dqypyqDbzairjDWf1#g`_=lfI z-=DRAsrp}gilxR{S@)aZp8ntYec_($EB__>|K9JD|B$g${W?nnJpXQ;p!Na6H9>wZ zFj)iG9>AA3v`q^CgRAc~cV9~WSp&SuEQAIu);{2;xE25&fcFE?1AsHD0bCDYgI=7q z0PBDqoEJo%X3z_$A7~DG1IoJUg01hZXHf1n-}8B>E37k^_xW0UUCTb!CP0tYV1ANo z6}e`4>+gP8^gWuAPeSKYRYq^M9)Ip8Wf=_%G%Gf_1_1 z8X(>S_~QD>;lFSBAIx2szR}zR4{#s&XCH7O>HymZ#9W~C0-Xl{XKSGW8^GO0_5v{P z#(Zpjz&m_DfZRr2Aj;+BTO-s5`UMTv5te(aDbyCuIkMNs*W&$1%==`YYZKU?;9MnZ zl`SRsuLb|Lt9x+v1m*zN^`j3kfcPCzUxvMB#`Xf(55PWK z@BltSri)!CYI&{gOY)DrUb&LFzi?hp`gA|N)c5tX`gxN7UCvkbCI6$pm(~9-mG|}g zG5&p@kNH2E^C$NjrRM*I|0G+d%hz55Mo|N#_5h-Lg4hF?O#c7ykv9e&!1e(bqZi1z zz~$-zSO&Chj;WPcg{&dtBQx@i8dUDXNxyTLzd0BQhgfGMZ}L<2nk z*z2BqU~h*8a9=!XfOr7c2avsVxW7&b{xeut=5=IlulRD|8eV(PKJq`{kl^>FfWL?^2`Z-?JZZ?>xV>35#!3bj`}kkw}E*E*=N3I(C>4OpL!#nAFs`t zpX{?f;d~`y^Y2RVztZu~9>93{$68wP0pbOsxjE&!*r{)^`R+rIDFrv_O5 z3)k~hhWvwho!6^GZ-2b^|J2_2UF7w6(1|uHeqi;0#eH48_-v@vPfCkV9tU!Jh4Uk>{eZbq`18h%FY61EH26&|x zP(PqNbBu9L)?LsuXz1_qd9=r%^*;56eMt5qgng`0V12@S7I^Q%rr%ipxp#Wn`G2)5 zI!x_KTLTFHS_5pbHGu5{Vl5E&y_Xt*`(Yyg;~qKLGu!W@5B5jFzjB^6mg2wS^0le+ zeU%quj($g!``Op4M5Ot7;XmMB`ubqM67%_4{MX+6JvXl(_4i|Q`o71P@cQcell+7G z8qfcC3jR&9{`ZXMKkNZ!-477tf4Lus{(o}%|GminzyG&4hzGdq(tk4dvJU_sumI}; z7NHm5>i|C=Kp((#~m6<#~DBX$ETv273#v zEjZu6wMTp%zMilT4bqyQ^Awz~q*vJp{<&u=`RD${$-R<_@{jZDJpVlVGL3)j1(N-c ze|Uf_wo@N>p1zp3S6^Sb0{y(Ky5Hx1@NVE$9*0kh@+7V>OD?Ey|c|95=&pWp#d2fzbR1H=Qs2T%jp3#1R=nt+wa zx2uqMIVULdf=&ZAfX@wjuMoIp&Sp)Zv4uSX4P_mpczpMBKHm%XU>}+y^*(Yw+2`wV zE`nIo}fJTJgLaXn^d63lA_we1Po*`1|Lf0dh|r$D{_3aeXYoyK=8%X8uQA zpPBn*KEM2aKKW0Z%P*JzSl;(uzdY`l^Ob$}_ReVjSr7OcAm{;RtphsA{P!%q!`%L_ zZ!mYD2DlR%Aaenj51jAj1Jwg8!JObStPNZa-d28^VfnWjfLZ{2YEOX7$~pn$ZRrny zXXbL{TQxzy7T+L0Yv&b|dp;lYK5KpU9{E~)U9I`aKDCMLbKYXZufAt=zf|2TEtP*) z14R4}2>-P_!#3ofXI}&wpuIr5e~#NjBh-KroGb4#axE@@p7V7n{+`~xayj@<$^GJ! zm)};{&qIID_VZHe|1X07l(oCU|3&HjKhEpNxG(1SGjcxJxBP<(x1Q&$bm|J(lc_2%|}dp$J3c>v4@XdSRH(F+g_;G6(`fb;~N4*;8M!6>a87_e zV1?EL;BpQ4lzJ$6XpJ(r^lXt)_h)NnE(B~I_UqE!@p-gF|U`! zKD~hV{1Nv)|HC7nvGsqJ|1T&1`+I>;9RJDD8 z0BQl|Sgi$?e&Wr(7La)X?**d1z#8WVL<`s>U^s7}BlEitl|<#r4Zezy2SxPeRsx(ltw4P0cd&09K#}K>o!8a6J(C7Z0!#b%1(+fPXpj zioJk@2avOKpaa`olwv*1)xmYeWou%LhrB1RD}Vf9xd&hG#ScgB$x(@WaMJR*@E`Q@ zY+ny^aoDG);-XtpUti|+yssa|`W+YW@p8>E8TkB{+P9+;^>NOBuqRMK{>OeGvIb!C z`+vg!2VQ4x0spt74){Gn`T+0&vL=8&K;{FmHW;ieLdcu|*9c2b(0YNDoF7Eknjq2v z*6h>*4Kl95UcL|aGt%Qjjn6p-a?f4^HHMy_ug7|y>{E|8M?v;EU%~Z@_kQXE*(;HA z06ece*#oRu0{(psfVBYX0aynF8ZbpQfM?r>J^=gNu@;z09SF35d*i4E_8*YdZmUnz6< z=iI>j9(k6Dyso|BJE#8ofVBSKsPg}bTsMcS~ z`;zf`)E@c}t@&A-kbPw zF&_sFk_h+QbJvv^)AD(x@bCV9?7ydb^2uI&7u{Ue>lfo#_?Nl6z2=;&n#cWoS+7<3 zUlpej|D#v~n6(CYlIQ=s7XRMd@V?iYo9O{i2T%i82T%jp3%Cn&g6suI9iX)UeE=9- zEE<3{LSS+kG(Z9#U3vqe1J)B*Z{X$*p#|y@Y<{=?Uj2jB5BnVS`s_7m-lyIu`}FSZ0mf6a<+%m?-$dU5uAKlYeM zFK`{|fc3ki7O2I!)wMXkww7mF%6Ydb8bFSPf7>VY*U~o(w8HW3wPO_g%UYkUZJFHb z{+mVl54f+vHDhA=zX@#a$@%yB|J3Kd6a4=U{NLm}z^xxdFHkf9b-=&b`G8OZr~?bw z4*-7*YJjZ=;ySQgJb}{!=@nWZuY8kt_4V=?y@T`>Sx-d1VhMT;n)8Kyye4zLt@$ng z^eJSY^A{_A^ffszA*}}Z(ZC^7y|@J$0REi^5dK#>{!s_ipdV;8fIc8g1F)a9YJsZ> zxEHn$a$j!X_s;ooTgiM`do#|~C^&o5X%QcHY-jOY`FZ$%?7dx+2-%DK!U@X%?(D8} ze$Z6%-;VU8pAYR~&&;Z;gfM7ph5$_jp!5l634yqpr#!wUJ5o~^Uqx(7Lddc^E9v6IW z&pxz=wFq^Hx!?MJ@Q=Td^A~r2_FZyz^1#4=*}s3kdGygojqaD+|M)YedSP>11D2r| zh&q7%z#8hnYVc1Vum(P0Ei^#9fck;J3(ybfxwiqAUIRis2(Qs^o016W!KLhnt1Rfb zddj#T{kb~e-(RD*_W2bB{8e7{Z|K#zJ+*bjzPdGtb8g^yeB-Ov#s9mrJJ0#;?r}d~ z*56b6Th#wCzL;ZOoGLn?e~*l1-UC#W{}%Jb4U@C}w_}mzpB~`G_uD$)R;dHf4}=eh zdI9JMvKG*O0NCTafO-Mx3%Z(s`FcqZtlO1q9kpK2uVuvVu$JKOim!0bM}48+mtH?c zVIQ8K?B9)=pShp?3i8jjOxu?&@Mk6+J9bR&1<*57F2dR0)r;T(q!-}o0BQj10BQj1 zfO0fIYk~|N5JrQ%?3oQbxPPqNpVQy~LA(Vevi-=22nUh)h+ zdj?nVnPy(oaBJL3p2MX-pEWXPrbfVejIqSyCtC+%e_QZg6}t!MB=O(A=yvm)_q^Kt z?!B*-KERFi00?RT`R9DV9l@Lcy@0F@5Fg;y2?jbKa|Egb7^P3hG0=nXnp)w*c?j~( z&>CX_>U>#`2+gtV+w}>YqhxKu+)tgNXW?3=rB{DNJiy@Kpy8P*>H)ad@rGlMWqSb8 z0Idaz_<;5B0gMgk3s47euC;{vfsC;RT2X`VNhExD(w7%}tvS13#B<_2ZGUDaIF6l@ z5q=$G3fzWg=@dIpC*V4E?rzaDczK^oJc{bVL~Fsx)PU28|An`kUxWYOyyrF00Mr5R z$6Ns8gXfvsv>*5(*AE0^WR2X>2iU$KdxNM4z^3MCj#3u{BlCI8BRGwqulM18-gED2 z3}IhAKXX2qXOMl)Rg!(?e)cV74&&1EO?y*=tOek?iSz(E7x*GH;B6bw2VCepz+(0S z&K#kJ`juQA@jRv%$rraRc zso|b)J;oraaa|BG#)rUsO030WSrIb62KTNeL(jb|ImWdh)P_`DNYjIueyK-D{Wb*p&d&%5ykU3&OAET7HkeYwHSqK3nVC*W&9*&5zfo7a{vT_v7#690u1jt@_o~ zu|B}@47JYy!Uk6}5P&HsVYJe0CsP;VpXn@m!8q5>g*oCvI5zvGh zXhK<`dXO9go(bT;n2@XEI*E^DaD=2(Vn872vr##x&NetWF8;Wmitbo(nqS zzb@2;0zF7-!KtnTrxE{c;Qv?eewAo|^#G^?Pz$gZa0`3@dAc2Q0=^c&eFB^pxQjsz zKn}hKaWDGo}Hr3F9 z>J>ev8eX6pUZ9#@VD*3v_<YK3_K^aMq&-dAgXbl8ep_K z7K8XY5v%ZZvA{Vs)p}M9*QXc6pgYfQ6l0b~cpXqpi0eU23&yJhC&~+)PW&&p&0Kr& ztDph&0I#+l;Jus=Krayd-wc+d4xkSBet=siEOP^Pe&C)DNBNmLaG!=_SoI)nB;!8d zBkn_pmf$&<@1@p&zOe3~_xJ<27xuwD`xE@T==Sm2AqccUq=ss&_00F0bqzM zktvxI5Dmb60$}Y9tp&g$c~mc8Yjex2cmn4Os0T4!Peme)e1mD)xYpMJBO@c`!3Q6d`2cFbAm#(>nfvJjI4(+Pz*4Lc zL{A{q3phUz^90~fJORfr0*2-LX~3&?O3Q7)HxL~4U@9)DcC1pI_N5A(g`eCzkE=eKhb?Dvy@YEvxtlYIo& zHQe!q51NklHn}f=^8s8Fz;%JoKKrcUoZym!51Xp_P1zc-1RAgu8n6tt0BZsX)C4O~ z6EIeyE?}(c_fdl!n;xhi@N*K#t2JS`-$~8SnzK_G#_QsF3IhC9mkO|49pkwVe5ZuW zP2usejIrl;`jOOzm~U`;kg5Z$5v&$u`hkMpK!z8XObs}l_@959x#pr*+8O|L!1bsD zI3M_1oeuy<^Z?cea9+^X1HLC9UVs{4^D?~vc~y3qw+kZ7>$qkgKNjxy|G<3-neVwb z2}isJeFr^1^+)>s=uO;;8ijL~tXHU4)Gg}QZ{PD;@iR+)_HEPE)g`?E?hnE_LG}Zw z0rUYk>^^EWU_N}n0{8&w37{Xa82x}Hm>ZDD(1GPiEuba@L7$)+F_v)!e!n1`_NWGh z<8pkjRNy|-YDATf9$Y5^dr?%4CH!-S&(G_GeuTOZ>OnCrDCP&k-oVM$fYXTomif1u zU%cy;@Bptg*GV7XRniCe4d(-(0XMi_fc67qZ2)}$gZB)(xPxKkFIJZ)~{x zR;dGedwXS%FlqpO0QU){23))I5Hz4M@&eQV(E(c%RI@jzTA+0SeF4@EN{v8&5C(N2 z5morQWF$HCUw4C{j97}tOT`lWj0-tu0@vp*!F^`QSj@BNUC7Vy=f)ld8d9JSP8ZVj zz-vLm6C`w?JTGtpeZc9)|AntG*SzZ$UIVVb#A<+e0MP);6z2tRMt&u0^xM8K2>!Gl z5Kh6W_yKx?M1;B!UQ2#wj@NL%r_ZN-KKhQ^B-HcU+&>>6i@gTW2e1}kKY%?!&I|lz=V4QIA2i@T)B~Xx5G{ZXEW%m=dIIkY;0d%opcWvi z&@V`j40f}a&U_yC{Cur)BFVRWjr}F?cgJEezMilMG$cbyV!GfpA)y883r4F0nO@)o zX~1d5|Id|w_<(Ea1K#~gsRN`JAZr8N+8}UcH2}>p9ZJtTZ!KiNeoA^Xg7ui0^if1U6(-F1Pk6#5IV2WeW6 zrUTj^$npXwQv*&T{+r?dfA-Fon_q|qXdUoMwJm(*dam z-~$Rgfzt!=2ku(APLAQv$Tjl(?_I9f=N0zhJvb*}YksRq%0BCr-!bo_eqqqda9)G` z&1=!;y#9l4G+UP~lzu=@Pmke!LR=%r^#SY&&Xc%tBS(q5a7C3Hn~BBlv(JrFG@ zsRL;>L83Q!vNYgy;{U!|%}?L)a`W>Gp#i7?SO;8tanK9koFLZ)NKf$9(hrdJLGS{$ z7O?AtZhgP?<60No4lO7*lzUs(+jTs`J8KLV>NBL*2hE}ONX^fBAAbw~CI;E(-^v~) z^S)O0;`;@$Yfnjt1t9|b0`o`<1Y3|>34K#rN&iPkgA#;P&0QLpx1$OV= zE%O7c38(|~1J6JIym{o==gj<(2Mo2~E$e!$7ToJ<1g#TTD`)`eE z?+8cLt`jmQ^d;1gP!~iK(zGD*1Oc5O{&09ynC5HNUOK_125gGZnLlj(irDYNyw**bhKm z{W}Di)3_=01JnWJX4VJFt>*1gBekO-kn5>0QO=KahdM+(vc4qpDdhk9i`n0nZqr zKcFr!STp2N-h05c7pkR4o_f~YJ@TOW;MQGM3w)gbU%>i6bs($@q(1;1P%n_I2gc(A z##;xRX8ivs;GcECHDH4AOXSCE!HU)a%%kiFYCWLLkwNhT^yAP0dUEn_>jI|*?lI8? zUaLkVA9ugU-{qX11hvJ*jnEto(H_S?=PG}Tyw9Ld(cI6!of^QN26I1kjq!_jvET7> z>3#CwOzr!{C9gO0fBbFJ*4W^6fIY(g{(kWU)B^4;&N&0FA2@XAkl|Tl)CHb7m`C~V z!2=IG;I;ILC!P@fWSvDVxU&0zsYBn?_A-OoAkcxRCK#gztke64Ii}1Djz=^f2kk9~OACGBIUq-Cu6K%nB*qm=)lN#i?C;OsL&iCVQCI9sPw)VH0#(x*}&E|ir z0qlX&12KMn@ptdtkcaHqIP)q!^^BJMv4{Ls#_igT%{O@vq z*!!cW5&kh!=lJhr&;#(_E*bzo#F~h{=$a3{+5G;SpEs*+`;BSZyfLN+stMGEJVy5( zu+O4e%6>~&XKC-1IjA9`lHNdld+OuLzVr2=rpUGHJy37N>q|ev zdH`yY`hM0b()V+^buIpW>;EyKYzZolGl z=Fb2AQg(bB-#f$O4*dJdiQnVrH14?a%Vyb)*P5-Xmc_JGy_NP}SzmE&CTj!sTQ?ki z%*@4_NM92KUZAK3#O4MA4bXYP$?yQD5&sRs|3AuSTl#?b>@yEC$cOI(AYZZzp9}lYh|wya#+QY(yHMTENlk0j{t9mt_1S9y{~nD*PKW z<8An!iyvUL@luR0HQ#sf6ZBhd`Rb?4syl8p9c`_?=c02k?7OnR^4MdKNw4te)6bcU zx1dLuQV*2WfMWf?iPnJAjDP+&=STpCf8!fd;#`t`Bd}XacDr`0l2Sefe%R30b}U{oW}f1AJBdNwKis-jJ()> zVAu<=JwfV#dhgH=*u3m|g#pXTZIbQe*ZOnPZ^Z8l`xs?zpUgvZY|!gdi)_f8pY#3K zv2LOFN3f33-rqI!{x0`R{qH=FdY{r-K!0HSraGo+!P)p&sCjX%u%1ZtAf^S*6F>*9 ze&?&r!s~uyIy*b1=gJ-ddjp(np$2e{;LliZaYfgGLJcU@4`3}IJ|NJ50{&Cx14gR> zX9)kw0{I{>*I>R(djaH0`T^<%kaK0Njq3?e2h^9B9<4?w?{CKXiUi^!>lyd{B0pa?9)b&B31{CuEIsg1DYyM+iO!7~jw5KLo06$>Y z+G$Oop4{qza!W1H>zGccUbr#*8T~n(dx(!wzK^_q{BKLs0RH`1I*{fG(rN^Kf6{b8 z^&x9Ko3tX*2kV1W56aPjpS=Ig-UqNBz@8x24spFD_icId&o7$uaAu=uK(RRiSs&>5 z_dWpor0dxxtOI=hPt*Xp|1XRGDECH8su$PVe7s}$8nJ8EBSD1P6kdluK(7mQfc1fD zUP^tCRVRdcaJF5i>0Fw|>6{8{pHK&EU6Aete)-W0WqyD?L9V;vnjzkk!G0j`x!C!{ zvt|nJkCuClRRer4Ah|y91bBe6!2hS^|By%Jelo`JbqcNn4X}N`LN8Ft4^R($kDyE~ zP)#T{o^6_-|6bLGP!CG#K%ySF;fw$7=LNXtlKnvT0`xqG>xYhwxel0c50LX8>%pC> z8jz&}VQo;L1qnSU$0roigfsbj%6~Xg3(|BT)eD3gfVqLYul`1?7r=FbTqDT)FaP|P zznZsf7>L&a-UDF&(ac_8q6Uc70YM*NG&MlZ|D^LzO)1Fz;bRFOP|OPy(}AQGq}B>L zpP*wQ0D{MA9>oX6W03%-F!en zFEFeDg1LZ#xxj#bGF`3)Ic<@vtdlpzN9Xw79{He z>OfKhbgcl_4{~nsr|OV1C)5GefHW@<*MP7m zXx9m89iSe7>x0<`_}kz9W*Q%Qs=x#I^#DABV1@1-7J7g(=K}Nmf6DT&Ix`j>C{-J% z7U(z@EjZhLC)B!_9zYAybRb?2Bv*=YUh3alpals(pk6>VAnXTljewsUlzyP~ z0KA`&^MPC&%r!w=55PVk`M-JZgOLYtYl6H7D7O!oRs-bxpBDU=?FCdTQfdR$g3)?| zv1-Pd^n1nhfLh@FK;Q*RYe3usTy?=KO*{GktN}O|$i3LO9*F!u`llC6jjRd8ngCf3 zkkSVX*8{nG0@CLJgC4+1W%+fbsSKM$12I5;-3gdm&@Zf}Fod-J*x# zzUkPn{j|vcrvd+=28~Js)E|W71nNRL+HpGlOqLF%YCwrPK<5MK0iyXpyJrB`1E=xd ze!Rq5fQ)&738?`*|GbZ&jQu}z{!i$7flvd&>v8Krs10Y!br~9%)Piy}pr{83`hZ#d zzwp<;nHtIeC!q&$n)A=Pi#6D&>;Ka%|A*`syUukXs_RDeUL>?3)PZ6epr5-?1Ly&C zE`T0D*8^qo&pP0ZYu$Z;S_Am?z=?T4-vdaj0~qTZz_|H$=cb+{|DW?;c>iKCebDcX zijA|yAMoE9=s=nV_+CJw4oLD(4M^4i#rXdS*8)yR4`7sY0AuBUDbAIX`v276pYK1l zxa|J_8Jho;LoopX(R=eo`A^ zI)Kkeng>YouNn~XZ|@CC_5h0I|Ev2B+W9lq(W6}lG*iB=|>No$LWl^g6&I{9Etu z{6D=vU)%eC>`_4fzZh#3xn4o}zmMmrpP2c7YHLyc;mfGg+Pe+^8~Pqk(AUa28678Q z>iacX{=*s|wkH6@`SGcg^u#P znsWH(+HK{ZqiR6Tf6jU_{qh*8VyFkNmIvbN^%ReR{I_PxAmd|2h9<@$cpXHE(d#e3I+|Xb&*Se>ewF zl7IT=kbn5=%sBve9}qbe4IsN(!-si2YYrg%8U4Lc@L#a@mw$JHf1ZgL-s`OEoyR)L z{XYfy|IE7wsMu#S|K8+lm(TxW;6JJX3g-YOl>ej$m?-|oIrmq2Pm}r5hITnGoqHYd z3?$v_V65Eh?={|gtIVaw{r@=l59R>)-#lk4e-HQ>Nb(={0JHLc;T*sz%zrHR7xw1_mVmSAonENCD-`d<^Mn*==@#Dv3zeDbIqzsQ|B>(KAB<}&9h&jNV|D1i* z|B2y0oCCCLfJT-7lWTyA><8xke>onY82kMF(bWDay?^q5)z&V#&nevNY&_iS@22rz zY7T%sz;b&4xd!AKFmd^RJp5;@0nq**b8?#h&*Hy;d0itM%=yRIPptc6?@!nLVSnOc zwSVP3jpo|Mo`QW&l;QD=?aS^B@_+Umz*yG-tL|$a$j7r&5A<^&V`2_K{Xa+98>qy6 z0Hf~zaShE_)&Q|?kMU2omGiXGvCrP0W4~zb7yjM6f5y6h_WsHLqKJ1I|7T2><)*=R-d;lgIyISvw{VVToFx^9Y$yM)-c#oKqP(Bt`;%+_ zlDR*a_hat=zY7}WtP|biXrg+54ygQjfJ_hIyk6|T{}?nN|NTS{ zJ@Fo%>KYmQ?^neCPa6MOYXH~-WG-es9_s-xXQ!MER!|Q(ug}K|Jby9v@w#E%uem?C z=U=`5$Je!s_Ye0t9#74m{N3w5@^^8hIxAgKWbUO=@#$HZqi_8G~)7oRg5 z!~1cnX@dXndjk0VWD&KE`V& zeLu#)zGp4?-IJ{c{%=a~&(DYU046&Bd;ViRz_15^8Xz_gnC=5&ULatdyldDwewXu; z^}eq4)wY%zip!U$2}{>=l>7O+Ii+u-viVh0PnNo zy#NJggF60GYJrGvGM*MepHKJqkh%iT>1+NJ-(Mv6e|$sNq~?BVz>7FL?I-VkU4aKk z>jQ8tW&!^hJ|L|gNaH_$EL|7%eHpbr7Gd8rh@UypI^bXXMDagxNIbOgUr+<2cz|Rb zpc>%U1vtKA?CWC~`E#E8;QrnO`~E&3*8H0L>HD?k@8pfX)Lj?=t^}_X7%}Za%>G z1A}@X^aH8~e9Q-b)_MK3oG;wF{XI1A2kiTvALjgO@7`g$4jmGv zJn{hUo9}sGQ_X2?qB(;EgkQS#ayOaHQk5(H}b>JjxzzNU) z{Rd_39M{m;8o>1d#RKU50M-NeeZjmAkX`2mv?d_iS;3!SsE1%*kQ#<^z&GAgKYFIzTN*=m9k$E7G)Kq8^)U zRww)U%hiHYO#@B}|LCC!|Dgw9pDm>h5KcKK;5EQ`flvpO^KkUcNBKU^eK~pGuJf(G zx6$-J_{d3NpIY#j7hfTSMX!zZ0?P3L>IITIP)rL{6Gk(RT0chNJ5)C&g7NV@$7uB7 zbkTs5#(&@ed>xR~0N)RA^?+L+5YqzZ=hfGTqk0Fse{Yg|*W;(>$Y8%3HUE8kj+zr$ z?_ndW$6X@Jwj|hK-UJO)dR(JfLf4@|KHx(KS@!XaXkNo{+9nBm1s1n zRNghw0!&P)DTz@NlW5Akm{^5njDjfm2J(`q!8cw7MNmLNN_i860LO8>bKC*Po5!06 zB5=ZSR;v0EP3vwM$(-bCp+^4}kmW^qk;eUT_!-Fkc`X8(><%^dMwC z%;$smfcaRAUxIt&B|PrG>e>Bw_8--2`WBVzvoUY(+rDCndd$oHWzhha4sb1iUtseM zOg*UdbxpN_Vw3_k!lemmT9AhZG!_3dz`s}E1L&~;kGWJ02y}q_ftVIhj9}FG3<~~7 z7kr0T$llv*e!%_}Z>?7?$4=?-{YGM+v|!JFSE;c>uL|=4rF;Rd12kU%8sI9V30dog z)&|muYHEkRm|9SY1|)m{%)@i|0kzP8@K^=S|C@~b|MZpWa;*WL50It-hzH0Q(7C~Y z;gIQo?F8!<=81cJr!HQMPjP<0JVV4+NN7M4^FImvPg@c24?n=J z0kL?1bbxC?Obdo4>H@c*Aqgbpq=Iy#B~v^&CMz97S74ZMyotS@ngBsPkU#;psHDH-HwdIm`Kf=a^bJT=e zuVpa-^9O>lf=CPe=ZldhP))kvs?#*V_Fh~kB7Y)pJ>Y&sQJRoN2eSGB^@;)N5&vKI z>IcfL0Za$T9|*M|U|91Erqhc5*((;dsGq#orvAKck9ub7LA9;tY|}7rY5={)IN|$K z#;FOzlUfk^1xyd12}xZbec-k4Kaf`N+SCi17GR%E4{E6eTo)?Y&Y~qT9kBQUrUqn+ z0}9Fk6wN>NQK`QyM2C)c?oz8~O;HP;en`D^@2zTbu&x-^7lsz_f@e26OP~>?6}-m! zWbqQ|1lI+dCPaGR`vxv8z!@Hog=Q|ozy0?YE(UMA zfPFCE*Vm_?pMLJ#IlZo0GuFcY;O*z4arWOH{DXZwLjliF?(Xi^&s4xO)tapc|Mos0 zYz*FZf!~j3r{OsY2M-=p9UUEN_wL`5<%EG@r1|a@jb>94kK0jL*#KpeL?@!o% z;lGf3W9rrK<+#4)693^niQ#=jKL4d-fXXzWRGi?l|Fwwuv$9_c-(T#%*#3HIQ9k_B zJ;l-50xsKc?sxeDS@{obzme}NC*~Lag~568e^(A5xrPAopS6zb zgiQmo@Ncr87V}r@_t(nyi~SevsW<J5^>uvyfLjAf@n1Ci1B!od4VmrRx2yg8_p8&V zPwV?TYSn+_dk3~Ia{hG=(Bt161IUwq(uueZB-b6vi+|TxUpX;%<)YIS0+ zG7X5HQ^fhV#r`(iKakij$Nv}pi}j1k>B9s0vw86U=GX_++0Q;x{Tc1Pt-aRFd?9!) zNqPK}&8OPrzg%CR>@nHrTGw9_-heck}y&f3fQ}dsx)I$o>b<9SX<( z{P{m={Exrs`|7~nPQ7+0Y=5=q{6+sIHRZ0Kh0iIq`TxX!7T-T+`%A?7CYMFM&WC@0 zofhb^jQ@+Cl*3^u{%tm&_APz&Id%N_alL*Z))6h|xUSE3tXr9x|ID`E!G2MUrya-S zpZhR$eWw3UW1`SUfBui~@9IyoKV1(i<^S1V{z!G~Y1iupU$}6gYTuv#POsd3lkV50 z>_72g`u?Ttej1NOzgG?a6enTqRObJs_jBIAWIJlBO8I}|$%oXwef!j@Q>S$7pJ$t{ zo&BPfe?IRAHj((}HWS++|AyT!n(Zvd3;8#4f4C01K9~JB`A4iK{(scB^X!w&`dyXs zzijTzn(=>P|DG8CX7107{j>0&g~fV&rds~}^%Q*j5B`z6#eEzy|6g<9|L2kW=jYmgsZTvUaH!Xp<=R z$DB#wpZ6cx&aQnC|Ir$N@FD#Ie=?fiM|nTgZJS&5^FomS!}Ejk%KzcF!#Lu^o4!{a z{{zYQxA4#PP5b{P#{LoioA$q}t4n?Q>8I+_rAxWwW`(QlTn&`A;{4mz0Ce%cxpk9j zZ*SMn2*k5O^BDi5Hp4TCCf@u5;or+~n6W>60Mq`*a({vUzhlP^b>P4O;Xmj3GSo+# zrCu}Vm2mwJ7ymX5ShsSyqCQ4zOjJAn2W^V)xbS(e8aM3frsls~-=F6GWf}X!dTu*+ z?o@{k9a5hK^D>*Izshyw>w~28AI$+WHQ@cZv((nDTXhV8X9u1?e_lUFyxQLfw*PSZ z4xRs_zD{v3pZml8W1jz`I6B6c=Ubu4~9;dX|+AK8AF?{CKb&v>!Zo;`ci(W6Im)8)MS zn|x1W=|Ib(H`Hj4e_8`D83V+90Mda8!+)waZ{DnZ0OSE+YmjRKzm;eJ{->|6Pn|h) zMos<8ZK3ZEdsmMAhYclr8ZuS~9k3lcu`ggB=Ua~L=ehqV_K(K?Cf+$rkIkGsd9rFv z7G4{R*Svp^jTQ0_9au8sC9MH?W_U+Oht35;1K`SrNse$)1I-yipHg8%u?KCXXkS`$O*#!U~CZc17P35ItsS25AVTWz#M{u2M?+R zlg4WHVcVQ@|Gn|AEc_?@01JP_VbQh8Jzl4=pUL(I<9>+a)-8QUoj7qK_jwt4_B;8m z#?X@OE0(14U&;r74!rU7BU%F}AAp=7d;#b{cXzk$i#{);V*oq`u=xPQS36u~+2YY2!`2uewibANP9WKTs;B+qPpW0ZdYunwYirYaLFfSd0q~7`h44HI z6l`N3-rMQTQ40RF(1EnBn}AU5Cya|f2s zo2{O^>&B3M+{YH-|qMO&-$RaKeGK_*JC&zEqYUI zzs&h7OZOK~dR&b{{BO$vl;H!={Q;A2AEM@ZF~PF8Ue`VW@&hQFHf>TX7S2=i#*bFd z|Lzyz`rl;xv+!^80bCkDe3jy{R`1jC(`uYMXCK!`g={}*BEQ!!obMCF{jmKLhF_=i zF36{f?Qcw5+lW5U?vIvh{&fss(*UoJ#q$8k@c?4qRl{D{iqC%Z96;*p@OND?fT;l# z3zX7KaT;(KI&Xx5DP#9xDF6wMRILB7M%yL>0EV< z^S9W1T(6CNm*4-d-`<$Z9L%hJMn2bE`m*fhN$Ea75C^1bfQ^5f&FA|{@n1>mTv}|NhMlHPHZQf{lIJS4soYd;!idF@kJj>oF8Z;)o&CqWeCyVgOND)5KR5PC7q)qGf=6BRwS*tQ z?-35i1v10~uCW2K{bp^`0X{c?YXNO@u58=2)&biw`93;Nv!?tWu9@0_;wOJ>#UFd+ z@&1_WuT-r|7gY7UMf$C!mYBiT2XJ4|Xpe2o3*z$vA`S3;0h&<7=0sB1!)bFXb=>YAE>jBLlpmDO&b+tMW@mu;>(P!yA{hF?YYPxRI-cziP zxqWEw|NP-*b@FhRjPHrhpS#cR(u_|IA5fF;7^?jMn+~9FM#a|x^xLoma|huQB>jTw z^?(@SwOu3P4A$y1{5;aONM8s@t*A*!sH9sbRdci$Tx^$1ZYAzx?tA^@*7acHKMUz^YcvdPCk;~ zCVpF??VkGichsVBBUR7GhwA>D*UF~I_YPct1@>s;f;no|gMZZPO<1*n`330HNlkET zgHs<$_zyUSqz_0RkUk)N zK>C360qFzM2c!>3ACNvEeL(tv^a1Gu(g&muNFR_sAbmjkfb;?B1JVbi4@e)7J|KOd zf7rq+Mt8R$p&}{`5=xCwN~B9_bi?S;|NZ{Y zzUR5y-QB*sednC-J?}YhoUXPSAs!tb004wf)s+E}ubHjYUuXe9z(W9lM*_eV<`jGj0K5eOV8;>wBy$0P+P&cCb7{;QIIlI- zl!5#I9>r~C836E{^Qp4JE5CpHZT_XFcM}GntJ@iN71f! zB-yo_*97Nqp>sb&mqgb;mi2!KB07kFpCbe#hKL7qhg`6rVe;zCusoFy4I@sjmHOou zRR`Y&))y|>EP4{o9_1NLF6fb7mHy;8Y5QX!uv!p!7)O6GW+`adL#oEW>ly2ZzY@zv zCePr}LkeR~7GH^lkfwB*#$3xtM5`%GxR?DZC!sy#W#ok=^-)`}ria5L= zGX4rKcJvnn#T1Q%N;;0ynbxhpYDH2vB8_s2QuKMXhkr3r2!qO#76R>$mhZh~WG^P? z(%9qh1CH4;2Uv5zeSDwHxwku9cUho~Gyd|C^nIQQ>b~`gRy_D5TKtF=#IWFDQ;x{EEMlXX@~9t4UOZzIZr^5ve|p$?qM&Q9tMuYG7B1w1zU%j$%i< z_gCBK1_Nor_u9PL1L}Z=@SE0k^P3o7vvQ;0>6O6!Cy!W>U*nT&XbQn8q0vfYw6tra zo&NT3x~}igl;S57Nu%_!kecSzf1&Nzp+^Q5Ag}Z=xvd?82|yJht;O(1O+ds_fHpFg z`MPc4+iJ&6f4Tc5`EWD|E13d2ygx-C^rYACI#4#_^)PlQ4O&mW2n)%)Rql_Yp+P16 z+5y4%7)9QRYAShxN?r;k4Y|C``Ji8}LKX`V{84+hDdjPgFB@;(WFJ;!I(Jx$&cKz; z0 z%6TWZmBf;pQ6lu?g(QsGDwRGg+A=#-xZhCjS{oYq#tkkPa45Gr+IwEIbQmG=uU;Ks zOa9L;#ENdTwNDw$PXrooXozoqe9{Fxv457X0QnVQ$Z~2}3iMCM(nAou#{S-_^|~V% z8y_E^h5#oG!U{@!a(5>z7PxxHib`QPG+@RRa1TOUEZ;las*S}5LRpn@;-Ld+@EfW( zcgwfv#mbA~xqa5B>FVG#;9Kuq(cPFU2=$QxJs4~Lu~y#0!RixN?)@^CT+lssKVp~A zPhI|cu2dM6c7JR3XL$JI^sWjx7NYAq?|!l%E+R#Z!<|-MroCN;T7Oy@~(iLa-bbaQvCp z)aU4F^Va;CxYr4t$PTMaWbN&8MDxjF8#~8b|ZS5qd*`CEkc*gp<3+=QUcnDSRhUf#s%~K6AU?^m{kMwYZmEd z>!Konr}-{lJNNgXg*zjE3!1%wouhtokAuhV_k)YQ-*O?gya!7jeHo_8po=0AqCU`l zZ7cf*NA&VoVIjES!Qk(@;<|$Z2aV|Nll!ft;a~WL-a?;0e;#oVSsAlD1&({W6p4us z=c|{wmabxcQ|7q-u5Lf|aCj1(=covs*!7-p62%NQ*Q~rWc39!tBRf;i<)`S0oC8Eo zN5dm~F~N0loVPc?QoCvdH2NZV3nnjoL`SNT0)L#6kSp37=Dy&2*jdjnM?7Tra^V^i z(jdLwVsBK^1qWQ_2o-j7ihTLauUfqnl)p?meVN{M5TSE;l8+hj(6?ZW&1_vtxx-3T$_EERR^kLl7^8dRWnOB`U@~ovY>dQtn$LR^N>u zQQ|2JFq6r<|6wc?V0j(Yd0$ah7@Z?~XiDkzBwy?Moa=wX_>l-*QZX*`Q%fr=kbwVz z%1o#`=>BxF1+V(ob&GEfvR3G5f7czV5H|lXi~{+4*S!_K+yMxU>=U8t1ASMHCxzoN zR;>)~gQ?9P-sR*9bGqbqm516UcwU%PQepmRK~Yg#xP=oS3F_;$6x1#35)}x291?Kx zX1>8y$DfD`>!-EVU45V{4k<+%96M+MpZQF?`{H)=eF`#;x+poMBSrNih-D=_$E~O3 zMQm!&?)?C@8C*4(#=g<`k*1E0wVfT#%a<=5n&!-R6@NrbWY`l|PK=y&j-#L-I-OrEJO;=<u@qAys%L~vEQ_X;{%&pI z%_~O=L!!^{SRY_!WgvXjgV^`oLtNi)0iH&^o^9aWO_S$dQ;+E;=WI9y(gY)2zC#jvEO@?fT939+6riKR8O zByWK#L{KqBQts}IOz2&=73=B6NPDvo#=_gv6OXPdy=G(qISXXDaKEL-{?IDuaLN^Z z<=eC}YKE(pVoQkSYUHf*)uWf3YoCyTz7TGH!HHSl-qV>DH=?5Jos^>=)pyNndyF#O zL->0PWT}>tc5S!fuc7WPw83<+T)m|Zp1od=>GBxJ=&>r;u;2XdHAt>n*qv4()m7fB zgfUeEJa`q|OA0GKwfV#Wx=Yu*=NU}zeMe0~9`XUl{retCR_zOG?U5HnY53}6cWK6O z&SK(ICIY;O?|<6=h=0HpJfaoGK|8)eQ5&9Ea;18`c{TMBm!#H$a;=lfRKvAOQ^4}o z%L6T{4+X;sYyp7Bn$cqeh8I*cY&2u9j+JvNk6U-j6 zjIpkf-VDZ2Nk-qhIB`D|GZCY7?B(Sp3yiP(c{C87pMU|qHPSM{FU&$PF%#f30dv*6 zier|6HyLnhpfj$d%B4+GW%Ego!+6y*x^&FRf6Cvkf0L@Q!&m#TQrm7Z8)qvEW*)<@ zk{Bdi@rm>V&}W>S{X^~c^c0{9o%bJRL;t-v?8K5fe?51bVzm?|u{ZCH5aUQ|N!DKH z6vO6}yfg)Nvs#^tzfm{R@M|2&Fkk941Vnhi1Tl{MTdT>nJE>_g;r?V_3N>?}G+a+5k!V+xCw5(TST=*A)_3L1kHC=zY06l7k;-GT$WW!|I;7{u9ZvD7kf;mExh! zFc~i^UYv)26{Rkg36`8H3gFOpZMN|zMNGRjliy!fcO<`0d7xgF{^YVx_}6c)C^HRfVmVk21W2RC z@547H`o)(|%WKZp?5%IPhh;-xNJNQ5>&b0VZAgX z{+XardiUcW1|Rhg8RQ_S@k-)V;=7x1rE(p9l$P^hih?5pa|&d}`oW&am{p{;e7SgG zMF;K;3v!MT$ojJQxA5;iLHHW>YyG^Lwo5hlCD%iQl5Yr&z0A@3nYSBy8g+a!RG^^U z&n>9z`%|4QpKJ4S9a$uK$y)^%;}2$jy%UW~J4^;Jv~A`6aT(Ap9@yyyS{lUyH|d9) z-4@>Qb^fjJ$IUEEeKWc>w2v?oyQ5-UICnHfof3bTHR|}HEa=II!9JJdH+Mzm32&N; zRIg_rF#LW6NgW9)TK2LLn-n<+3P=tT*Ah=)v<1r!e} z%MShziu!F{cfcbhu-^9VNFici=ZdF-g-S*x)4i3u;QZ{`i#A8ii7T)KF2m zU$OLb8*9S^kc;^t^bw1=?qd?w%9Z;04(hsbK)@|%@wn&?=5=B=w8RYxVM6tiZc!-- zNPmXc%&`+7eMPVg!rjV0X^C2D|-5!pVs z2p%;|AlD9L+&R1IQ7@OirE_5*X+8+Sbmnh=0Pjt zV$L4zvPsY2OR>MsqU@Y>Gel@4 z1K-jajPsmA?~NmduW@_1*>TV240Xl*7!SR|5zO&{Evz zt(HJgpAlhA=bj&#dnY3+Sc*@#Xd8w*!J~3@GGFMnia01Lp59H$V$-Do|L5TD=)Ulh zt6g6RR5yk8?j_1`ogWO~=#jPf=iHMB%k; za-KJJiFtMTyKHh!cz#IpkoB#`&pVDzCWZD)z=8#_xf9ypdP~Tr3ObhmOu1%3+``oN zPZpE}i51a8#Q(IF1%brtHcfJ+JONBv!vWRf2A+5YbRlbT<8I@(by4sZ|M0b1h{E5UCCa|Gm#tG0_V=!;m8CRMK%0+xfYugB#h?fVQ+QxG5Nub-UGN z273);P97VD4qyc{c<`C&t`RzT&4-5LFG`Owud(BkVkd|NUNAy>`l4oC6O;h8jh|CY z293p^58dc0A%2*TeAZ5t^5$*T{}JsiZ~Es9%=aSQx&}pZbt{V#p#FRMS+PrX9OSG4C`D{YLw32fh)RScE_u(ufz;9#i$dac0ogY(R|d$!zF@#Ncx z=K-H7_Hg^gslF)JZs!?E$;#N*^b*XqYJ&pP7r zR%&893@KK3M}7`~X+GDz3HfuHU@bHooyTj22OpD8+{Oi}L8Vl$-&hRU`tBf2K$ZAO*KOm#^Wg5duuAo&SDc;Cd`5gJh`|^t}A*?-XW(bBI^g!-!mtix?4zs04 z*c0H?n`gXgz`y_p^mWZ(=6Zg_^HC+mIy}2TEdj#E+j~CD(agI2fKdkKKx%eZ z$0Wyn$c{me4A>*})9%-mp~+E9-TO?Z#8UDnlh<-oz3kO)i5tbEJ38z>Nlm57x=7*e zcfc3p!)y!CmcU+K->brzX#I1n;93KTT@M^pOD6oL%9$(bw%^OBF5#bQjHlDzkKiLY zST$?lW!AJ#C`8`l_iv$Elixqyw7t1~1TUKk$wU`rFm4dLdR+s4KAJyl#1`($WR@wgU0RF} z?$q3%KG?^XfG6y2BERNpt_3&HjAgGi({mn>=2v(8LIr&oUfJy=1yeQuMLZ8|Gs6-kNgIMZ_ zr}`5nHk8ky*C(!~cfBzb$w${I40Mw#lXA^|&mErkt z$HNmu9(vnL7>OVaaDq(W08VETG){|SZ&$~@cb8D)z9rhV7MTz83xs_+sfUX671czd$4BeXEfJ=3Bb9D7qY3 z$`)Zcp7FL&RRvk9`>n)KOxWN>WQIK1MH@_4;OlhrPF3e8qL$pDS_<<=Gn z9Kx3tL)8!clxaKN3YfuY7zeX|5iPjmeLGvDCVnAiasU412X#4Fj^gExZgMhP52$KT zsydJ5aJFWsc|cm0$#eeD?RCTRD{bNNM=lcfScNy}4EOCQ&JWM2oqUU4Va%hk?uKUX z*Me9Fs6OzG2SPwu2>oC>6x;Bg3{s3`XrLbC8sF#gg89++9C@yaOUHT^nivoHVk@?v z({EvL^1o1ZiMxU{*!FB`MQcQfu7i$#T{gqIDg|5#Mg#)hc){@SDykvejaooV~L>Hlmf!)!3tLCe_?&FQ%TPN>mzsD$x-It$>)JXzVc z%|hcF&4CmiE`DKeiH9Fnu@>yPnYKLUY%f$cEgacuOeF#hYmm!o13$YRMzs zO+GSx`!@i?=CS>ivwkpD?MmOHQY-T>_LxipQ9Zqjck_5qAg_aI&dr~RNcz|tcP;#Q zAtd;xzW1#`CT=Ro6s!F>Ka%4Hw(q?b50A!vSfn?6{g|&vx}V5C`^h^I>R{+=eOwN_ z>1bVtRG1^9ypM9ZQFjLaVq}?Fz~jb(`o-E8?M16!a6$k)^b<6F5{}?Z!h7FVdQAAm zO$AF;l*-DX36a+bi>@;ZZ41W|TuU&ck>>kF*T+{h`K-mH%AYjc1Hiy`@YB6XCH?|# ze)M>%($BM=cO$)@^ob3x;U#LRT#o@Ltn?r_-5>kC-Gkxwn^&fne}si5zw){dwrvC) z!7km{!$zZ9eaUW)mp(Pow;bG=?6n^rBKm_GYse|?L7QwI})B}=5_w$?Ud{*UDr{O|Jxj4WJmVd{OO0=HcRR2<8jp3W?_`C+4HU?9116F=e}LOpvvSFUNdXl zLoFKT1MmhBb!Nd@e}**jr5JW~10lN@V#CVamm7`FjIc7K!6#;H4V+kU4v@np$iASC zt~z)ddnRZlu|$4QksO15f0T~Bxa14P>|a~H7uL(D2n-(Ryx9vDue;~WT?odfB62f$ z`P=>obWhhIupxkTmt}KX?g*$B6dDDLDS;OjnjQxGFlbJ9i2SM}*O&isT18!ga*2g+ zA7vWfhzP-rh&7@aP&Z{o@i}cs7m*!NrLb)9tMbVg%7}g|2ER4unftzJp-?}mT* zRrSQtN$(FFy*UTy4yF62YgPl`0KM;`UTsjj1gJJPAvmF zuP6Dr@w1CLQOK{sG zuz577`xL0CkR#=;gnJ7xv%LK$dUNM2(CNrI#D&_O)4&jOcD|wb7q_SfTSJEo5A#w* zFOfdE9vdcKh!(i?dcJ@iyiOz>RC{qo%!{VZX$GBZzXfz zz3akMB`_=?W7|b2GOVbT6I<3FUbLD`cc3Qwgp&-GsogbxF1z+#QX#lFG4Q=X zQO9HovIqdB5@5Y>cI!D^3olHh1eU>tiYCz-i!Pi?7Sw#Ra5e$3wC3CBNBA zap#kf{b}Evr>Mi`>-*jax!}-j>DRZHKhG8ZW#d8(=Tc6thO^uZU#LTEWw83CRgA-W z*>WL*eWdXTZDDC)Or3J^Q1yTRI-K|W|Kg9NctMnDUl>}+m&Bo_iB?32n5}xSoO+8i zm$YmQi(RoNHc#-YW)e9~n>%Z6goTAMzf`e)w&$bVJs0EE_H~Rzit4+sc1J^UO9ip4 z*YAVL6MJK2f3PNHesj>iT<)Y{1ZCX}^t&;6Mk$82R0WlHjCEQ(gPew&|GKY{*U{&3 z-vRYmdrdHYm{-YeO zQD{}&)gGwb?Zr+tcKBjzy!EWj+OzDCPcwzjNI*$CI0~tHMh~Fv<}PP!XpMMya;`ns z9m)Y{ba(g=-m1uq{U~^bmgHU0`+YfHiPK0oxz9I8FJJ;s$sRkbhpD)odm)t9)P#pXrH%)dXd|(YIK&zK(DFW*Vr>Gi_UEI;O)ijxjbJ)QS-LV}a^c=- zrT8BG6bR)A?=Zv~`NKfV-C-oHlYL3g$i~;HgB$5a92VowJdKXBPqxc09B&I!LgJGM zrMhSDlqZC8n`kyUH6O=k=*BE}o?ih^(&8rz zRu*f-fH`PA2IozmxPoi(2pbpi4fKvw33yy_MKX_r%Brys580w|SPX}{5aH!Bkl(28 z4_7ec3iu)4nmj&{FV{ivXdxwZBuMx5&9c(7e{t-bNfI5lJdirP^&1HM1Pke2mva-% zds`GC7#{rhPPqz)h{uF*;esp&H8?0D%u})a^it6(s;0O?daA|xxnVGTO zmZXZMoaoxr_8_vyfuV$)(f?5g>Ql+poVKG8UR5oB!Pj4lVUET4t>C{u#{C&y2hrsG zYm*6QN8$z{BcV6Hm;u}6D9UMjMJC=s`_Fej>BB1~tTaIM&bv5kReSFI0p_|@C z&TpEqt!+jM-(%*DMBKe!LM&tX@_3}|In|wG89VXhJ&ShheaDTLPPya^lQ1O}H6?i% z*$Q5W*dT={Dbb@u%)T;{Y=qoXp8RG-ah8I(nAwsJRVT99GRo$q@&s9HLQnH#-u)=DCy3riSGbbAo$<_LpvEAKq-g+%;!K zUK6c;5M!Y6dRN(L-!2k?p9;(+BRx92ET2C-k-BpdHuv%&3$w6_eau>1kCz%=a9RVm zAtlJ*Tl4DP#I|{$a4kAE`NY5CsNTKMzRow!F-rUsW2aH9$1-hsqFKn09eIN>p>m`Q zaWIU%j!%vyn$k8ZNSilQef#7Xy}=F3I5-lin=F|~adzTm3@T?QaUvhTEYqQf=vGg&j1661 z`-XJ41i!jtR+o+D#k#YV|6&%dNYFr<#l!Nahov-l%>xW-@uk2*Em-VXRsT&|V`Cj+ zISCrdo3h$@pd$R~`$&_rr75h~;xV!OdW(khGdq`}D4emeOL@1yirqW(a`>eZsl9=H z$zg#6^P0-wut>pyckhFp8s~I=-icxw(3m`>W!GiU2XHK@a4$J&fJv*zZkNlcE*u=$#1l z1B}I4n7BuhB!gaDK3lT>Y1Vx;sr50ptYSe-nnkEJBT;(3{dkcynyPaAMi&|| z`=(o^;dN*eGl&1lcyRtJO`*{R0i?&H0?{5zzU$ATe)b6b8J?MmXAOL+EL|0^i`Ybo z2l6Cr(mp=iMV~>vlk=<`ciTPq-YYT3V~GV{5~~KK0L{PnI7;$J5dMwcj>W&2VX5NfUPgw-lUGGJHM2KQ!$wg!aHe}0(5!G zV+>$GFp~gj$z;XXe|NH|az7J+P%h21zq&^<#MeQq4P*n~1=6@g81MJAn*UvNSv*2s z-AXgol;Z)J#Wm{6X(e z)Q5$&DlYw*6}5EvK{WiW!>_NDr89Z0Rb(motGUQnLW1OAcyUF748U4cto1$qtray{#*hX=GXKzAU;l%r0oCmgnVm>e(w^uYT zM(DXrjo!xpQsxcOjDxGX5Z3ibjUT5Q51d5e|0Vm7Ma&$D|HfKoKN~bL1%;&UngJ7 zv{QeLVLmMc*>PCO$T4L;8m4vZ88M(dUI}sSC)F=3e`NT1QT=Psw#Vb6y`}+DtOVbd z=MC8_yT*P)hd8b9&;u*6$#0Hf{^5(z)xx$mcP1L1-3OLNkGs|NgSxDgYjG?O!8R`$ z*v*)L?=wsD$kmtVV2`)raWa8|E+xZFuX0HhY2|6JEh}0>mTnkGmzFakY;*}Gim6ah zW@tCDvxFRGE{o#)zAro^{MKYhQgy`(0{^ejV)N=ey-)>hamxdmKRr*LROAW%^~#1t z^*y~i+60=fA%9%RwzFu$ElU&XR2{Y7 z^fwK^E+2EETXrkR23$l~beUq<1s$E`xCt>ume2nP>yIm!|KV4vi2uk7{)s&$jV&es zOtpiXH($ys03v+gomrJHfp{Nbp+AnjB}7aTb$OQB;m}Au7013`k0CWH?Bk<@OFf95x{;dazHDGx`NWlEO{4B=C@H}Sx*n(X}fd$ded?wGe|KNqEj&t85f#b7au7@4X z*_##_dTcdsK@Yg$bqIkrU&6c7%7gP|opx8M>dSLu7ljx6J)c*CHx6&|hnhDSA_t%B z$Xz{4cYHtZ_sTNGBoW1Bcwbyx27?!{0awb3WWounAQxE=%2)kbZC5EHK0rG6$NUDM z?+e~16Z;?Xwg>CO73_f%B2(-Bbvp!G4Y2PcAHG2R^|Wi6PLogVIxT0;$l_N*-$S*V zS(oskDbe{`ufT~EJl-z!K|EiD6Gx zYT)Uq6vbjGv!$6xR^Z&gmB^3Qyy)fQwPT-!mQ>br^D`0Q?GssHDFdmg6A885H2bXwD6f#eF(FL3wK2%O2G(vIIZ-nBn_HadMf{?wA|g8AjA?>>y#KKE_3F%(tPdhP|Q zetvJU?CCv?237CIY>Vj%PmbZ#s3{{;YKjkh7wHboPP$>I1JMxkqK*nqdBjmMP=_5v ze^WA3H0yKr$hGgT>I-c2U&YvbIbDk)*l#1_2|XddQMvQ5u>`-Px6xeu;Ll&wSLr4C zFAA3VuN~JA`X_CCaee>j5?ykb6C+E;XvIgGzNAy`{=2`F@#8TB^^p=dW;#g+$VQ)^ z{t!mbXxy43I6HJTMMUCRbu@IqYQhxAa5Y_g%jq*>qtMkFu7oZZXkPWK{K0eTv=e*6;bhCaN=dY|cz)N@Tz7Y5bg@v&9U9NM!uDWEhwNa3$=qzumvNzxu|T zs3@k_23I7OhlWwhtP+@BH(b{o>WbBW@A}vFtDEOZ;vbH6K6&@4h`y%1Pk+K?yQ9xD zW}S?bAkQ>I)5j&v3u?CWphCtUp3bU0O*lc?V+qVpcQa1Zl4goRdC6~VIS zt6zEZbAUA8(&~cMpkWqQQNr&3`d=Co6nS(CQt2>E7uw3U??r0}N`vd>y|km0T&Y{~ zwy^5|%Iz9O+UOm^o^TJ{oAlQgGcCF+->q7T&VJ#kc&>e%cr&FgVSNw}KbJKrq@Ll* z^djD~{SsH1O2vVt2MexPGE$;q`W2Jo6@(A~0Q&~CQ-ln@ny%lPouY5oIl84uZ=->no^2v|sGrv``-`Av2+>GV(cburo zU>owWz#f&<4)sT(>*;626{j_bNMjh4s&hb9?;Kkm;dOezb579oS5D^W)3`oP~I zEKBjt85x#dy+h7ps3R82ffu4_NR><{!J;f0S}YTQ5Usk3t=NEs*|hOMB;+ zfwK?i@7#9Y47gk!ejZ{(6;2{xe;->LGR^q?GQMF}1{=6`Z&j{3)34b|)sHaF>#uNjA|HKkoZ9+y%E?-8eOriauz7mqG>$|~ zH;0>2TGR8m+tIyC#A$CNRuu_-kuKMiR-HBz^&{Lmi$A>Jj=L*n(yf9ca}hhn7`yto zygb?p^ESHi@o`KObjCE`B2YZ(w5)Z19#l~8KX5C!aAvUHVQ3T>PD@nNNG;#*^XDtq zCs$zXT~bY<2_SrD->?ncE(Bh3@UwlngveNoSy;8FJlcrzp^jFBw)OaTdVak0sZvAy zeZi8CDqaru1uC=@S6d!x)U#=Ij7BLV-1e2ec1zY$@@-=0KpSjTD2$v73+9T@vTUPw z**%+tg6Wa#*&L!PMPY zEq}q0ASc9Y&DwHE2FuZgd06-x*5GtQs$yJNK`A3IYS2mLouD!$)g&kF@z1=0#WtW6 zsz6OCT7#`b)fkH~lC-&y)Z?&se)O)Z98!=Y;~@4FHb75d`Var$LCr5=BF;&O*%66l zxir~{l`xo-f0Ug^lLovLdm)j^lxpK4rzJ<3lvMpf+@--7(}+HB%VhgXQsV;rq{<>{ zDfG<9Z)4SdBGUTf7N_H&lpRF%{6$mhxAyOvdDE7Al34PYmp$w@nt7N!tm{J+zTQ`8 zrU`9qSIYcA3BM4e>q7pgAVz=UAy%2FJSD3fZ<~2k{rN+g{!4t@#2M3ktddCjak7`p z%Vh^W>O1k_K=b0&ST8Zd4O-Qd9jc5@8h6; z^Z<4pgT+*K%U*EWzp)Q@Wc{Z|5oZ?exX|G`3Ox@7@a1iEhoCZo;w){dFsPX%qw&>Ik#2A8sq#OJonjo2n&-FvhUg04Z-zhQ1Df8Y5X z_2;w0b^z2JpVoiHb5Lyfl%h`0`^idRKVE^js^sO6ulDq}<&6|ksFbxm^ZL&V?zxyD}!jSx- z&$aa1U#TbEkMdmSZ|d2a*AHZU4%Dc~)P1us(L+gWnJRkwSecykL{8&0oqB&-n^5&JU>@ z(tOtacKK6&UDIf)8*A!~kB{2fde3q^sLU8ZJpGaVGT`9{b1a2W{>%#{9=3F0-$qSF zxC%~m*2Ry#Y&&Fm)7OY}D^dkKm%0@vtg<*qhr9)1OC5rBHV{M|aU`8;eP%U49p{E$ z3u5(A0zC{o5|%5of-oWrEFB`H7(GIYs_a7m!JL0wEn34O`G&I9;E@Q1Tw6f8O8Vl|9UpPSc{R|G8sqQi>T z3j!sz2hT_*C!9<$k{(PeN7?vup$?J8jAxUDP|%h}q$CW}>chv!_n#7~Z7~(1?Mmd1 z$t0ep+gQ9ia}$+mEHD?SuB_p<&*FfO;K2nR<=!fHQveYHnj@6IDYo%yBr=Q?o=ua_ ztj#FDoOe+?s)#g>>V2CKw9lF+q_9WU4xac(p7VkYs%--LL?%_BVxA2*qHjviG3 zY;Cgh={j4){`NymLe7C^wKcdu%>5e{CE|gM2#WgsJIoH_Ux?q zcRxwxIAQD7LAyx7<9@Y`%arnS0T^h_A1k60;fK-Uc3KuAsskYj-de{SS3X+)^{3Aq zLeDRMKW1M4vbg3OoY03bqh#)VCV2&KFcvfkydBefX5OWe^Ie*H-U4eM{yV~gg15Wp1<*Ix(&Y41vAc0ti~|Dlrx-xCK+kczXdaoKR0dR z;L70DSqJ2@=4(biR(>39<)J#OK-Ig2oPN~K|8DRRrX17#<)D6MNh_G!4gSX88!3Q| z-WU8u2

Y8q9_a3_%|Ut5yrruwp$C#X5JnXRFh{>3ac|DT$+SE&U)pAbTwSZCJ`I zB0YO4B=N<}NiHW`nwjxsh)wZIwVMXK{+gj^&1welqSehK-DI-sC z`tXK6$B={PeN*qd`AoEJ?%a8+AC|g4LKtRv?3T zPp5^Gvm}&Tu~jMre+#vjDj7~jKDg*iQh;6_z|uN0oB1kJh45H*f<0)wKDj)tS(iOP zAN(K*k1mCc)mcJbaCDu1C{=7W4ts{J?JG?@c~0hyP#jz@jB*+FfpUgD9F0_L_?fftWBxwlj_cwBXSBY<#r{JEuUj#kRFA?BvNpl~C${e#$Hq*_pB46a(RhY3 z5&peEYNB>ThAMTmUemBJuKj_e7}e*S>mrjaF;q}vOLC216ZLI~#df@7b@uxZ*~- z^8MTQ5fT%NF|Ve6%1nlM&YIPVXBCjM2GF99Q{S@omHU0ZF#lxvwYDNFAJrhrnFD#u ztJA*GZubBvuPR;)L`-@0Tp0r6V+e^AZ0 z9M@saTIyP#Yo8(}L_gtTT9T<{CZ)*`o=MophMo0K9Grl{Co2**x-_haq97=%fE6T% zd)#etgsh@U`w5K3b-dsLKLfraLgBuu6aeSr+sQJU`RMQWW}lgs>q72nx_AQ{h~f+U z{L1v{;99}%(GPqW%NDKQtFBbTEqv-!Q7hUVaFwNf1^^4))8>eMxs;D+)V4HoQfn2y zB8gnRF;x)c_fiFmpF@5HDfW3?Yq3Iq9e-^&cf{K9uGEb~Z4rRT1TPbr!_DPG_PQM} z-I^%;1dy$S*mlqyAvAiao05eQ8{x`I>*(glGt{{PjvIM2D7 zo7pq-JhRtcd)BP?CE!AK>P0_bwKY=EwIeX_wsO*z=VGfTn zmh8oFUq2EPT0<2GDO6qURK%a=n-;RdooCyp2(}!5IY<^qkZpHW?{zPx?$k%-h7>ji ze)j1}O%(%Sb|#&DFRFfb=Gxuvd#vp1Jt&k6+EJ(wEi zdoElB6)V~|TM0sI6S3~$Il)_rP9BkuY9qC4)k$%E;*1(=q%lA-Zp?{aYcq@Ht& zbzEv(MylThOUv(@sG-~#sg+5(BM^;Tq^p|TpP4C4en957tyh}?x70%%7 zJA-Nl_u@?T>?a|}=H)?`~R#xR{oap1ZDsVVr* z+?7e+vBP5LE=i9T2+JNUm$DeF( z50WrXiIvEgx$w}SilIX6lW1wg2Ft$?m_#}1^w9lVm9`B(%xSQlSg#+LP-4->`x*QeO%(0k6n?sh06^eRm6jlGE*1 zhK9*pYdSsYGg-cMKzA=?A!4iD4tLk?U&Q4)-w!ainpd-};#z@s%SQ4)_(O)HCYBP8 zkx0UpxbxH0W32fv-IFH=<>zEzTE3REk>ls<_^FU*A_@x|e!xb!`m43Ul$zv>u6Kg) z$l{|9q{wm$s~{<0;xbKw69d4(zT_IlxHfR`^&6jt<4jOysDyaf@t2ssQ3whE`}Z4cwHC*_U4vv zR;G;<(xdp1Vb&6fA$|SDpruMZU8Jdi;ayDDBz#i$DjvrNyc>O7{(4e!j{>N(SQPC3 zG39IlK>kdQ03*fXZqPHJyJmT~?N(aGz~7W~!~B&pii9=*-0R zb0tAN25DQ`&SB}qD@j3#QSgXS=zATfkKT7Ww&SUOZjARHE4M<^Kms-7^>4VT_G}!( zl~9t7ALl7aW>Yd~J%vJzz;w%B&~}n~;Rs!P*oWbs(jwH+zEUKIaA zQ388S$aF?_$c$p)-lzLnJdp85#q8-&Q4!OKn_7@Uk**``Q4@u?-iVuw?5~}-%4@{Q?2Km+t;OTw%HgDMcejrYPx%12=vmX4$+SfiE`f&EKfzhpo z=3INB2|ex=+D!fk=^*ruyUj42-e}juQN@K}&XY9qQ%stay{tbFK)2?o-nh3g#5N2o zN*7E3Fv|1N?pG<>U7!D1nrrFdYF~?i^1yTW!S>k>Z^v)bL=g;C4yv!PDNsP;E`|~< zo?Ld9`J=vvG$XZ$R_H@b86&7Y1i1rz)c4 z?gmUGx^1%fK7WMgrAF;-v2H?jv86Ig%O;n>OFggMyQPGsfsVoV5%=sZ!=vrMA0;$; zJ({SiyyKXZ7}5F=w}KmD)~n;7kd5wo_G;QYW8lx9G9JOySem)GMOnj7tV@@&EX~F{ zyhLk`%~|%$mYpSzAaN;}N9!3(-XGopTq#trW6mjVkFTcxiV-hZtYoyXbK*_r>*ley z;z_Y?_A;|#FbsL_=Qd)p|0ka{s%RbuB3J`c|FHC)puBg8XNM~XI|2I*(Dn2#w*en* zf@AUb14fSP!UxtmiYgZGJNYsjY9x23Kuz5JN{bwi@{ZpT30G`f;}ty;A`nBkG?w?D zGa+n4QqO&6q&e#B_puN}Vr>zLPk8=ka{=hfrJJ~YZm1nj-OM+4_P4Cx8W(#$^lGxl zz)?7sk1eWBWMhNIZHjlwwpv>nkpFNDeGx8~8;Shd=N<91@tFXPOSx0y*xQ#DP%&?H zuxe6@ie1#KYp~&M?%bQ2JN0Gr;RFAq5v)dGfn>^o%)wz7L7zFpFe@gQ|8&@8PvIBm zGlDtRqQRgN)lmHd2oSgITG1PmmU+C-gG_cE0x7duc(1%)%SKQoq6F5cQ#G}oiQbNV zl0N03X*n~A2#Kb>aZKu~skU^wtf{pSi0Gid2+%x5H%YeRZl! zKZA5E$JcXD}rO3;n}EUMl2Vr3=NV2PY&>-H?Mk}xcP_oYVuvu zhhLRntLYT?hE2`w>4}wxrR4Ha8YYJR z3#7>T1N#}=OI800NLR>TtCJdn5%N&36QM-vQWa0ANVM;}_5e9bSX)`K^pdxw)tH9* z6Yplcb;p23&b6PE4GC~&28j?3ksgV)4>{{BWc|+hMtjO5GOB&Pg*~K2Mrq2QOj}Oo zOW&-G?8{zo(O?c#>NCy9#x}_D1c6M+gpj!5gBZ8lYHGG?a(A5q-cmPQot{OsG5@g| zB-3MT8GPExMqsu$@;Ack(F+l12~fIEL6lacEPi#DFOxhhK9@U;I~M2`8k|y#%+TRxKM`~ffI}`M7_^;TNR8zM*LE+ zbAX58g2Qu=nbRbefQlJz1zFYncAMxI(`SGr`Qh32gD5- zsWCBF@XsS1!`d_Y*vZ?BoZOm>L~tHb8QLnraB|1|+1S23S++-B#krVz-pitbU3mKu z*|2X#pm41vL^X71%N(V!lC~=3b{+iN4@}wLj~f|?$lgFobq0g&~a&k*$`gIU}AdifuvoP4*tcbTPAxJT~>_GP4!GMv$S zt-IKou=RJvCg@-!qDmx2L+~lc%Q(_fS*wLUviT@-fOhv>#dX`;OVRyxD2sb_L*}7( zlT89334@vl!`Qx*+Q*o)UogU1*><|VC*0t3(CM{~Oog7JF1R`+*4!Q85jq_Dn>&K7 zvqc-=J&wTi=*UPsZNTtCk;>F^tkT9}u>yU-7~@STGfIBS_PEp>9&P>k#78j%*q_t0 zu>+R!+P1IdhI8&sKrOoDFa?UbP7Q0DeLZl{N{`#PqeaJ{s6%2PlVG8K)B{)6ES_WQ(=Kg?WyN zobS`CLm9<`#`fra~mz_t2mfB)gl< z*7%dqa?a#tXRW#Ck2?{HR$AFsBZ7Hem@V4RAa_Bu;W^~}#J+k`M%H9bLEOt($(XMA zV}2l?K9zvcQIPqy{q=!W={C=5cP&y1HgI1sK+mC&HtuzlyJOpT>h(70AeB$)0EV9! zEf!>ahe)jV>29D}2gT~bJB`^-fQ2gC4$n0RGdcG|mXjUc;Mo+6RJ6ox=4V;*XNA2R zmSj)+By&l10T}4M(E^z9!C5G9&;OQF)zffd|5jKM3Y=ffu}Dc}6>8{A@jN)>KjGh9 z4FbR!B#xh!8p)t14NU7zDK=QU7F<^WLOk9F@XM0lW41pf6BN|YjankCeOs9VKFy_9K78_ z90itmZQsa$lh4+L;r~DH;Gq8>d&(TRS=#i~4M3vE@273$=j`C;qUhw~0vtfnC}}Bi z)D7_)C^M-Wiqf)*GP2?*IYkue;&Jfo{~F-w?fk$M`+pBe5$n4T3;^kC8*9~QU?Tqq D`G5c$ diff --git a/src/console/pythonconsole.cs b/src/console/pythonconsole.cs deleted file mode 100644 index bf17848f7..000000000 --- a/src/console/pythonconsole.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using Python.Runtime; - -namespace Python.Runtime -{ - ///

- /// Example of Embedding Python inside of a .NET program. - /// - /// - /// It has similar functionality to doing `import clr` from within Python, but this does it - /// the other way around; That is, it loads Python inside of .NET program. - /// See https://github.com/pythonnet/pythonnet/issues/358 for more info. - /// - public sealed class PythonConsole - { -#if NET40 - private static AssemblyLoader assemblyLoader = new AssemblyLoader(); -#endif - private PythonConsole() - { - } - - [STAThread] - public static int Main(string[] args) - { - // Only .NET Framework is capable to safely inject python.runtime.dll into resources. -#if NET40 - // reference the static assemblyLoader to stop it being optimized away - AssemblyLoader a = assemblyLoader; -#endif - string[] cmd = Environment.GetCommandLineArgs(); - PythonEngine.Initialize(); - - int i = Runtime.Py_Main(cmd.Length, cmd); - PythonEngine.Shutdown(); - - return i; - } - -#if NET40 - // Register a callback function to load embedded assemblies. - // (Python.Runtime.dll is included as a resource) - private sealed class AssemblyLoader - { - private Dictionary loadedAssemblies; - - public AssemblyLoader() - { - loadedAssemblies = new Dictionary(); - - AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => - { - string shortName = args.Name.Split(',')[0]; - string resourceName = $"{shortName}.dll"; - - if (loadedAssemblies.ContainsKey(resourceName)) - { - return loadedAssemblies[resourceName]; - } - - // looks for the assembly from the resources and load it - using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) - { - if (stream != null) - { - var assemblyData = new byte[stream.Length]; - stream.Read(assemblyData, 0, assemblyData.Length); - Assembly assembly = Assembly.Load(assemblyData); - loadedAssemblies[resourceName] = assembly; - return assembly; - } - } - return null; - }; - } - } -#endif - } -} From 0ea1d29f912e9e4e1b43c4179df842ac2f3a0af2 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 8 Nov 2024 13:50:39 +0100 Subject: [PATCH 075/194] Drop reference to Py_Main (#2504) Was only used in the console and might not be available in some embedding scenarios. --- src/runtime/Runtime.Delegates.cs | 2 -- src/runtime/Runtime.cs | 14 -------------- 2 files changed, 16 deletions(-) diff --git a/src/runtime/Runtime.Delegates.cs b/src/runtime/Runtime.Delegates.cs index 6490c3fe5..639ba4812 100644 --- a/src/runtime/Runtime.Delegates.cs +++ b/src/runtime/Runtime.Delegates.cs @@ -35,7 +35,6 @@ static Delegates() PyGILState_Ensure = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyGILState_Ensure), GetUnmanagedDll(_PythonDll)); PyGILState_Release = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyGILState_Release), GetUnmanagedDll(_PythonDll)); PyGILState_GetThisThreadState = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyGILState_GetThisThreadState), GetUnmanagedDll(_PythonDll)); - Py_Main = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(Py_Main), GetUnmanagedDll(_PythonDll)); PyEval_InitThreads = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyEval_InitThreads), GetUnmanagedDll(_PythonDll)); PyEval_ThreadsInitialized = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyEval_ThreadsInitialized), GetUnmanagedDll(_PythonDll)); PyEval_AcquireLock = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyEval_AcquireLock), GetUnmanagedDll(_PythonDll)); @@ -319,7 +318,6 @@ static Delegates() internal static delegate* unmanaged[Cdecl] PyGILState_Ensure { get; } internal static delegate* unmanaged[Cdecl] PyGILState_Release { get; } internal static delegate* unmanaged[Cdecl] PyGILState_GetThisThreadState { get; } - internal static delegate* unmanaged[Cdecl] Py_Main { get; } internal static delegate* unmanaged[Cdecl] PyEval_InitThreads { get; } internal static delegate* unmanaged[Cdecl] PyEval_ThreadsInitialized { get; } internal static delegate* unmanaged[Cdecl] PyEval_AcquireLock { get; } diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index a33e20b4a..d769ebdc0 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -719,20 +719,6 @@ internal static T TryUsingDll(Func op) internal static PyThreadState* PyGILState_GetThisThreadState() => Delegates.PyGILState_GetThisThreadState(); - public static int Py_Main(int argc, string[] argv) - { - var marshaler = StrArrayMarshaler.GetInstance(null); - var argvPtr = marshaler.MarshalManagedToNative(argv); - try - { - return Delegates.Py_Main(argc, argvPtr); - } - finally - { - marshaler.CleanUpNativeData(argvPtr); - } - } - internal static void PyEval_InitThreads() => Delegates.PyEval_InitThreads(); From 7a9e3bf5ffb6cffebed2832ce4dfc7cd3b688247 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 11 Dec 2024 18:12:04 +0100 Subject: [PATCH 076/194] Stick to ubuntu-22.04 for now as that image still contains Mono --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3396b83cc..f6970d85b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,7 +26,7 @@ jobs: - category: ubuntu platform: x64 - instance: ubuntu-latest + instance: ubuntu-22.04 - category: macos platform: x64 From 5cf2534a07ab8cc2041997574138bedee4a21ade Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 19 Sep 2024 19:10:46 +0200 Subject: [PATCH 077/194] First attempt at Python 3.13 --- src/runtime/Native/TypeOffset313.cs | 146 ++++++++++++++++++++++++++++ src/runtime/Runtime.Delegates.cs | 9 +- 2 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/runtime/Native/TypeOffset313.cs diff --git a/src/runtime/Native/TypeOffset313.cs b/src/runtime/Native/TypeOffset313.cs new file mode 100644 index 000000000..20a67fbff --- /dev/null +++ b/src/runtime/Native/TypeOffset313.cs @@ -0,0 +1,146 @@ + +// Auto-generated by geninterop.py. +// DO NOT MODIFY BY HAND. + +// Python 3.13: ABI flags: '' + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +using Python.Runtime.Native; + +namespace Python.Runtime +{ + [SuppressMessage("Style", "IDE1006:Naming Styles", + Justification = "Following CPython", + Scope = "type")] + + [StructLayout(LayoutKind.Sequential)] + internal class TypeOffset313 : GeneratedTypeOffsets, ITypeOffsets + { + public TypeOffset313() { } + // Auto-generated from PyHeapTypeObject in Python.h + public int ob_refcnt { get; private set; } + public int ob_type { get; private set; } + public int ob_size { get; private set; } + public int tp_name { get; private set; } + public int tp_basicsize { get; private set; } + public int tp_itemsize { get; private set; } + public int tp_dealloc { get; private set; } + public int tp_vectorcall_offset { get; private set; } + public int tp_getattr { get; private set; } + public int tp_setattr { get; private set; } + public int tp_as_async { get; private set; } + public int tp_repr { get; private set; } + public int tp_as_number { get; private set; } + public int tp_as_sequence { get; private set; } + public int tp_as_mapping { get; private set; } + public int tp_hash { get; private set; } + public int tp_call { get; private set; } + public int tp_str { get; private set; } + public int tp_getattro { get; private set; } + public int tp_setattro { get; private set; } + public int tp_as_buffer { get; private set; } + public int tp_flags { get; private set; } + public int tp_doc { get; private set; } + public int tp_traverse { get; private set; } + public int tp_clear { get; private set; } + public int tp_richcompare { get; private set; } + public int tp_weaklistoffset { get; private set; } + public int tp_iter { get; private set; } + public int tp_iternext { get; private set; } + public int tp_methods { get; private set; } + public int tp_members { get; private set; } + public int tp_getset { get; private set; } + public int tp_base { get; private set; } + public int tp_dict { get; private set; } + public int tp_descr_get { get; private set; } + public int tp_descr_set { get; private set; } + public int tp_dictoffset { get; private set; } + public int tp_init { get; private set; } + public int tp_alloc { get; private set; } + public int tp_new { get; private set; } + public int tp_free { get; private set; } + public int tp_is_gc { get; private set; } + public int tp_bases { get; private set; } + public int tp_mro { get; private set; } + public int tp_cache { get; private set; } + public int tp_subclasses { get; private set; } + public int tp_weaklist { get; private set; } + public int tp_del { get; private set; } + public int tp_version_tag { get; private set; } + public int tp_finalize { get; private set; } + public int tp_vectorcall { get; private set; } + public int tp_watched { get; private set; } + public int tp_versions_used { get; private set; } + public int am_await { get; private set; } + public int am_aiter { get; private set; } + public int am_anext { get; private set; } + public int am_send { get; private set; } + public int nb_add { get; private set; } + public int nb_subtract { get; private set; } + public int nb_multiply { get; private set; } + public int nb_remainder { get; private set; } + public int nb_divmod { get; private set; } + public int nb_power { get; private set; } + public int nb_negative { get; private set; } + public int nb_positive { get; private set; } + public int nb_absolute { get; private set; } + public int nb_bool { get; private set; } + public int nb_invert { get; private set; } + public int nb_lshift { get; private set; } + public int nb_rshift { get; private set; } + public int nb_and { get; private set; } + public int nb_xor { get; private set; } + public int nb_or { get; private set; } + public int nb_int { get; private set; } + public int nb_reserved { get; private set; } + public int nb_float { get; private set; } + public int nb_inplace_add { get; private set; } + public int nb_inplace_subtract { get; private set; } + public int nb_inplace_multiply { get; private set; } + public int nb_inplace_remainder { get; private set; } + public int nb_inplace_power { get; private set; } + public int nb_inplace_lshift { get; private set; } + public int nb_inplace_rshift { get; private set; } + public int nb_inplace_and { get; private set; } + public int nb_inplace_xor { get; private set; } + public int nb_inplace_or { get; private set; } + public int nb_floor_divide { get; private set; } + public int nb_true_divide { get; private set; } + public int nb_inplace_floor_divide { get; private set; } + public int nb_inplace_true_divide { get; private set; } + public int nb_index { get; private set; } + public int nb_matrix_multiply { get; private set; } + public int nb_inplace_matrix_multiply { get; private set; } + public int mp_length { get; private set; } + public int mp_subscript { get; private set; } + public int mp_ass_subscript { get; private set; } + public int sq_length { get; private set; } + public int sq_concat { get; private set; } + public int sq_repeat { get; private set; } + public int sq_item { get; private set; } + public int was_sq_slice { get; private set; } + public int sq_ass_item { get; private set; } + public int was_sq_ass_slice { get; private set; } + public int sq_contains { get; private set; } + public int sq_inplace_concat { get; private set; } + public int sq_inplace_repeat { get; private set; } + public int bf_getbuffer { get; private set; } + public int bf_releasebuffer { get; private set; } + public int name { get; private set; } + public int ht_slots { get; private set; } + public int qualname { get; private set; } + public int ht_cached_keys { get; private set; } + public int ht_module { get; private set; } + public int _ht_tpname { get; private set; } + public int spec_cache_getitem { get; private set; } + public int getitem_version { get; private set; } + public int init { get; private set; } + } +} + diff --git a/src/runtime/Runtime.Delegates.cs b/src/runtime/Runtime.Delegates.cs index 639ba4812..78bad2739 100644 --- a/src/runtime/Runtime.Delegates.cs +++ b/src/runtime/Runtime.Delegates.cs @@ -23,7 +23,14 @@ static Delegates() Py_EndInterpreter = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(Py_EndInterpreter), GetUnmanagedDll(_PythonDll)); PyThreadState_New = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyThreadState_New), GetUnmanagedDll(_PythonDll)); PyThreadState_Get = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyThreadState_Get), GetUnmanagedDll(_PythonDll)); - _PyThreadState_UncheckedGet = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(_PyThreadState_UncheckedGet), GetUnmanagedDll(_PythonDll)); + try + { + _PyThreadState_UncheckedGet = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(_PyThreadState_UncheckedGet), GetUnmanagedDll(_PythonDll)); + } + catch (MissingMethodException) + { + // Not supported in Python 3.13 anymore + } try { PyGILState_Check = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyGILState_Check), GetUnmanagedDll(_PythonDll)); From 2f3a0e79b01a30e729ec22bc50dcf0139b9424ae Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 25 Oct 2024 12:11:51 +0200 Subject: [PATCH 078/194] Add Python 3.13 to CI --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f6970d85b..ac603f32d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,7 +32,7 @@ jobs: platform: x64 instance: macos-13 - python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - name: Set Environment on macOS From 5a7b5be4516bd08990e6f80211af1759d2d4d3b7 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 25 Oct 2024 12:14:29 +0200 Subject: [PATCH 079/194] Update project file --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4ece5f3a4..ab2693a38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "clr_loader>=0.2.6,<0.3.0" ] -requires-python = ">=3.7, <3.13" +requires-python = ">=3.7, <3.14" classifiers = [ "Development Status :: 5 - Production/Stable", @@ -27,6 +27,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Operating System :: MacOS :: MacOS X", From f3face061ac4432762fe707081c3e437b1f42d7d Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 11 Dec 2024 17:54:54 +0100 Subject: [PATCH 080/194] Use PyThreadState_GetUnchecked on Python 3.13 --- src/runtime/Runtime.Delegates.cs | 9 ++++++--- src/runtime/Runtime.cs | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/runtime/Runtime.Delegates.cs b/src/runtime/Runtime.Delegates.cs index 78bad2739..262dc1e19 100644 --- a/src/runtime/Runtime.Delegates.cs +++ b/src/runtime/Runtime.Delegates.cs @@ -25,11 +25,14 @@ static Delegates() PyThreadState_Get = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyThreadState_Get), GetUnmanagedDll(_PythonDll)); try { - _PyThreadState_UncheckedGet = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(_PyThreadState_UncheckedGet), GetUnmanagedDll(_PythonDll)); + // Up until Python 3.13, this function was private and named + // slightly differently. + PyThreadState_GetUnchecked = (delegate* unmanaged[Cdecl])GetFunctionByName("_PyThreadState_UncheckedGet", GetUnmanagedDll(_PythonDll)); } catch (MissingMethodException) { - // Not supported in Python 3.13 anymore + + PyThreadState_GetUnchecked = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyThreadState_GetUnchecked), GetUnmanagedDll(_PythonDll)); } try { @@ -320,7 +323,7 @@ static Delegates() internal static delegate* unmanaged[Cdecl] Py_EndInterpreter { get; } internal static delegate* unmanaged[Cdecl] PyThreadState_New { get; } internal static delegate* unmanaged[Cdecl] PyThreadState_Get { get; } - internal static delegate* unmanaged[Cdecl] _PyThreadState_UncheckedGet { get; } + internal static delegate* unmanaged[Cdecl] PyThreadState_GetUnchecked { get; } internal static delegate* unmanaged[Cdecl] PyGILState_Check { get; } internal static delegate* unmanaged[Cdecl] PyGILState_Ensure { get; } internal static delegate* unmanaged[Cdecl] PyGILState_Release { get; } diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index d769ebdc0..c8f022860 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -316,7 +316,7 @@ internal static void Shutdown() // Then release the GIL for good, if there is somehting to release // Use the unchecked version as the checked version calls `abort()` // if the current state is NULL. - if (_PyThreadState_UncheckedGet() != (PyThreadState*)0) + if (PyThreadState_GetUnchecked() != (PyThreadState*)0) { PyEval_SaveThread(); } @@ -705,7 +705,7 @@ internal static T TryUsingDll(Func op) internal static PyThreadState* PyThreadState_Get() => Delegates.PyThreadState_Get(); - internal static PyThreadState* _PyThreadState_UncheckedGet() => Delegates._PyThreadState_UncheckedGet(); + internal static PyThreadState* PyThreadState_GetUnchecked() => Delegates.PyThreadState_GetUnchecked(); internal static int PyGILState_Check() => Delegates.PyGILState_Check(); From 88d98f2f1a69a4658a8a5053f86015701cde8227 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 12 Dec 2024 20:28:02 +0100 Subject: [PATCH 081/194] Workaround for geninterop failure to handle non-pointer fields --- src/runtime/Native/TypeOffset313.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/runtime/Native/TypeOffset313.cs b/src/runtime/Native/TypeOffset313.cs index 20a67fbff..4c2e71295 100644 --- a/src/runtime/Native/TypeOffset313.cs +++ b/src/runtime/Native/TypeOffset313.cs @@ -75,8 +75,14 @@ public TypeOffset313() { } public int tp_version_tag { get; private set; } public int tp_finalize { get; private set; } public int tp_vectorcall { get; private set; } + // This is an error in our generator: + // + // The fields below are actually not pointers (like we incorrectly + // assume for all other fields) but instead a char (1 byte) and a short + // (2 bytes). By dropping one of the fields, we still get the correct + // overall size of the struct. public int tp_watched { get; private set; } - public int tp_versions_used { get; private set; } + // public int tp_versions_used { get; private set; } public int am_await { get; private set; } public int am_aiter { get; private set; } public int am_anext { get; private set; } From 4bb673f960ce1f9aa77157dda4f2a83cb3476eb8 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 12 Dec 2024 21:30:20 +0100 Subject: [PATCH 082/194] Bump clr-loader and dev dependencies --- pyproject.toml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ab2693a38..31432f04c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ license = {text = "MIT"} readme = "README.rst" dependencies = [ - "clr_loader>=0.2.6,<0.3.0" + "clr_loader>=0.2.7,<0.3.0" ] requires-python = ">=3.7, <3.14" @@ -35,6 +35,15 @@ classifiers = [ dynamic = ["version"] +[dependency-groups] +dev = [ + "pytest >= 6", + "find_libpython >= 0.3.0", + "numpy >=2 ; python_version >= '3.10'", + "numpy <2 ; python_version < '3.10'", + "psutil" +] + [[project.authors]] name = "The Contributors of the Python.NET Project" email = "pythonnet@python.org" From 828362748ee097bddfe292e46ec6f748e7c3ecd6 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 12 Dec 2024 21:30:32 +0100 Subject: [PATCH 083/194] Raise maximum supported version --- src/runtime/PythonEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index 2c4c6c088..0b28c3a35 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, 12, int.MaxValue, int.MaxValue); + public static Version MaxSupportedVersion => new(3, 13, int.MaxValue, int.MaxValue); public static bool IsSupportedVersion(Version version) => version >= MinSupportedVersion && version <= MaxSupportedVersion; public static string Version From 1920b19749082c6b8c57fd77164f2fbe2d8d775c Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 12 Dec 2024 21:30:53 +0100 Subject: [PATCH 084/194] Xfail a test that only fails locally on my machine right now --- tests/test_method.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_method.py b/tests/test_method.py index b86bbd6b4..c70200c7e 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -1023,6 +1023,7 @@ 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(): """Test that managed object is freed after calling overloaded method. Issue #691""" From 60a057f36402578a69403d0e195925468a50c87b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 13 Dec 2024 09:06:16 +0100 Subject: [PATCH 085/194] Skip embed tests on Python 3.13 for now Verified them locally, but there is an issue with the Github workflow image that can hopefully be resolved later by using a full venv instead of relying on the system environment. --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ac603f32d..d21a761c6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -77,6 +77,7 @@ jobs: Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append -InputObject "PYTHONHOME=$(python -c 'import sys; print(sys.prefix)')" - name: Embedding tests + if: ${{ matrix.python != '3.13' }} run: dotnet test --runtime any-${{ matrix.os.platform }} --logger "console;verbosity=detailed" src/embed_tests/ env: MONO_THREADS_SUSPEND: preemptive # https://github.com/mono/mono/issues/21466 @@ -95,6 +96,7 @@ jobs: 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 From 8f0ccb75b8c5239d995e4f84f528c0dbdbd316f8 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 13 Dec 2024 09:16:02 +0100 Subject: [PATCH 086/194] Add changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29b4c4a68..e4332cded 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ## Unreleased ### Added + +- Support for Python 3.13 (#2454) + ### Changed ### Fixed From 7c87fec879573089e62831230878b7216320ebc0 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 13 Dec 2024 09:16:12 +0100 Subject: [PATCH 087/194] Workaround for setuptools bug #4759 --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 31432f04c..968998e8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ Sources = "https://github.com/pythonnet/pythonnet" [tool.setuptools] zip-safe = false py-modules = ["clr"] +license-files = [] [tool.setuptools.dynamic.version] file = "version.txt" From 0ea8e6f479426c5e6eb38e00843c34fa3153a6db Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 13 Dec 2024 09:23:50 +0100 Subject: [PATCH 088/194] Remove win-x86-py3.13 entirely for now --- .github/workflows/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d21a761c6..f7e3eaa4e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,6 +34,12 @@ jobs: python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + # 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: { category: windows, platform: x86 } + python: ["3.13"] + steps: - name: Set Environment on macOS uses: maxim-lobanov/setup-xamarin@v1 From 0826fc0bffb9aa6f91abae37f05e4b259c0ca94f Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 13 Dec 2024 09:29:39 +0100 Subject: [PATCH 089/194] Release 3.0.5 --- CHANGELOG.md | 5 +---- version.txt | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4332cded..a983b4ea1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,15 +5,12 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. -## Unreleased +## [3.0.5](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.5) - 2024-12-13 ### Added - Support for Python 3.13 (#2454) -### Changed -### Fixed - ## [3.0.4](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.4) - 2024-09-19 diff --git a/version.txt b/version.txt index 0f9d6b15d..eca690e73 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.0-dev +3.0.5 From f361fa9a04ff578fecf0137145438c300cf98689 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 13 Dec 2024 09:31:38 +0100 Subject: [PATCH 090/194] Back to dev --- CHANGELOG.md | 6 ++++++ version.txt | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a983b4ea1..078a6ad6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. +## Unreleased + +### Added +### Changed +### Fixed + ## [3.0.5](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.5) - 2024-12-13 ### Added diff --git a/version.txt b/version.txt index eca690e73..0f9d6b15d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.5 +3.1.0-dev From dfd746b339dc24d530f7d64768e1c8acf9d84cb4 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 13 Dec 2024 09:33:25 +0100 Subject: [PATCH 091/194] Drop icon --- src/runtime/Python.Runtime.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 5072f23cd..0a7f58a3c 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -12,8 +12,6 @@ https://github.com/pythonnet/pythonnet git python interop dynamic dlr Mono pinvoke - python-clear.png - https://raw.githubusercontent.com/pythonnet/pythonnet/master/src/console/python-clear.ico https://pythonnet.github.io/ README.md true @@ -48,7 +46,6 @@ - From 030a9f9916563babc5055ffeb9dc85d44831ce74 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 20 Feb 2025 17:29:11 +0100 Subject: [PATCH 092/194] ci: properly exclude job (#2542) --- .github/workflows/main.yml | 6 ++++-- AUTHORS.md | 1 + CHANGELOG.md | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f7e3eaa4e..8485189e1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,8 +37,10 @@ jobs: # 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: { category: windows, platform: x86 } - python: ["3.13"] + - os: + category: windows + platform: x86 + python: "3.13" steps: - name: Set Environment on macOS diff --git a/AUTHORS.md b/AUTHORS.md index 6aa4a6010..7ea639059 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -59,6 +59,7 @@ - Peter Kese ([@pkese](https://github.com/pkese)) - Raphael Nestler ([@rnestler](https://github.com/rnestler)) - Rickard Holmberg ([@rickardraysearch](https://github.com/rickardraysearch)) +- Roberto Pastor Muela ([@RobPasMue](https://github.com/RobPasMue)) - Sam Winstanley ([@swinstanley](https://github.com/swinstanley)) - Sean Freitag ([@cowboygneox](https://github.com/cowboygneox)) - Serge Weinstock ([@sweinst](https://github.com/sweinst)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 078a6ad6e..1863a0806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Changed ### Fixed +- ci: properly exclude job (#2542) + ## [3.0.5](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.5) - 2024-12-13 ### Added From a6746588785516f0f470d01ebbcd14267ad0c17c Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 18 Dec 2024 21:11:06 +0000 Subject: [PATCH 093/194] added NixOS FHS shell.nix --- pythonnet.sln | 1 + shell.nix | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 shell.nix diff --git a/pythonnet.sln b/pythonnet.sln index 5bf4a2dbf..cf684c0e1 100644 --- a/pythonnet.sln +++ b/pythonnet.sln @@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F LICENSE = LICENSE README.rst = README.rst version.txt = version.txt + shell.nix = shell.nix EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{D301657F-5EAF-4534-B280-B858D651B2E5}" diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..fe653deb7 --- /dev/null +++ b/shell.nix @@ -0,0 +1,10 @@ +{ pkgs ? import {}}: +let + fhs = pkgs.buildFHSUserEnv { + name = "my-fhs-environment"; + + targetPkgs = _: [ + pkgs.python3 + ]; + }; +in fhs.env From ac605fd3e3ccad28c105ef2d57b955a23f51c2a9 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 18 Dec 2024 21:13:13 +0000 Subject: [PATCH 094/194] index setter was leaking memory --- src/runtime/Types/ClassBase.cs | 10 ++-------- src/runtime/Types/Indexer.cs | 4 ++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 7296a1900..8b2a98903 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -497,14 +497,8 @@ static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, Bo // Add value to argument list Runtime.PyTuple_SetItem(real.Borrow(), i, v); - cls.indexer.SetItem(ob, real.Borrow()); - - if (Exceptions.ErrorOccurred()) - { - return -1; - } - - return 0; + using var result = cls.indexer.SetItem(ob, real.Borrow()); + return result.IsNull() ? -1 : 0; } static NewReference tp_call_impl(BorrowedReference ob, BorrowedReference args, BorrowedReference kw) diff --git a/src/runtime/Types/Indexer.cs b/src/runtime/Types/Indexer.cs index 4903b6f76..fe6dab4c9 100644 --- a/src/runtime/Types/Indexer.cs +++ b/src/runtime/Types/Indexer.cs @@ -50,9 +50,9 @@ internal NewReference GetItem(BorrowedReference inst, BorrowedReference args) } - internal void SetItem(BorrowedReference inst, BorrowedReference args) + internal NewReference SetItem(BorrowedReference inst, BorrowedReference args) { - SetterBinder.Invoke(inst, args, null); + return SetterBinder.Invoke(inst, args, null); } internal bool NeedsDefaultArgs(BorrowedReference args) From 77bdf6d2bc5b7b6697d51f1b4ed765b01c322e59 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Wed, 18 Dec 2024 21:14:40 +0000 Subject: [PATCH 095/194] implemented __delitem__ for IDictionary and IList fixed crash for all other types (now properly throws TypeError) fixes https://github.com/pythonnet/pythonnet/issues/2530 --- CHANGELOG.md | 4 +++ src/runtime/ClassManager.cs | 17 ++++++++++++ src/runtime/MethodBinder.cs | 5 ++++ src/runtime/Types/ArrayObject.cs | 6 ++++ src/runtime/Types/ClassBase.cs | 44 ++++++++++++++++++++++++++++++ src/runtime/Util/ReflectionUtil.cs | 7 +++++ tests/test_indexer.py | 36 ++++++++++++++++++++++++ 7 files changed, 119 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1863a0806..df68fbb39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,13 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ## Unreleased ### Added + +- Support `del obj[...]` for types derived from `IList` and `IDictionary` + ### Changed ### Fixed +- Fixed crash when trying to `del clrObj[...]` for non-arrays - ci: properly exclude job (#2542) ## [3.0.5](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.5) - 2024-12-13 diff --git a/src/runtime/ClassManager.cs b/src/runtime/ClassManager.cs index d743bc006..b884bfa92 100644 --- a/src/runtime/ClassManager.cs +++ b/src/runtime/ClassManager.cs @@ -213,6 +213,7 @@ internal static void InitClassBase(Type type, ClassBase impl, ReflectedClrType p ClassInfo info = GetClassInfo(type, impl); impl.indexer = info.indexer; + impl.del = info.del; impl.richcompare.Clear(); @@ -538,6 +539,21 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl) ob = new MethodObject(type, name, mlist); ci.members[name] = ob.AllocObject(); + if (name == nameof(IDictionary.Remove) + && mlist.Any(m => m.DeclaringType?.GetInterfaces() + .Any(i => i.TryGetGenericDefinition() == typeof(IDictionary<,>)) is true)) + { + ci.del = new(); + ci.del.AddRange(mlist.Where(m => !m.IsStatic)); + } + else if (name == nameof(IList.RemoveAt) + && mlist.Any(m => m.DeclaringType?.GetInterfaces() + .Any(i => i.TryGetGenericDefinition() == typeof(IList<>)) is true)) + { + ci.del = new(); + ci.del.AddRange(mlist.Where(m => !m.IsStatic)); + } + if (mlist.Any(OperatorMethod.IsOperatorMethod)) { string pyName = OperatorMethod.GetPyMethodName(name); @@ -581,6 +597,7 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl) private class ClassInfo { public Indexer? indexer; + public MethodBinder? del; public readonly Dictionary members = new(); internal ClassInfo() diff --git a/src/runtime/MethodBinder.cs b/src/runtime/MethodBinder.cs index 9a5515c8e..af75a34b4 100644 --- a/src/runtime/MethodBinder.cs +++ b/src/runtime/MethodBinder.cs @@ -54,6 +54,11 @@ internal void AddMethod(MethodBase m) list.Add(m); } + internal void AddRange(IEnumerable methods) + { + list.AddRange(methods.Select(m => new MaybeMethodBase(m))); + } + /// /// Given a sequence of MethodInfo and a sequence of types, return the /// MethodInfo that matches the signature represented by those types. diff --git a/src/runtime/Types/ArrayObject.cs b/src/runtime/Types/ArrayObject.cs index b95934baf..f220d53fb 100644 --- a/src/runtime/Types/ArrayObject.cs +++ b/src/runtime/Types/ArrayObject.cs @@ -247,6 +247,12 @@ public static NewReference mp_subscript(BorrowedReference ob, BorrowedReference /// public static int mp_ass_subscript(BorrowedReference ob, BorrowedReference idx, BorrowedReference v) { + if (v.IsNull) + { + Exceptions.RaiseTypeError("'System.Array' object does not support item deletion"); + return -1; + } + var obj = (CLRObject)GetManagedObject(ob)!; var items = (Array)obj.inst; Type itemType = obj.inst.GetType().GetElementType(); diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 8b2a98903..2d6ce8a47 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -25,6 +25,7 @@ internal class ClassBase : ManagedType, IDeserializationCallback [NonSerialized] internal List dotNetMembers = new(); internal Indexer? indexer; + internal MethodBinder? del; internal readonly Dictionary richcompare = new(); internal MaybeType type; @@ -465,6 +466,11 @@ static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, Bo // with the index arg (method binders expect arg tuples). NewReference argsTuple = default; + if (v.IsNull) + { + return DelImpl(ob, idx, cls); + } + if (!Runtime.PyTuple_Check(idx)) { argsTuple = Runtime.PyTuple_New(1); @@ -501,6 +507,44 @@ static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, Bo return result.IsNull() ? -1 : 0; } + /// Implements __delitem__ (del x[...]) for IList<T> and IDictionary<TKey, TValue>. + private static int DelImpl(BorrowedReference ob, BorrowedReference idx, ClassBase cls) + { + if (cls.del is null) + { + Exceptions.SetError(Exceptions.TypeError, "object does not support item deletion"); + return -1; + } + + if (Runtime.PyTuple_Check(idx)) + { + Exceptions.SetError(Exceptions.TypeError, "multi-index deletion not supported"); + return -1; + } + + using var argsTuple = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(argsTuple.Borrow(), 0, idx); + using var result = cls.del.Invoke(ob, argsTuple.Borrow(), kw: null); + if (result.IsNull()) + return -1; + + if (Runtime.PyBool_CheckExact(result.Borrow())) + { + if (Runtime.PyObject_IsTrue(result.Borrow()) != 0) + return 0; + + Exceptions.SetError(Exceptions.KeyError, "key not found"); + return -1; + } + + if (!result.IsNone()) + { + Exceptions.warn("unsupported return type for __delitem__", Exceptions.TypeError); + } + + return 0; + } + static NewReference tp_call_impl(BorrowedReference ob, BorrowedReference args, BorrowedReference kw) { BorrowedReference tp = Runtime.PyObject_TYPE(ob); diff --git a/src/runtime/Util/ReflectionUtil.cs b/src/runtime/Util/ReflectionUtil.cs index 58d0a506e..0fad2d4b2 100644 --- a/src/runtime/Util/ReflectionUtil.cs +++ b/src/runtime/Util/ReflectionUtil.cs @@ -53,4 +53,11 @@ public static BindingFlags GetBindingFlags(this PropertyInfo property) flags |= accessor.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic; return flags; } + + public static Type? TryGetGenericDefinition(this Type type) + { + if (type is null) throw new ArgumentNullException(nameof(type)); + + return type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : null; + } } diff --git a/tests/test_indexer.py b/tests/test_indexer.py index 8cf3150ba..108573f0d 100644 --- a/tests/test_indexer.py +++ b/tests/test_indexer.py @@ -668,3 +668,39 @@ def test_public_inherited_overloaded_indexer(): with pytest.raises(TypeError): ob[[]] + +def test_del_indexer_dict(): + """Test deleting indexers (__delitem__).""" + from System.Collections.Generic import Dictionary, KeyNotFoundException + d = Dictionary[str, str]() + d["delme"] = "1" + with pytest.raises(KeyError): + del d["nonexistent"] + del d["delme"] + with pytest.raises(KeyError): + del d["delme"] + +def test_del_indexer_list(): + """Test deleting indexers (__delitem__).""" + from System import ArgumentOutOfRangeException + from System.Collections.Generic import List + l = List[str]() + l.Add("1") + with pytest.raises(ArgumentOutOfRangeException): + del l[3] + del l[0] + assert len(l) == 0 + +def test_del_indexer_array(): + """Test deleting indexers (__delitem__).""" + from System import Array + l = Array[str](0) + with pytest.raises(TypeError): + del l[0] + +def test_del_indexer_absent(): + """Test deleting indexers (__delitem__).""" + from System import Uri + l = Uri("http://www.example.com") + with pytest.raises(TypeError): + del l[0] From a21c7972809500edd6364003c37695193ac1a5d4 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 14 Apr 2025 20:12:49 +0200 Subject: [PATCH 096/194] Bump action versions in doc workflow (#2584) --- .github/workflows/docs.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5b782c8b4..30163cd14 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,9 +6,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Doxygen Action - uses: mattnotmitt/doxygen-action@1.9.4 + uses: mattnotmitt/doxygen-action@1.12.0 with: working-directory: "doc/" @@ -19,7 +19,7 @@ jobs: - name: Upload artifact # Automatically uploads an artifact from the './_site' directory by default - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: path: doc/build/html/ @@ -37,4 +37,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 From 06dbd41d8a3ca86999d84a2239a108794d9c1c65 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 19 Oct 2025 11:46:03 +0100 Subject: [PATCH 097/194] Drop EOLd Python versions (#2632) --- .github/workflows/main.yml | 4 +- pyproject.toml | 2 +- src/runtime/Native/TypeOffset37.cs | 136 ---------------------------- src/runtime/Native/TypeOffset38.cs | 138 ----------------------------- src/runtime/Native/TypeOffset39.cs | 138 ----------------------------- 5 files changed, 3 insertions(+), 415 deletions(-) delete mode 100644 src/runtime/Native/TypeOffset37.cs delete mode 100644 src/runtime/Native/TypeOffset38.cs delete mode 100644 src/runtime/Native/TypeOffset39.cs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8485189e1..5676b44a5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,12 +32,12 @@ jobs: platform: x64 instance: macos-13 - python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python: ["3.10", "3.11", "3.12", "3.13"] # 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: + - os: category: windows platform: x86 python: "3.13" diff --git a/pyproject.toml b/pyproject.toml index 968998e8d..87018826b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "clr_loader>=0.2.7,<0.3.0" ] -requires-python = ">=3.7, <3.14" +requires-python = ">=3.10, <3.14" classifiers = [ "Development Status :: 5 - Production/Stable", diff --git a/src/runtime/Native/TypeOffset37.cs b/src/runtime/Native/TypeOffset37.cs deleted file mode 100644 index 951cb1068..000000000 --- a/src/runtime/Native/TypeOffset37.cs +++ /dev/null @@ -1,136 +0,0 @@ - -// Auto-generated by geninterop.py. -// DO NOT MODIFY BY HAND. - -// Python 3.7: ABI flags: '' - -// ReSharper disable InconsistentNaming -// ReSharper disable IdentifierTypo - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -using Python.Runtime.Native; - -namespace Python.Runtime -{ - [SuppressMessage("Style", "IDE1006:Naming Styles", - Justification = "Following CPython", - Scope = "type")] - - [StructLayout(LayoutKind.Sequential)] - internal class TypeOffset37 : GeneratedTypeOffsets, ITypeOffsets - { - public TypeOffset37() { } - // Auto-generated from PyHeapTypeObject in Python.h - public int ob_refcnt { get; private set; } - public int ob_type { get; private set; } - public int ob_size { get; private set; } - public int tp_name { get; private set; } - public int tp_basicsize { get; private set; } - public int tp_itemsize { get; private set; } - public int tp_dealloc { get; private set; } - public int tp_print { get; private set; } - public int tp_getattr { get; private set; } - public int tp_setattr { get; private set; } - public int tp_as_async { get; private set; } - public int tp_repr { get; private set; } - public int tp_as_number { get; private set; } - public int tp_as_sequence { get; private set; } - public int tp_as_mapping { get; private set; } - public int tp_hash { get; private set; } - public int tp_call { get; private set; } - public int tp_str { get; private set; } - public int tp_getattro { get; private set; } - public int tp_setattro { get; private set; } - public int tp_as_buffer { get; private set; } - public int tp_flags { get; private set; } - public int tp_doc { get; private set; } - public int tp_traverse { get; private set; } - public int tp_clear { get; private set; } - public int tp_richcompare { get; private set; } - public int tp_weaklistoffset { get; private set; } - public int tp_iter { get; private set; } - public int tp_iternext { get; private set; } - public int tp_methods { get; private set; } - public int tp_members { get; private set; } - public int tp_getset { get; private set; } - public int tp_base { get; private set; } - public int tp_dict { get; private set; } - public int tp_descr_get { get; private set; } - public int tp_descr_set { get; private set; } - public int tp_dictoffset { get; private set; } - public int tp_init { get; private set; } - public int tp_alloc { get; private set; } - public int tp_new { get; private set; } - public int tp_free { get; private set; } - public int tp_is_gc { get; private set; } - public int tp_bases { get; private set; } - public int tp_mro { get; private set; } - public int tp_cache { get; private set; } - public int tp_subclasses { get; private set; } - public int tp_weaklist { get; private set; } - public int tp_del { get; private set; } - public int tp_version_tag { get; private set; } - public int tp_finalize { get; private set; } - public int am_await { get; private set; } - public int am_aiter { get; private set; } - public int am_anext { get; private set; } - public int nb_add { get; private set; } - public int nb_subtract { get; private set; } - public int nb_multiply { get; private set; } - public int nb_remainder { get; private set; } - public int nb_divmod { get; private set; } - public int nb_power { get; private set; } - public int nb_negative { get; private set; } - public int nb_positive { get; private set; } - public int nb_absolute { get; private set; } - public int nb_bool { get; private set; } - public int nb_invert { get; private set; } - public int nb_lshift { get; private set; } - public int nb_rshift { get; private set; } - public int nb_and { get; private set; } - public int nb_xor { get; private set; } - public int nb_or { get; private set; } - public int nb_int { get; private set; } - public int nb_reserved { get; private set; } - public int nb_float { get; private set; } - public int nb_inplace_add { get; private set; } - public int nb_inplace_subtract { get; private set; } - public int nb_inplace_multiply { get; private set; } - public int nb_inplace_remainder { get; private set; } - public int nb_inplace_power { get; private set; } - public int nb_inplace_lshift { get; private set; } - public int nb_inplace_rshift { get; private set; } - public int nb_inplace_and { get; private set; } - public int nb_inplace_xor { get; private set; } - public int nb_inplace_or { get; private set; } - public int nb_floor_divide { get; private set; } - public int nb_true_divide { get; private set; } - public int nb_inplace_floor_divide { get; private set; } - public int nb_inplace_true_divide { get; private set; } - public int nb_index { get; private set; } - public int nb_matrix_multiply { get; private set; } - public int nb_inplace_matrix_multiply { get; private set; } - public int mp_length { get; private set; } - public int mp_subscript { get; private set; } - public int mp_ass_subscript { get; private set; } - public int sq_length { get; private set; } - public int sq_concat { get; private set; } - public int sq_repeat { get; private set; } - public int sq_item { get; private set; } - public int was_sq_slice { get; private set; } - public int sq_ass_item { get; private set; } - public int was_sq_ass_slice { get; private set; } - public int sq_contains { get; private set; } - public int sq_inplace_concat { get; private set; } - public int sq_inplace_repeat { get; private set; } - public int bf_getbuffer { get; private set; } - public int bf_releasebuffer { get; private set; } - public int name { get; private set; } - public int ht_slots { get; private set; } - public int qualname { get; private set; } - public int ht_cached_keys { get; private set; } - } -} diff --git a/src/runtime/Native/TypeOffset38.cs b/src/runtime/Native/TypeOffset38.cs deleted file mode 100644 index 67a40eabd..000000000 --- a/src/runtime/Native/TypeOffset38.cs +++ /dev/null @@ -1,138 +0,0 @@ - -// Auto-generated by geninterop.py. -// DO NOT MODIFY BY HAND. - -// Python 3.8: ABI flags: '' - -// ReSharper disable InconsistentNaming -// ReSharper disable IdentifierTypo - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -using Python.Runtime.Native; - -namespace Python.Runtime -{ - [SuppressMessage("Style", "IDE1006:Naming Styles", - Justification = "Following CPython", - Scope = "type")] - - [StructLayout(LayoutKind.Sequential)] - internal class TypeOffset38 : GeneratedTypeOffsets, ITypeOffsets - { - public TypeOffset38() { } - // Auto-generated from PyHeapTypeObject in Python.h - public int ob_refcnt { get; private set; } - public int ob_type { get; private set; } - public int ob_size { get; private set; } - public int tp_name { get; private set; } - public int tp_basicsize { get; private set; } - public int tp_itemsize { get; private set; } - public int tp_dealloc { get; private set; } - public int tp_vectorcall_offset { get; private set; } - public int tp_getattr { get; private set; } - public int tp_setattr { get; private set; } - public int tp_as_async { get; private set; } - public int tp_repr { get; private set; } - public int tp_as_number { get; private set; } - public int tp_as_sequence { get; private set; } - public int tp_as_mapping { get; private set; } - public int tp_hash { get; private set; } - public int tp_call { get; private set; } - public int tp_str { get; private set; } - public int tp_getattro { get; private set; } - public int tp_setattro { get; private set; } - public int tp_as_buffer { get; private set; } - public int tp_flags { get; private set; } - public int tp_doc { get; private set; } - public int tp_traverse { get; private set; } - public int tp_clear { get; private set; } - public int tp_richcompare { get; private set; } - public int tp_weaklistoffset { get; private set; } - public int tp_iter { get; private set; } - public int tp_iternext { get; private set; } - public int tp_methods { get; private set; } - public int tp_members { get; private set; } - public int tp_getset { get; private set; } - public int tp_base { get; private set; } - public int tp_dict { get; private set; } - public int tp_descr_get { get; private set; } - public int tp_descr_set { get; private set; } - public int tp_dictoffset { get; private set; } - public int tp_init { get; private set; } - public int tp_alloc { get; private set; } - public int tp_new { get; private set; } - public int tp_free { get; private set; } - public int tp_is_gc { get; private set; } - public int tp_bases { get; private set; } - public int tp_mro { get; private set; } - public int tp_cache { get; private set; } - public int tp_subclasses { get; private set; } - public int tp_weaklist { get; private set; } - public int tp_del { get; private set; } - public int tp_version_tag { get; private set; } - public int tp_finalize { get; private set; } - public int tp_vectorcall { get; private set; } - public int tp_print { get; private set; } - public int am_await { get; private set; } - public int am_aiter { get; private set; } - public int am_anext { get; private set; } - public int nb_add { get; private set; } - public int nb_subtract { get; private set; } - public int nb_multiply { get; private set; } - public int nb_remainder { get; private set; } - public int nb_divmod { get; private set; } - public int nb_power { get; private set; } - public int nb_negative { get; private set; } - public int nb_positive { get; private set; } - public int nb_absolute { get; private set; } - public int nb_bool { get; private set; } - public int nb_invert { get; private set; } - public int nb_lshift { get; private set; } - public int nb_rshift { get; private set; } - public int nb_and { get; private set; } - public int nb_xor { get; private set; } - public int nb_or { get; private set; } - public int nb_int { get; private set; } - public int nb_reserved { get; private set; } - public int nb_float { get; private set; } - public int nb_inplace_add { get; private set; } - public int nb_inplace_subtract { get; private set; } - public int nb_inplace_multiply { get; private set; } - public int nb_inplace_remainder { get; private set; } - public int nb_inplace_power { get; private set; } - public int nb_inplace_lshift { get; private set; } - public int nb_inplace_rshift { get; private set; } - public int nb_inplace_and { get; private set; } - public int nb_inplace_xor { get; private set; } - public int nb_inplace_or { get; private set; } - public int nb_floor_divide { get; private set; } - public int nb_true_divide { get; private set; } - public int nb_inplace_floor_divide { get; private set; } - public int nb_inplace_true_divide { get; private set; } - public int nb_index { get; private set; } - public int nb_matrix_multiply { get; private set; } - public int nb_inplace_matrix_multiply { get; private set; } - public int mp_length { get; private set; } - public int mp_subscript { get; private set; } - public int mp_ass_subscript { get; private set; } - public int sq_length { get; private set; } - public int sq_concat { get; private set; } - public int sq_repeat { get; private set; } - public int sq_item { get; private set; } - public int was_sq_slice { get; private set; } - public int sq_ass_item { get; private set; } - public int was_sq_ass_slice { get; private set; } - public int sq_contains { get; private set; } - public int sq_inplace_concat { get; private set; } - public int sq_inplace_repeat { get; private set; } - public int bf_getbuffer { get; private set; } - public int bf_releasebuffer { get; private set; } - public int name { get; private set; } - public int ht_slots { get; private set; } - public int qualname { get; private set; } - public int ht_cached_keys { get; private set; } - } -} diff --git a/src/runtime/Native/TypeOffset39.cs b/src/runtime/Native/TypeOffset39.cs deleted file mode 100644 index cf3acc984..000000000 --- a/src/runtime/Native/TypeOffset39.cs +++ /dev/null @@ -1,138 +0,0 @@ - -// Auto-generated by geninterop.py. -// DO NOT MODIFY BY HAND. - -// Python 3.9: ABI flags: '' - -// ReSharper disable InconsistentNaming -// ReSharper disable IdentifierTypo - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -using Python.Runtime.Native; - -namespace Python.Runtime -{ - [SuppressMessage("Style", "IDE1006:Naming Styles", - Justification = "Following CPython", - Scope = "type")] - - [StructLayout(LayoutKind.Sequential)] - internal class TypeOffset39 : GeneratedTypeOffsets, ITypeOffsets - { - public TypeOffset39() { } - // Auto-generated from PyHeapTypeObject in Python.h - public int ob_refcnt { get; private set; } - public int ob_type { get; private set; } - public int ob_size { get; private set; } - public int tp_name { get; private set; } - public int tp_basicsize { get; private set; } - public int tp_itemsize { get; private set; } - public int tp_dealloc { get; private set; } - public int tp_vectorcall_offset { get; private set; } - public int tp_getattr { get; private set; } - public int tp_setattr { get; private set; } - public int tp_as_async { get; private set; } - public int tp_repr { get; private set; } - public int tp_as_number { get; private set; } - public int tp_as_sequence { get; private set; } - public int tp_as_mapping { get; private set; } - public int tp_hash { get; private set; } - public int tp_call { get; private set; } - public int tp_str { get; private set; } - public int tp_getattro { get; private set; } - public int tp_setattro { get; private set; } - public int tp_as_buffer { get; private set; } - public int tp_flags { get; private set; } - public int tp_doc { get; private set; } - public int tp_traverse { get; private set; } - public int tp_clear { get; private set; } - public int tp_richcompare { get; private set; } - public int tp_weaklistoffset { get; private set; } - public int tp_iter { get; private set; } - public int tp_iternext { get; private set; } - public int tp_methods { get; private set; } - public int tp_members { get; private set; } - public int tp_getset { get; private set; } - public int tp_base { get; private set; } - public int tp_dict { get; private set; } - public int tp_descr_get { get; private set; } - public int tp_descr_set { get; private set; } - public int tp_dictoffset { get; private set; } - public int tp_init { get; private set; } - public int tp_alloc { get; private set; } - public int tp_new { get; private set; } - public int tp_free { get; private set; } - public int tp_is_gc { get; private set; } - public int tp_bases { get; private set; } - public int tp_mro { get; private set; } - public int tp_cache { get; private set; } - public int tp_subclasses { get; private set; } - public int tp_weaklist { get; private set; } - public int tp_del { get; private set; } - public int tp_version_tag { get; private set; } - public int tp_finalize { get; private set; } - public int tp_vectorcall { get; private set; } - public int am_await { get; private set; } - public int am_aiter { get; private set; } - public int am_anext { get; private set; } - public int nb_add { get; private set; } - public int nb_subtract { get; private set; } - public int nb_multiply { get; private set; } - public int nb_remainder { get; private set; } - public int nb_divmod { get; private set; } - public int nb_power { get; private set; } - public int nb_negative { get; private set; } - public int nb_positive { get; private set; } - public int nb_absolute { get; private set; } - public int nb_bool { get; private set; } - public int nb_invert { get; private set; } - public int nb_lshift { get; private set; } - public int nb_rshift { get; private set; } - public int nb_and { get; private set; } - public int nb_xor { get; private set; } - public int nb_or { get; private set; } - public int nb_int { get; private set; } - public int nb_reserved { get; private set; } - public int nb_float { get; private set; } - public int nb_inplace_add { get; private set; } - public int nb_inplace_subtract { get; private set; } - public int nb_inplace_multiply { get; private set; } - public int nb_inplace_remainder { get; private set; } - public int nb_inplace_power { get; private set; } - public int nb_inplace_lshift { get; private set; } - public int nb_inplace_rshift { get; private set; } - public int nb_inplace_and { get; private set; } - public int nb_inplace_xor { get; private set; } - public int nb_inplace_or { get; private set; } - public int nb_floor_divide { get; private set; } - public int nb_true_divide { get; private set; } - public int nb_inplace_floor_divide { get; private set; } - public int nb_inplace_true_divide { get; private set; } - public int nb_index { get; private set; } - public int nb_matrix_multiply { get; private set; } - public int nb_inplace_matrix_multiply { get; private set; } - public int mp_length { get; private set; } - public int mp_subscript { get; private set; } - public int mp_ass_subscript { get; private set; } - public int sq_length { get; private set; } - public int sq_concat { get; private set; } - public int sq_repeat { get; private set; } - public int sq_item { get; private set; } - public int was_sq_slice { get; private set; } - public int sq_ass_item { get; private set; } - public int was_sq_ass_slice { get; private set; } - public int sq_contains { get; private set; } - public int sq_inplace_concat { get; private set; } - public int sq_inplace_repeat { get; private set; } - public int bf_getbuffer { get; private set; } - public int bf_releasebuffer { get; private set; } - public int name { get; private set; } - public int ht_slots { get; private set; } - public int qualname { get; private set; } - public int ht_cached_keys { get; private set; } - public int ht_module { get; private set; } - } -} From 4cbc6a914cdb4460b8658f45818474654c0bcfa3 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 19 Oct 2025 12:13:39 +0100 Subject: [PATCH 098/194] Bump setuptools and adjust license information (#2633) The `project.license` entry in `pyproject.toml` should be a bare string now and the license tag is removed. --- pyproject.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 87018826b..20947be0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [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" @@ -18,7 +18,6 @@ requires-python = ">=3.10, <3.14" 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", @@ -55,7 +54,6 @@ Sources = "https://github.com/pythonnet/pythonnet" [tool.setuptools] zip-safe = false py-modules = ["clr"] -license-files = [] [tool.setuptools.dynamic.version] file = "version.txt" From c6a3ed58afc9b5011182e6b0e252dc58b82fba49 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 19 Oct 2025 12:27:42 +0100 Subject: [PATCH 099/194] Minimal .NET 8 usage changes (#2634) - Disables NonCopyableAnalyzer for now as it breaks the build --- .github/workflows/ARM.yml | 6 +++--- .github/workflows/main.yml | 2 +- Directory.Build.props | 8 ++++---- src/embed_tests/Python.EmbeddingTest.csproj | 2 +- src/python_tests_runner/Python.PythonTestsRunner.csproj | 2 +- src/testing/Python.Test.csproj | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ARM.yml b/.github/workflows/ARM.yml index eef0e666d..c2b89edfe 100644 --- a/.github/workflows/ARM.yml +++ b/.github/workflows/ARM.yml @@ -14,12 +14,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v5 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '6.0.x' + dotnet-version: '8.0.x' - name: Clean previous install run: | diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5676b44a5..4e37eabcd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,7 +55,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: '6.0.x' + dotnet-version: '8.0.x' - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v2 diff --git a/Directory.Build.props b/Directory.Build.props index e45c16f6a..fcbae2c8f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 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,13 +12,13 @@ - + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 4993994d3..2b3d6610e 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -1,7 +1,7 @@ - net472;net6.0 + net472;net8.0 ..\pythonnet.snk true diff --git a/src/python_tests_runner/Python.PythonTestsRunner.csproj b/src/python_tests_runner/Python.PythonTestsRunner.csproj index 63981c424..964166907 100644 --- a/src/python_tests_runner/Python.PythonTestsRunner.csproj +++ b/src/python_tests_runner/Python.PythonTestsRunner.csproj @@ -1,7 +1,7 @@ - net472;net6.0 + net472;net8.0 diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index 3adc5c0c6..b7ba6cd4e 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -1,6 +1,6 @@ - netstandard2.0;net6.0 + netstandard2.0;net8.0 true true ..\pythonnet.snk From 117c66a0ea7a37e129fd4f4260729710e29ba174 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 21 Oct 2025 18:44:28 +0200 Subject: [PATCH 100/194] Drop performance tests (#2636) --- .github/workflows/main.yml | 6 -- pythonnet.sln | 2 - .../BaselineComparisonBenchmarkBase.cs | 74 ------------------- src/perf_tests/BaselineComparisonConfig.cs | 53 ------------- src/perf_tests/BenchmarkTests.cs | 74 ------------------- src/perf_tests/Python.PerformanceTests.csproj | 48 ------------ src/perf_tests/PythonCallingNetBenchmark.cs | 58 --------------- src/perf_tests/baseline/.gitkeep | 0 8 files changed, 315 deletions(-) delete mode 100644 src/perf_tests/BaselineComparisonBenchmarkBase.cs delete mode 100644 src/perf_tests/BaselineComparisonConfig.cs delete mode 100644 src/perf_tests/BenchmarkTests.cs delete mode 100644 src/perf_tests/Python.PerformanceTests.csproj delete mode 100644 src/perf_tests/PythonCallingNetBenchmark.cs delete mode 100644 src/perf_tests/baseline/.gitkeep diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4e37eabcd..ea6b2c99a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -107,10 +107,4 @@ jobs: 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/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/src/perf_tests/BaselineComparisonBenchmarkBase.cs b/src/perf_tests/BaselineComparisonBenchmarkBase.cs deleted file mode 100644 index 06adcbc67..000000000 --- a/src/perf_tests/BaselineComparisonBenchmarkBase.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; - -using Python.Runtime; - -namespace Python.PerformanceTests -{ - public class BaselineComparisonBenchmarkBase - { - public BaselineComparisonBenchmarkBase() - { - Console.WriteLine($"CWD: {Environment.CurrentDirectory}"); - Console.WriteLine($"Using Python.Runtime from {typeof(PythonEngine).Assembly.Location} {typeof(PythonEngine).Assembly.GetName()}"); - - try { - PythonEngine.Initialize(); - Console.WriteLine("Python Initialized"); - Trace.Assert(PythonEngine.BeginAllowThreads() != IntPtr.Zero); - Console.WriteLine("Threading enabled"); - } - catch (Exception e) { - Console.WriteLine(e); - throw; - } - } - - static BaselineComparisonBenchmarkBase() - { - SetupRuntimeResolve(); - } - - public static void SetupRuntimeResolve() - { - string pythonRuntimeDll = Environment.GetEnvironmentVariable(BaselineComparisonConfig.EnvironmentVariableName); - if (string.IsNullOrEmpty(pythonRuntimeDll)) - { - throw new ArgumentException( - "Required environment variable is missing", - BaselineComparisonConfig.EnvironmentVariableName); - } - - Console.WriteLine("Preloading " + pythonRuntimeDll); - Assembly.LoadFrom(pythonRuntimeDll); - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { - if (assembly.FullName.StartsWith("Python.Runtime")) - Console.WriteLine(assembly.Location); - foreach(var dependency in assembly.GetReferencedAssemblies()) - if (dependency.FullName.Contains("Python.Runtime")) { - Console.WriteLine($"{assembly} -> {dependency}"); - } - } - - AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; - } - - static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) { - if (!args.Name.StartsWith("Python.Runtime")) - return null; - - var preloaded = AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(a => a.GetName().Name == "Python.Runtime"); - if (preloaded != null) return preloaded; - - string pythonRuntimeDll = Environment.GetEnvironmentVariable(BaselineComparisonConfig.EnvironmentVariableName); - if (string.IsNullOrEmpty(pythonRuntimeDll)) - return null; - - return Assembly.LoadFrom(pythonRuntimeDll); - } - } -} diff --git a/src/perf_tests/BaselineComparisonConfig.cs b/src/perf_tests/BaselineComparisonConfig.cs deleted file mode 100644 index 3f6766554..000000000 --- a/src/perf_tests/BaselineComparisonConfig.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; - -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Jobs; - -using Perfolizer.Horology; - -namespace Python.PerformanceTests -{ - public class BaselineComparisonConfig : ManualConfig - { - public const string EnvironmentVariableName = "PythonRuntimeDLL"; - - public BaselineComparisonConfig() - { - this.Options |= ConfigOptions.DisableOptimizationsValidator; - - string deploymentRoot = BenchmarkTests.DeploymentRoot; - - var baseJob = Job.Default - .WithLaunchCount(1) - .WithWarmupCount(3) - .WithMaxIterationCount(100) - .WithIterationTime(TimeInterval.FromMilliseconds(100)); - this.Add(baseJob - .WithId("baseline") - .WithEnvironmentVariable(EnvironmentVariableName, - Path.Combine(deploymentRoot, "baseline", "Python.Runtime.dll")) - .WithBaseline(true)); - this.Add(baseJob - .WithId("new") - .WithEnvironmentVariable(EnvironmentVariableName, - Path.Combine(deploymentRoot, "new", "Python.Runtime.dll"))); - } - - static BaselineComparisonConfig() { - AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; - } - - static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) { - Console.WriteLine(args.Name); - if (!args.Name.StartsWith("Python.Runtime")) - return null; - string pythonRuntimeDll = Environment.GetEnvironmentVariable(EnvironmentVariableName); - if (string.IsNullOrEmpty(pythonRuntimeDll)) - pythonRuntimeDll = Path.Combine(BenchmarkTests.DeploymentRoot, "baseline", "Python.Runtime.dll"); - return Assembly.LoadFrom(pythonRuntimeDll); - } - } -} diff --git a/src/perf_tests/BenchmarkTests.cs b/src/perf_tests/BenchmarkTests.cs deleted file mode 100644 index 9e033d11f..000000000 --- a/src/perf_tests/BenchmarkTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Reflection; - -using BenchmarkDotNet.Reports; -using BenchmarkDotNet.Running; -using NUnit.Framework; - -namespace Python.PerformanceTests -{ - public class BenchmarkTests - { - Summary summary; - - [OneTimeSetUp] - public void SetUp() - { - Environment.CurrentDirectory = Path.Combine(DeploymentRoot, "new"); - this.summary = BenchmarkRunner.Run(); - Assert.IsNotEmpty(this.summary.Reports); - Assert.IsTrue( - condition: this.summary.Reports.All(r => r.Success), - message: "BenchmarkDotNet failed to execute or collect results of performance tests. See logs above."); - } - - [Test] - public void ReadInt64Property() - { - double optimisticPerfRatio = GetOptimisticPerfRatio(this.summary.Reports); - AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 1.35); - } - - [Test] - public void WriteInt64Property() - { - double optimisticPerfRatio = GetOptimisticPerfRatio(this.summary.Reports); - AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 1.25); - } - - static double GetOptimisticPerfRatio( - IReadOnlyList reports, - [CallerMemberName] string methodName = null) - { - reports = reports.Where(r => r.BenchmarkCase.Descriptor.WorkloadMethod.Name == methodName).ToArray(); - if (reports.Count == 0) - throw new ArgumentException( - message: $"No reports found for {methodName}. " - + "You have to match test method name to benchmark method name or " - + "pass benchmark method name explicitly", - paramName: nameof(methodName)); - - var baseline = reports.Single(r => r.BenchmarkCase.Job.ResolvedId == "baseline").ResultStatistics; - var @new = reports.Single(r => r.BenchmarkCase.Job.ResolvedId != "baseline").ResultStatistics; - - double newTimeOptimistic = @new.Mean - (@new.StandardDeviation + baseline.StandardDeviation) * 0.5; - - return newTimeOptimistic / baseline.Mean; - } - - public static string DeploymentRoot => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - - public static void AssertPerformanceIsBetterOrSame( - double actual, double target, - double wiggleRoom = 1.1, [CallerMemberName] string testName = null) { - double threshold = target * wiggleRoom; - Assert.LessOrEqual(actual, threshold, - $"{testName}: {actual:F3} > {threshold:F3} (target: {target:F3})" - + ": perf result is higher than the failure threshold."); - } - } -} diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj deleted file mode 100644 index bde07ecab..000000000 --- a/src/perf_tests/Python.PerformanceTests.csproj +++ /dev/null @@ -1,48 +0,0 @@ - - - - net472 - false - x64 - x64 - - - - - - PreserveNewest - - - - - - false - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - diff --git a/src/perf_tests/PythonCallingNetBenchmark.cs b/src/perf_tests/PythonCallingNetBenchmark.cs deleted file mode 100644 index d7edd4583..000000000 --- a/src/perf_tests/PythonCallingNetBenchmark.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text; - -using BenchmarkDotNet.Attributes; -using Python.Runtime; - -namespace Python.PerformanceTests -{ - [Config(typeof(BaselineComparisonConfig))] - public class PythonCallingNetBenchmark: BaselineComparisonBenchmarkBase - { - [Benchmark] - public void ReadInt64Property() - { - using (Py.GIL()) - { - var locals = new PyDict(); - locals.SetItem("a", new NetObject().ToPython()); - Exec($@" -s = 0 -for i in range(50000): - s += a.{nameof(NetObject.LongProperty)} -", locals: locals); - } - } - - [Benchmark] - public void WriteInt64Property() { - using (Py.GIL()) { - var locals = new PyDict(); - locals.SetItem("a", new NetObject().ToPython()); - Exec($@" -s = 0 -for i in range(50000): - a.{nameof(NetObject.LongProperty)} += i -", locals: locals); - } - } - - static void Exec(string code, PyDict locals) - { - MethodInfo exec = typeof(PythonEngine).GetMethod(nameof(PythonEngine.Exec)); - object localsArg = typeof(PyObject).Assembly.GetName().Version.Major >= 3 - ? locals : locals.Handle; - exec.Invoke(null, new[] - { - code, localsArg, null - }); - } - } - - class NetObject - { - public long LongProperty { get; set; } = 42; - } -} diff --git a/src/perf_tests/baseline/.gitkeep b/src/perf_tests/baseline/.gitkeep deleted file mode 100644 index e69de29bb..000000000 From 0258688e684c0a7ad59b4d76384a1d4a21735472 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 21 Oct 2025 19:20:12 +0200 Subject: [PATCH 101/194] Properly detect availability of BinaryFormatter (#2639) --- src/runtime/StateSerialization/RuntimeData.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 { From cccb2956d5ac581093ac8fe5702ea06e28d5e4cc Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 21 Oct 2025 19:26:13 +0200 Subject: [PATCH 102/194] Use last compiler toolset version that support .NET 8 (#2640) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index fcbae2c8f..0288a4d64 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers From 4cb57084f9de54eff9f16e0ce69dd70ec9a7e8d6 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 22 Oct 2025 19:22:08 +0200 Subject: [PATCH 103/194] Add dependabot file (#2642) --- .github/dependabot.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/dependabot.yml 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" From dcf7abc44848a11d46dc28e274a35681d3210177 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 22 Oct 2025 19:22:52 +0200 Subject: [PATCH 104/194] Use official ARM runners (#2641) --- .github/workflows/ARM.yml | 56 -------------------------------------- .github/workflows/main.yml | 4 +++ 2 files changed, 4 insertions(+), 56 deletions(-) delete mode 100644 .github/workflows/ARM.yml diff --git a/.github/workflows/ARM.yml b/.github/workflows/ARM.yml deleted file mode 100644 index c2b89edfe..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@v5 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - dotnet-version: '8.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/main.yml b/.github/workflows/main.yml index ea6b2c99a..93c76064a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,6 +28,10 @@ jobs: platform: x64 instance: ubuntu-22.04 + - category: ubuntu + platform: arm64 + instance: ubuntu-22.04-arm + - category: macos platform: x64 instance: macos-13 From 08381a40608ed15826414ee9c0237e6dfd94a0f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Oct 2025 19:27:10 +0200 Subject: [PATCH 105/194] Bump actions/upload-pages-artifact from 3 to 4 (#2644) Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-pages-artifact dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 30163cd14..5e6b76fdd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -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/ From 6cedd18bc7cc3a6ad0bccca1f298d454853b9d36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Oct 2025 19:27:19 +0200 Subject: [PATCH 106/194] Bump actions/setup-python from 2 to 6 (#2646) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- .github/workflows/nuget-preview.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 93c76064a..dc9148a55 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -62,7 +62,7 @@ jobs: dotnet-version: '8.0.x' - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python }} architecture: ${{ matrix.os.platform }} diff --git a/.github/workflows/nuget-preview.yml b/.github/workflows/nuget-preview.yml index d652f4b1e..1aa071480 100644 --- a/.github/workflows/nuget-preview.yml +++ b/.github/workflows/nuget-preview.yml @@ -29,7 +29,7 @@ jobs: 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 From 36d5d59d03536bf9bf0fca2bba402dd42267b584 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Oct 2025 19:27:28 +0200 Subject: [PATCH 107/194] Bump actions/checkout from 2 to 5 (#2648) Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 2 +- .github/workflows/nuget-preview.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5e6b76fdd..2b1dda732 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@v5 - name: Doxygen Action uses: mattnotmitt/doxygen-action@1.12.0 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dc9148a55..26f48aaaa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,7 +54,7 @@ jobs: mono-version: latest - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v5 - name: Setup .NET uses: actions/setup-dotnet@v1 diff --git a/.github/workflows/nuget-preview.yml b/.github/workflows/nuget-preview.yml index 1aa071480..0f6d3ea41 100644 --- a/.github/workflows/nuget-preview.yml +++ b/.github/workflows/nuget-preview.yml @@ -21,7 +21,7 @@ jobs: echo "DATE_VER=$(date "+%Y-%m-%d")" >> $GITHUB_ENV - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v5 - name: Setup .NET uses: actions/setup-dotnet@v1 From e4211db55c989b133859d107ae71bb37b19be5cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Oct 2025 19:27:47 +0200 Subject: [PATCH 108/194] Bump actions/setup-dotnet from 1 to 5 (#2645) Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 1 to 5. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v1...v5) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- .github/workflows/nuget-preview.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 26f48aaaa..5499cc020 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -57,7 +57,7 @@ jobs: uses: actions/checkout@v5 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v5 with: dotnet-version: '8.0.x' diff --git a/.github/workflows/nuget-preview.yml b/.github/workflows/nuget-preview.yml index 0f6d3ea41..4d1476560 100644 --- a/.github/workflows/nuget-preview.yml +++ b/.github/workflows/nuget-preview.yml @@ -24,7 +24,7 @@ jobs: uses: actions/checkout@v5 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v5 with: dotnet-version: '6.0.x' From 7d3a6dc5cc12ffa869a79106cac890014110b66c Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 22 Oct 2025 21:31:52 +0200 Subject: [PATCH 109/194] Bump some dependencies --- pyproject.toml | 2 +- src/embed_tests/Python.EmbeddingTest.csproj | 4 ++-- src/python_tests_runner/Python.PythonTestsRunner.csproj | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 20947be0e..e3fba0dda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ 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" diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 2b3d6610e..da6799912 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -20,11 +20,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + 1.0.0 all diff --git a/src/python_tests_runner/Python.PythonTestsRunner.csproj b/src/python_tests_runner/Python.PythonTestsRunner.csproj index 964166907..e55c1af37 100644 --- a/src/python_tests_runner/Python.PythonTestsRunner.csproj +++ b/src/python_tests_runner/Python.PythonTestsRunner.csproj @@ -10,14 +10,14 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - 1.0.0 + 1.* all runtime; build; native; contentfiles; analyzers From 3426bfd34fe9274012d16e82431441ad4a6773a1 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 25 Oct 2025 19:46:01 +0200 Subject: [PATCH 110/194] Use uv and derive as much as possible from the environment, if available (#2652) * Use uv * Derive program name from venv if possible * Implement venv derivation --- .github/workflows/main.yml | 31 +-- src/runtime/Python.Runtime.csproj | 2 +- src/runtime/Runtime.cs | 43 ++-- src/runtime/Util/PythonEnvironment.cs | 188 ++++++++++++++++ tools/geninterop/geninterop.py | 7 +- uv.lock | 295 ++++++++++++++++++++++++++ 6 files changed, 512 insertions(+), 54 deletions(-) create mode 100644 src/runtime/Util/PythonEnvironment.cs create mode 100644 uv.lock diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5499cc020..11762a875 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -62,34 +62,14 @@ jobs: dotnet-version: '8.0.x' - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v6 + uses: astral-sh/setup-uv@v6 with: - python-version: ${{ matrix.python }} architecture: ${{ matrix.os.platform }} - - - name: Install dependencies - run: | - pip install --upgrade -r requirements.txt - pip install numpy # for tests - - - name: Build and Install - run: | - pip install -v . - - - name: Set Python DLL path 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 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)')" + python-version: ${{ matrix.python }} + activate-environment: true + enable-cache: true - name: Embedding tests - if: ${{ matrix.python != '3.13' }} run: dotnet test --runtime any-${{ matrix.os.platform }} --logger "console;verbosity=detailed" src/embed_tests/ env: MONO_THREADS_SUSPEND: preemptive # https://github.com/mono/mono/issues/21466 @@ -108,7 +88,6 @@ jobs: 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/ + run: uv run dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/ # TODO: Run mono tests on Windows? diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 0a7f58a3c..307b2c3ad 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -2,7 +2,7 @@ netstandard2.0 AnyCPU - 10.0 + 10 Python.Runtime Python.Runtime enable 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/Util/PythonEnvironment.cs b/src/runtime/Util/PythonEnvironment.cs new file mode 100644 index 000000000..701db3c93 --- /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 (Runtime.IsWindows) + { + 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/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..9f73eb110 --- /dev/null +++ b/uv.lock @@ -0,0 +1,295 @@ +version = 1 +revision = 3 +requires-python = ">=3.10, <3.14" + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, +] + +[[package]] +name = "clr-loader" +version = "0.2.7.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/b3/8ae917e458394e2cebdbf17bed0a8204f8d4ffc79a093a7b1141c7731d3c/clr_loader-0.2.7.post0.tar.gz", hash = "sha256:b7a8b3f8fbb1bcbbb6382d887e21d1742d4f10b5ea209e4ad95568fe97e1c7c6", size = 56701, upload-time = "2024-12-12T20:15:15.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/c0/06e64a54bced4e8b885c1e7ec03ee1869e52acf69e87da40f92391a214ad/clr_loader-0.2.7.post0-py3-none-any.whl", hash = "sha256:e0b9fcc107d48347a4311a28ffe3ae78c4968edb216ffb6564cb03f7ace0bb47", size = 50649, upload-time = "2024-12-12T20:15:13.714Z" }, +] + +[[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.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, +] + +[[package]] +name = "find-libpython" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/86/b1d3a9c49d907cac74f9d8bcead2c8e807a878c0e218d8ef1d38e6a4f59a/find_libpython-0.4.0.tar.gz", hash = "sha256:46f9cdcd397ddb563b2d7592ded3796a41c1df5222443bd9d981721c906c03e6", size = 8979, upload-time = "2024-03-13T17:01:10.727Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/89/6b4624122d5c61a86e8aebcebd377866338b705ce4f115c45b046dc09b99/find_libpython-0.4.0-py3-none-any.whl", hash = "sha256:034a4253bd57da3408aefc59aeac1650150f6c1f42e10fdd31615cf1df0842e3", size = 8670, upload-time = "2024-03-13T17:01:09.712Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701, upload-time = "2025-03-16T18:27:00.648Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/89/a79e86e5c1433926ed7d60cb267fb64aa578b6101ab645800fd43b4801de/numpy-2.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9", size = 21250661, upload-time = "2025-03-16T18:02:13.017Z" }, + { url = "https://files.pythonhosted.org/packages/79/c2/f50921beb8afd60ed9589ad880332cfefdb805422210d327fb48f12b7a81/numpy-2.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae", size = 14389926, upload-time = "2025-03-16T18:02:39.022Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/2c4e96130b0b0f97b0ef4a06d6dae3b39d058b21a5e2fa2decd7fd6b1c8f/numpy-2.2.4-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:a84eda42bd12edc36eb5b53bbcc9b406820d3353f1994b6cfe453a33ff101775", size = 5428329, upload-time = "2025-03-16T18:02:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a5/3d7094aa898f4fc5c84cdfb26beeae780352d43f5d8bdec966c4393d644c/numpy-2.2.4-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:4ba5054787e89c59c593a4169830ab362ac2bee8a969249dc56e5d7d20ff8df9", size = 6963559, upload-time = "2025-03-16T18:03:02.523Z" }, + { url = "https://files.pythonhosted.org/packages/4c/22/fb1be710a14434c09080dd4a0acc08939f612ec02efcb04b9e210474782d/numpy-2.2.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7716e4a9b7af82c06a2543c53ca476fa0b57e4d760481273e09da04b74ee6ee2", size = 14368066, upload-time = "2025-03-16T18:03:27.146Z" }, + { url = "https://files.pythonhosted.org/packages/c2/07/2e5cc71193e3ef3a219ffcf6ca4858e46ea2be09c026ddd480d596b32867/numpy-2.2.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf8c1d66f432ce577d0197dceaac2ac00c0759f573f28516246351c58a85020", size = 16417040, upload-time = "2025-03-16T18:03:55.999Z" }, + { url = "https://files.pythonhosted.org/packages/1a/97/3b1537776ad9a6d1a41813818343745e8dd928a2916d4c9edcd9a8af1dac/numpy-2.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:218f061d2faa73621fa23d6359442b0fc658d5b9a70801373625d958259eaca3", size = 15879862, upload-time = "2025-03-16T18:04:23.56Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b7/4472f603dd45ef36ff3d8e84e84fe02d9467c78f92cc121633dce6da307b/numpy-2.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:df2f57871a96bbc1b69733cd4c51dc33bea66146b8c63cacbfed73eec0883017", size = 18206032, upload-time = "2025-03-16T18:04:53.694Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bd/6a092963fb82e6c5aa0d0440635827bbb2910da229545473bbb58c537ed3/numpy-2.2.4-cp310-cp310-win32.whl", hash = "sha256:a0258ad1f44f138b791327961caedffbf9612bfa504ab9597157806faa95194a", size = 6608517, upload-time = "2025-03-16T18:05:06.647Z" }, + { url = "https://files.pythonhosted.org/packages/01/e3/cb04627bc2a1638948bc13e818df26495aa18e20d5be1ed95ab2b10b6847/numpy-2.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:0d54974f9cf14acf49c60f0f7f4084b6579d24d439453d5fc5805d46a165b542", size = 12943498, upload-time = "2025-03-16T18:05:28.591Z" }, + { url = "https://files.pythonhosted.org/packages/16/fb/09e778ee3a8ea0d4dc8329cca0a9c9e65fed847d08e37eba74cb7ed4b252/numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4", size = 21254989, upload-time = "2025-03-16T18:06:04.092Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0a/1212befdbecab5d80eca3cde47d304cad986ad4eec7d85a42e0b6d2cc2ef/numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4", size = 14425910, upload-time = "2025-03-16T18:06:29.062Z" }, + { url = "https://files.pythonhosted.org/packages/2b/3e/e7247c1d4f15086bb106c8d43c925b0b2ea20270224f5186fa48d4fb5cbd/numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f", size = 5426490, upload-time = "2025-03-16T18:06:39.901Z" }, + { url = "https://files.pythonhosted.org/packages/5d/fa/aa7cd6be51419b894c5787a8a93c3302a1ed4f82d35beb0613ec15bdd0e2/numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880", size = 6967754, upload-time = "2025-03-16T18:06:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ee/96457c943265de9fadeb3d2ffdbab003f7fba13d971084a9876affcda095/numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1", size = 14373079, upload-time = "2025-03-16T18:07:16.297Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/ceefca458559f0ccc7a982319f37ed07b0d7b526964ae6cc61f8ad1b6119/numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5", size = 16428819, upload-time = "2025-03-16T18:07:44.188Z" }, + { url = "https://files.pythonhosted.org/packages/22/31/9b2ac8eee99e001eb6add9fa27514ef5e9faf176169057a12860af52704c/numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687", size = 15881470, upload-time = "2025-03-16T18:08:11.545Z" }, + { url = "https://files.pythonhosted.org/packages/f0/dc/8569b5f25ff30484b555ad8a3f537e0225d091abec386c9420cf5f7a2976/numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6", size = 18218144, upload-time = "2025-03-16T18:08:42.042Z" }, + { url = "https://files.pythonhosted.org/packages/5e/05/463c023a39bdeb9bb43a99e7dee2c664cb68d5bb87d14f92482b9f6011cc/numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09", size = 6606368, upload-time = "2025-03-16T18:08:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/8b/72/10c1d2d82101c468a28adc35de6c77b308f288cfd0b88e1070f15b98e00c/numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91", size = 12947526, upload-time = "2025-03-16T18:09:16.844Z" }, + { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156, upload-time = "2025-03-16T18:09:51.975Z" }, + { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092, upload-time = "2025-03-16T18:10:16.329Z" }, + { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515, upload-time = "2025-03-16T18:10:26.19Z" }, + { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558, upload-time = "2025-03-16T18:10:38.996Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742, upload-time = "2025-03-16T18:11:02.76Z" }, + { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051, upload-time = "2025-03-16T18:11:32.767Z" }, + { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972, upload-time = "2025-03-16T18:11:59.877Z" }, + { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106, upload-time = "2025-03-16T18:12:31.487Z" }, + { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190, upload-time = "2025-03-16T18:12:44.46Z" }, + { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305, upload-time = "2025-03-16T18:13:06.864Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623, upload-time = "2025-03-16T18:13:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681, upload-time = "2025-03-16T18:14:08.031Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759, upload-time = "2025-03-16T18:14:18.613Z" }, + { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092, upload-time = "2025-03-16T18:14:31.386Z" }, + { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422, upload-time = "2025-03-16T18:14:54.83Z" }, + { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202, upload-time = "2025-03-16T18:15:22.035Z" }, + { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131, upload-time = "2025-03-16T18:15:48.546Z" }, + { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270, upload-time = "2025-03-16T18:16:20.274Z" }, + { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141, upload-time = "2025-03-16T18:20:15.297Z" }, + { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885, upload-time = "2025-03-16T18:20:36.982Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829, upload-time = "2025-03-16T18:16:56.191Z" }, + { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419, upload-time = "2025-03-16T18:17:22.811Z" }, + { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414, upload-time = "2025-03-16T18:17:34.066Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379, upload-time = "2025-03-16T18:17:47.466Z" }, + { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725, upload-time = "2025-03-16T18:18:11.904Z" }, + { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638, upload-time = "2025-03-16T18:18:40.749Z" }, + { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717, upload-time = "2025-03-16T18:19:04.512Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998, upload-time = "2025-03-16T18:19:32.52Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896, upload-time = "2025-03-16T18:19:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119, upload-time = "2025-03-16T18:20:03.94Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5c/f09c33a511aff41a098e6ef3498465d95f6360621034a3d95f47edbc9119/numpy-2.2.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7051ee569db5fbac144335e0f3b9c2337e0c8d5c9fee015f259a5bd70772b7e8", size = 21081956, upload-time = "2025-03-16T18:21:12.955Z" }, + { url = "https://files.pythonhosted.org/packages/ba/30/74c48b3b6494c4b820b7fa1781d441e94d87a08daa5b35d222f06ba41a6f/numpy-2.2.4-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ab2939cd5bec30a7430cbdb2287b63151b77cf9624de0532d629c9a1c59b1d5c", size = 6827143, upload-time = "2025-03-16T18:21:26.748Z" }, + { url = "https://files.pythonhosted.org/packages/54/f5/ab0d2f48b490535c7a80e05da4a98902b632369efc04f0e47bb31ca97d8f/numpy-2.2.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f35b19894a9e08639fd60a1ec1978cb7f5f7f1eace62f38dd36be8aecdef4d", size = 16233350, upload-time = "2025-03-16T18:21:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/3b/3a/2f6d8c1f8e45d496bca6baaec93208035faeb40d5735c25afac092ec9a12/numpy-2.2.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b4adfbbc64014976d2f91084915ca4e626fbf2057fb81af209c1a6d776d23e3d", size = 12857565, upload-time = "2025-03-16T18:22:17.631Z" }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +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 = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "pythonnet" +source = { editable = "." } +dependencies = [ + { name = "clr-loader" }, +] + +[package.dev-dependencies] +dev = [ + { name = "find-libpython" }, + { name = "numpy" }, + { name = "psutil" }, + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [{ name = "clr-loader", specifier = ">=0.2.7,<0.3.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 = "psutil" }, + { name = "pytest", specifier = ">=6" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] From 5af3b273753c5db4c4dc6770558a6ddb971a6bd5 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 26 Oct 2025 18:35:06 +0100 Subject: [PATCH 111/194] Disable 32bit tests for now --- .github/workflows/main.yml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 11762a875..46b1c0c7c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,9 +16,11 @@ jobs: fail-fast: false matrix: os: - - category: windows - platform: x86 - instance: windows-latest + # Disabled for now, will require some work (#2653) + # + # - category: windows + # platform: x86 + # instance: windows-latest - category: windows platform: x64 @@ -38,14 +40,6 @@ jobs: python: ["3.10", "3.11", "3.12", "3.13"] - # 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: - category: windows - platform: x86 - python: "3.13" - steps: - name: Set Environment on macOS uses: maxim-lobanov/setup-xamarin@v1 From ec651f13e42fcb32346f371bd1fd5df50ff8a85e Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 26 Oct 2025 18:38:39 +0100 Subject: [PATCH 112/194] Always run the embedding tests, and run them separately for Mono and .NET Core --- .github/workflows/main.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 46b1c0c7c..57ef57682 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -63,18 +63,21 @@ jobs: activate-environment: true enable-cache: true - - name: Embedding tests - run: dotnet test --runtime any-${{ matrix.os.platform }} --logger "console;verbosity=detailed" src/embed_tests/ + - name: Embedding tests (Mono/.NET Framework) + run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net472 --logger "console;verbosity=detailed" src/embed_tests/ + if: always() env: MONO_THREADS_SUSPEND: preemptive # https://github.com/mono/mono/issues/21466 + - name: Embedding tests (.NET Core) + run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net8.0 --logger "console;verbosity=detailed" src/embed_tests/ + if: always() + - name: Python Tests (Mono) if: ${{ matrix.os.category != 'windows' }} run: pytest --runtime mono - # TODO: Run these tests on Windows x86 - name: Python Tests (.NET Core) - if: ${{ matrix.os.platform == 'x64' }} run: pytest --runtime coreclr - name: Python Tests (.NET Framework) @@ -82,6 +85,4 @@ jobs: run: pytest --runtime netfx - name: Python tests run from .NET - run: uv run dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/ - - # TODO: Run mono tests on Windows? + run: dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/ From 13dff92a39e2d3b13331171c6b9db269cfa242ba Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 26 Oct 2025 18:54:21 +0100 Subject: [PATCH 113/194] Synchronize the environment --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 57ef57682..8c700af49 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -63,6 +63,9 @@ jobs: activate-environment: true enable-cache: true + - name: Synchronize the virtual environment + run: uv sync + - name: Embedding tests (Mono/.NET Framework) run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net472 --logger "console;verbosity=detailed" src/embed_tests/ if: always() From 85c8c2cff42b69253cd032fba2a2bf269f8e46bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 09:55:05 +0100 Subject: [PATCH 114/194] Bump astral-sh/setup-uv from 6 to 7 (#2656) Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 6 to 7. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v6...v7) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c700af49..f2611e77d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,7 +56,7 @@ jobs: dotnet-version: '8.0.x' - name: Set up Python ${{ matrix.python }} - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: architecture: ${{ matrix.os.platform }} python-version: ${{ matrix.python }} From 5276ac6b16ded87226258049789446152617a37b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:19:13 +0100 Subject: [PATCH 115/194] Bump actions/checkout from 5 to 6 (#2663) Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 2 +- .github/workflows/nuget-preview.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2b1dda732..3937d85e0 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@v5 + - uses: actions/checkout@v6 - name: Doxygen Action uses: mattnotmitt/doxygen-action@1.12.0 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f2611e77d..ca2562d04 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,7 +48,7 @@ jobs: mono-version: latest - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5 diff --git a/.github/workflows/nuget-preview.yml b/.github/workflows/nuget-preview.yml index 4d1476560..6de97d50e 100644 --- a/.github/workflows/nuget-preview.yml +++ b/.github/workflows/nuget-preview.yml @@ -21,7 +21,7 @@ jobs: echo "DATE_VER=$(date "+%Y-%m-%d")" >> $GITHUB_ENV - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5 From b871d3532bbc1a17f949949aa61cfc0d84316244 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 22 Oct 2025 22:11:27 +0200 Subject: [PATCH 116/194] Only init/shutdown Python once --- Directory.Build.props | 2 +- src/embed_tests/CallableObject.cs | 25 +++++---- src/embed_tests/ClassManagerTests.cs | 12 ---- src/embed_tests/CodecGroups.cs | 62 ++++++++++----------- src/embed_tests/Codecs.cs | 12 ---- src/embed_tests/{dynamic.cs => Dynamic.cs} | 12 ---- src/embed_tests/ExtensionTypes.cs | 14 +---- src/embed_tests/GlobalTestsSetup.cs | 1 + src/embed_tests/Inheritance.cs | 52 ++++++++++------- src/embed_tests/Inspect.cs | 14 +---- src/embed_tests/Modules.cs | 48 ++++++++-------- src/embed_tests/NumPyTests.cs | 5 +- src/embed_tests/Python.EmbeddingTest.csproj | 4 ++ src/embed_tests/References.cs | 16 +----- src/embed_tests/TestCallbacks.cs | 12 +--- src/embed_tests/TestConverter.cs | 46 ++++++--------- src/embed_tests/TestCustomMarshal.cs | 18 +----- src/embed_tests/TestFinalizer.cs | 27 +++++---- src/embed_tests/TestGILState.cs | 12 ---- src/embed_tests/TestInstanceWrapping.cs | 16 +----- src/embed_tests/TestInterrupt.cs | 12 ++-- src/embed_tests/TestNamedArguments.cs | 16 +----- src/embed_tests/TestNativeTypeOffset.cs | 12 ---- src/embed_tests/TestOperator.cs | 3 +- src/embed_tests/TestPyBuffer.cs | 21 ++++--- src/embed_tests/TestPyFloat.cs | 32 ++++------- src/embed_tests/pyimport.cs | 18 +++--- src/embed_tests/pyinitialize.cs | 6 +- src/embed_tests/pyrunstring.cs | 20 ++----- 29 files changed, 193 insertions(+), 357 deletions(-) rename src/embed_tests/{dynamic.cs => Dynamic.cs} (95%) diff --git a/Directory.Build.props b/Directory.Build.props index 0288a4d64..5f8157f2e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ 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]) 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..cecc6825d 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -8,18 +8,6 @@ namespace Python.EmbeddingTest { public class Codecs { - [SetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [TearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TupleConversionsGeneric() { 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/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..fc9b8f398 100644 --- a/src/embed_tests/Modules.cs +++ b/src/embed_tests/Modules.cs @@ -50,7 +50,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 +66,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 +83,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 +102,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 +123,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 +134,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 +219,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 +235,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 +266,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 +289,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 +345,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 +420,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 +434,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 +448,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/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 da6799912..cb3b52052 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -20,6 +20,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + all runtime; build; native; contentfiles; analyzers; buildtransitive 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/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(); - Assert.IsTrue(PythonReferenceComparer.Instance.Equals(i, ni)); + Assert.That(PythonReferenceComparer.Instance.Equals(i, ni), Is.True); } [Test] @@ -158,7 +146,7 @@ public void ToPyList() list.Append("hello".ToPython()); list.Append("world".ToPython()); var back = list.ToPython().As(); - Assert.AreEqual(list.Length(), back.Length()); + Assert.That(back.Length(), Is.EqualTo(list.Length())); } [Test] @@ -182,7 +170,7 @@ public void RawPyObjectProxy() const string handlePropertyName = nameof(PyObject.Handle); #pragma warning restore CS0612 // Type or member is obsolete var proxiedHandle = pyObjectProxy.GetAttr(handlePropertyName).As(); - Assert.AreEqual(pyObject.DangerousGetAddressOrNull(), proxiedHandle); + Assert.That(proxiedHandle, Is.EqualTo(pyObject.DangerousGetAddressOrNull())); } [Test] @@ -191,7 +179,7 @@ public void GenericToPython() int i = 42; var pyObject = i.ToPythonAs(); var type = pyObject.GetPythonType(); - Assert.AreEqual(nameof(IConvertible), type.Name); + Assert.That(type.Name, Is.EqualTo(nameof(IConvertible))); } // regression for https://github.com/pythonnet/pythonnet/issues/451 @@ -207,7 +195,7 @@ class PyGetListImpl(test.GetListImpl): var pyImpl = scope.Get("PyGetListImpl"); dynamic inst = pyImpl.Invoke(); List result = inst.GetList(); - CollectionAssert.AreEqual(new[] { "testing" }, result); + Assert.That(result, Is.EqualTo(new[] { "testing" }).AsCollection); } } diff --git a/src/embed_tests/TestCustomMarshal.cs b/src/embed_tests/TestCustomMarshal.cs index 312863d0c..3bcb6b2e6 100644 --- a/src/embed_tests/TestCustomMarshal.cs +++ b/src/embed_tests/TestCustomMarshal.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestCustomMarshal { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public static void GetManagedStringTwice() { @@ -27,9 +15,9 @@ public static void GetManagedStringTwice() string s1 = Runtime.Runtime.GetManagedString(op.BorrowOrThrow()); string s2 = Runtime.Runtime.GetManagedString(op.Borrow()); - Assert.AreEqual(1, Runtime.Runtime.Refcount32(op.Borrow())); - Assert.AreEqual(expected, s1); - Assert.AreEqual(expected, s2); + Assert.That(Runtime.Runtime.Refcount32(op.Borrow()), Is.EqualTo(1)); + Assert.That(s1, Is.EqualTo(expected)); + Assert.That(s2, Is.EqualTo(expected)); } } } diff --git a/src/embed_tests/TestFinalizer.cs b/src/embed_tests/TestFinalizer.cs index b748a2244..9ef3e25b6 100644 --- a/src/embed_tests/TestFinalizer.cs +++ b/src/embed_tests/TestFinalizer.cs @@ -17,7 +17,6 @@ public class TestFinalizer public void SetUp() { _oldThreshold = Finalizer.Instance.Threshold; - PythonEngine.Initialize(); Exceptions.Clear(); } @@ -25,7 +24,6 @@ public void SetUp() public void TearDown() { Finalizer.Instance.Threshold = _oldThreshold; - PythonEngine.Shutdown(); } private static void FullGCCollect() @@ -38,7 +36,7 @@ private static void FullGCCollect() [Obsolete("GC tests are not guaranteed")] public void CollectBasicObject() { - Assert.IsTrue(Finalizer.Instance.Enable); + Assert.That(Finalizer.Instance.Enable, Is.True); Finalizer.Instance.Threshold = 1; bool called = false; @@ -49,7 +47,7 @@ public void CollectBasicObject() called = true; }; - Assert.IsFalse(called, "The event handler was called before it was installed"); + Assert.That(called, Is.False, "The event handler was called before it was installed"); Finalizer.Instance.BeforeCollect += handler; IntPtr pyObj = MakeAGarbage(out var shortWeak, out var longWeak); @@ -60,10 +58,10 @@ public void CollectBasicObject() "The referenced object is alive although it should have been collected", shortWeak ); - Assert.IsTrue( + Assert.That( longWeak.IsAlive, - "The reference object is not alive although it should still be", - longWeak + Is.True, + $"The reference object is not alive although it should still be" ); { @@ -83,7 +81,7 @@ public void CollectBasicObject() { Finalizer.Instance.BeforeCollect -= handler; } - Assert.IsTrue(called, "The event handler was not called during finalization"); + Assert.That(called, Is.True, "The event handler was not called during finalization"); Assert.GreaterOrEqual(objectCount, 1); } @@ -93,10 +91,11 @@ public void CollectOnShutdown() { IntPtr op = MakeAGarbage(out var shortWeak, out var longWeak); FullGCCollect(); - Assert.IsFalse(shortWeak.IsAlive); + Assert.That(shortWeak.IsAlive, Is.False); List garbage = Finalizer.Instance.GetCollectedObjects(); Assert.IsNotEmpty(garbage, "The garbage object should be collected"); - Assert.IsTrue(garbage.Contains(op), + Assert.That(garbage.Contains(op), + Is.True, "Garbage should contains the collected object"); PythonEngine.Shutdown(); @@ -133,7 +132,7 @@ private static IntPtr MakeAGarbage(out WeakReference shortWeak, out WeakReferenc handle = obj.Handle; }); garbageGen.Start(); - Assert.IsTrue(garbageGen.Join(TimeSpan.FromSeconds(5)), "Garbage creation timed out"); + Assert.That(garbageGen.Join(TimeSpan.FromSeconds(5)), Is.True, "Garbage creation timed out"); shortWeak = @short; longWeak = @long; return handle; @@ -209,8 +208,8 @@ public void ValidateRefCount() Finalizer.IncorrectRefCntHandler handler = (s, e) => { called = true; - Assert.AreEqual(ptr, e.Handle); - Assert.AreEqual(2, e.ImpactedObjects.Count); + Assert.That(e.Handle, Is.EqualTo(ptr)); + Assert.That(e.ImpactedObjects.Count, Is.EqualTo(2)); // Fix for this test, don't do this on general environment #pragma warning disable CS0618 // Type or member is obsolete Runtime.Runtime.XIncref(e.Reference); @@ -223,7 +222,7 @@ public void ValidateRefCount() ptr = CreateStringGarbage(); FullGCCollect(); Assert.Throws(() => Finalizer.Instance.Collect()); - Assert.IsTrue(called); + Assert.That(called, Is.True); } finally { diff --git a/src/embed_tests/TestGILState.cs b/src/embed_tests/TestGILState.cs index bf6f02dc6..ba2ab500f 100644 --- a/src/embed_tests/TestGILState.cs +++ b/src/embed_tests/TestGILState.cs @@ -17,17 +17,5 @@ public void CanDisposeMultipleTimes() gilState.Dispose(); } } - - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } } } diff --git a/src/embed_tests/TestInstanceWrapping.cs b/src/embed_tests/TestInstanceWrapping.cs index 0a441c823..c6996fd87 100644 --- a/src/embed_tests/TestInstanceWrapping.cs +++ b/src/embed_tests/TestInstanceWrapping.cs @@ -7,18 +7,6 @@ namespace Python.EmbeddingTest { public class TestInstanceWrapping { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - // regression test for https://github.com/pythonnet/pythonnet/issues/811 [Test] public void OverloadResolution_UnknownToObject() @@ -30,7 +18,7 @@ public void OverloadResolution_UnknownToObject() dynamic callWithSelf = PythonEngine.Eval("lambda o: o.ObjOrClass(object())"); callWithSelf(o); - Assert.AreEqual(Overloaded.Object, overloaded.Value); + Assert.That(overloaded.Value, Is.EqualTo(Overloaded.Object)); } } @@ -41,7 +29,7 @@ public void WeakRefIsNone_AfterObjectIsGone() var ub = new UriBuilder().ToPython(); using var weakref = makeref.Invoke(ub); ub.Dispose(); - Assert.IsTrue(weakref.Invoke().IsNone()); + Assert.That(weakref.Invoke().IsNone(), Is.True); } class Base {} diff --git a/src/embed_tests/TestInterrupt.cs b/src/embed_tests/TestInterrupt.cs index e6546adb2..d48f7c73b 100644 --- a/src/embed_tests/TestInterrupt.cs +++ b/src/embed_tests/TestInterrupt.cs @@ -15,7 +15,6 @@ public class TestInterrupt [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); // workaround for assert tlock.locked() warning threading = Py.Import("threading"); } @@ -24,7 +23,6 @@ public void SetUp() public void Dispose() { threading.Dispose(); - PythonEngine.Shutdown(); } [Test] @@ -50,9 +48,9 @@ public void PythonThreadIDStable() } PythonEngine.EndAllowThreads(threadState); - Assert.IsTrue(asyncCall.Wait(TimeSpan.FromSeconds(5)), "Async thread has not finished in time"); + Assert.That(asyncCall.Wait(TimeSpan.FromSeconds(5)), Is.True, "Async thread has not finished in time"); - Assert.AreEqual(pythonThreadID, pythonThreadID2); + Assert.That(pythonThreadID2, Is.EqualTo(pythonThreadID)); Assert.NotZero(pythonThreadID); } @@ -86,13 +84,13 @@ import time PythonEngine.EndAllowThreads(threadState); int interruptReturnValue = PythonEngine.Interrupt((ulong)Interlocked.Read(ref pythonThreadID)); - Assert.AreEqual(1, interruptReturnValue); + Assert.That(interruptReturnValue, Is.EqualTo(1)); threadState = PythonEngine.BeginAllowThreads(); - Assert.IsTrue(asyncCall.Wait(TimeSpan.FromSeconds(5)), "Async thread was not interrupted in time"); + Assert.That(asyncCall.Wait(TimeSpan.FromSeconds(5)), Is.True, "Async thread was not interrupted in time"); PythonEngine.EndAllowThreads(threadState); - Assert.AreEqual(0, asyncCall.Result); + Assert.That(asyncCall.Result, Is.EqualTo(0)); } } } diff --git a/src/embed_tests/TestNamedArguments.cs b/src/embed_tests/TestNamedArguments.cs index c86302038..16d7d5b8f 100644 --- a/src/embed_tests/TestNamedArguments.cs +++ b/src/embed_tests/TestNamedArguments.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestNamedArguments { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - /// /// Test named arguments support through Py.kw method /// @@ -27,7 +15,7 @@ public void TestKeywordArgs() dynamic a = CreateTestClass(); var result = (int)a.Test3(2, Py.kw("a4", 8)); - Assert.AreEqual(12, result); + Assert.That(result, Is.EqualTo(12)); } @@ -40,7 +28,7 @@ public void TestNamedArgs() dynamic a = CreateTestClass(); var result = (int)a.Test3(2, a4: 8); - Assert.AreEqual(12, result); + Assert.That(result, Is.EqualTo(12)); } diff --git a/src/embed_tests/TestNativeTypeOffset.cs b/src/embed_tests/TestNativeTypeOffset.cs index d692c24e6..61b6903c5 100644 --- a/src/embed_tests/TestNativeTypeOffset.cs +++ b/src/embed_tests/TestNativeTypeOffset.cs @@ -13,18 +13,6 @@ namespace Python.EmbeddingTest { public class TestNativeTypeOffset { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - /// /// Tests that installation has generated code for NativeTypeOffset and that it can be loaded. /// diff --git a/src/embed_tests/TestOperator.cs b/src/embed_tests/TestOperator.cs index 6bfb81bdb..ab71ed9b3 100644 --- a/src/embed_tests/TestOperator.cs +++ b/src/embed_tests/TestOperator.cs @@ -14,14 +14,13 @@ public class TestOperator [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); OwnIntCodec.Setup(); } [OneTimeTearDown] public void Dispose() { - PythonEngine.Shutdown(); + PyObjectConversions.Reset(); } // Mock Integer class to test math ops on non-native dotnet types diff --git a/src/embed_tests/TestPyBuffer.cs b/src/embed_tests/TestPyBuffer.cs index 1b4e28d12..89ddf9370 100644 --- a/src/embed_tests/TestPyBuffer.cs +++ b/src/embed_tests/TestPyBuffer.cs @@ -11,14 +11,13 @@ class TestPyBuffer [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); TupleCodec.Register(); } [OneTimeTearDown] public void Dispose() { - PythonEngine.Shutdown(); + PyObjectConversions.Reset(); } [Test] @@ -38,7 +37,7 @@ public void TestBufferWrite() } string result = pythonArray.InvokeMethod("decode", "utf-8".ToPython()).As(); - Assert.IsTrue(result == bufferTestString2); + Assert.That(result == bufferTestString2, Is.True); } [Test] @@ -58,7 +57,7 @@ public void TestBufferRead() } string result = new UTF8Encoding().GetString(managedArray); - Assert.IsTrue(result == " " + bufferTestString.Substring(1)); + Assert.That(result == " " + bufferTestString.Substring(1), Is.True); } [Test] @@ -67,8 +66,8 @@ public void ArrayHasBuffer() var array = new[,] {{1, 2}, {3,4}}; var memoryView = PythonEngine.Eval("memoryview"); var mem = memoryView.Invoke(array.ToPython()); - Assert.AreEqual(1, mem[(0, 0).ToPython()].As()); - Assert.AreEqual(array[1,0], mem[(1, 0).ToPython()].As()); + Assert.That(mem[(0, 0).ToPython()].As(), Is.EqualTo(1)); + Assert.That(mem[(1, 0).ToPython()].As(), Is.EqualTo(array[1, 0])); } [Test] @@ -77,14 +76,14 @@ public void RefCount() using var _ = Py.GIL(); using var arr = ByteArrayFromAsciiString("hello world! !$%&/()=?"); - Assert.AreEqual(1, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(1)); using (PyBuffer buf = arr.GetBuffer()) { - Assert.AreEqual(2, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(2)); } - Assert.AreEqual(1, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(1)); } [Test] @@ -99,7 +98,7 @@ public void Finalization() using var _ = Py.GIL(); using var arr = ByteArrayFromAsciiString("hello world! !$%&/()=?"); - Assert.AreEqual(1, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(1)); MakeBufAndLeak(arr); @@ -107,7 +106,7 @@ public void Finalization() GC.WaitForPendingFinalizers(); Finalizer.Instance.Collect(); - Assert.AreEqual(1, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(1)); } [Test] diff --git a/src/embed_tests/TestPyFloat.cs b/src/embed_tests/TestPyFloat.cs index 89e29e5fd..c6111f180 100644 --- a/src/embed_tests/TestPyFloat.cs +++ b/src/embed_tests/TestPyFloat.cs @@ -9,18 +9,6 @@ namespace Python.EmbeddingTest /// public class TestPyFloat { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void FloatCtor() { @@ -132,14 +120,14 @@ public void CompareTo() { var v = new PyFloat(42); - Assert.AreEqual(0, v.CompareTo(42f)); - Assert.AreEqual(0, v.CompareTo(42d)); + Assert.That(v.CompareTo(42f), Is.EqualTo(0)); + Assert.That(v.CompareTo(42d), Is.EqualTo(0)); - Assert.AreEqual(1, v.CompareTo(41f)); - Assert.AreEqual(1, v.CompareTo(41d)); + Assert.That(v.CompareTo(41f), Is.EqualTo(1)); + Assert.That(v.CompareTo(41d), Is.EqualTo(1)); - Assert.AreEqual(-1, v.CompareTo(43f)); - Assert.AreEqual(-1, v.CompareTo(43d)); + Assert.That(v.CompareTo(43f), Is.EqualTo(-1)); + Assert.That(v.CompareTo(43d), Is.EqualTo(-1)); } [Test] @@ -147,11 +135,11 @@ public void Equals() { var v = new PyFloat(42); - Assert.IsTrue(v.Equals(42f)); - Assert.IsTrue(v.Equals(42d)); + Assert.That(v.Equals(42f), Is.True); + Assert.That(v.Equals(42d), Is.True); - Assert.IsFalse(v.Equals(41f)); - Assert.IsFalse(v.Equals(41d)); + Assert.That(v.Equals(41f), Is.False); + Assert.That(v.Equals(41d), Is.False); } } } diff --git a/src/embed_tests/pyimport.cs b/src/embed_tests/pyimport.cs index b828d5315..551df1915 100644 --- a/src/embed_tests/pyimport.cs +++ b/src/embed_tests/pyimport.cs @@ -21,28 +21,28 @@ namespace Python.EmbeddingTest /// public class PyImportTest { + string TestPath; + [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); - /* Append the tests directory to sys.path * using reflection to circumvent the private * modifiers placed on most Runtime methods. */ - string testPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "fixtures"); - TestContext.Out.WriteLine(testPath); + TestPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "fixtures"); + TestContext.Out.WriteLine(TestPath); - using var str = Runtime.Runtime.PyString_FromString(testPath); - Assert.IsFalse(str.IsNull()); + using var str = Runtime.Runtime.PyString_FromString(TestPath); + Assert.That(str.IsNull(), Is.False); BorrowedReference path = Runtime.Runtime.PySys_GetObject("path"); - Assert.IsFalse(path.IsNull); + Assert.That(path.IsNull, Is.False); Runtime.Runtime.PyList_Append(path, str.Borrow()); } [OneTimeTearDown] public void Dispose() { - PythonEngine.Shutdown(); + // TODO Undo the above } /// @@ -89,7 +89,7 @@ public void BadAssembly() path = @"C:\Windows\System32\kernel32.dll"; } - Assert.IsTrue(File.Exists(path), $"Test DLL {path} does not exist!"); + Assert.That(File.Exists(path), Is.True, $"Test DLL {path} does not exist!"); string code = $@" import clr diff --git a/src/embed_tests/pyinitialize.cs b/src/embed_tests/pyinitialize.cs index 25dafb686..cde463e61 100644 --- a/src/embed_tests/pyinitialize.cs +++ b/src/embed_tests/pyinitialize.cs @@ -2,6 +2,7 @@ using NUnit.Framework; using Python.Runtime; +#if false namespace Python.EmbeddingTest { public class PyInitializeTest @@ -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])); } } } @@ -155,3 +156,4 @@ public void ShutdownHandlers() public class ImportClassShutdownRefcountClass { } } +#endif \ No newline at end of file diff --git a/src/embed_tests/pyrunstring.cs b/src/embed_tests/pyrunstring.cs index 57c133c00..fb1302800 100644 --- a/src/embed_tests/pyrunstring.cs +++ b/src/embed_tests/pyrunstring.cs @@ -6,26 +6,14 @@ namespace Python.EmbeddingTest { public class RunStringTest { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestRunSimpleString() { int aa = PythonEngine.RunSimpleString("import sys"); - Assert.AreEqual(0, aa); + Assert.That(aa, Is.EqualTo(0)); int bb = PythonEngine.RunSimpleString("import 1234"); - Assert.AreEqual(-1, bb); + Assert.That(bb, Is.EqualTo(-1)); } [Test] @@ -39,7 +27,7 @@ public void TestEval() object b = PythonEngine.Eval("sys.attr1 + a + 1", null, locals) .AsManagedObject(typeof(int)); - Assert.AreEqual(111, b); + Assert.That(b, Is.EqualTo(111)); } [Test] @@ -53,7 +41,7 @@ public void TestExec() PythonEngine.Exec("c = sys.attr1 + a + 1", null, locals); object c = locals.GetItem("c").AsManagedObject(typeof(int)); - Assert.AreEqual(111, c); + Assert.That(c, Is.EqualTo(111)); } [Test] From 05b8e32bd4f20a355992bba86108cb88b5fb2cf9 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 23 Oct 2025 11:47:03 +0200 Subject: [PATCH 117/194] Disable NUnit analyzer for now --- src/embed_tests/Python.EmbeddingTest.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index cb3b52052..eb2048c76 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -20,10 +20,10 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From ca50a92ccbfb1f57db9d81c74d0f128cd03c1e0b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 23 Oct 2025 11:47:25 +0200 Subject: [PATCH 118/194] Reset conversions after each codec test --- src/embed_tests/Codecs.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index cecc6825d..d4d22dcac 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -8,6 +8,12 @@ namespace Python.EmbeddingTest { public class Codecs { + [TearDown] + public void TearDown() + { + PyObjectConversions.Reset(); + } + [Test] public void TupleConversionsGeneric() { From e7b51d770d6ea9705139a17d8b23a8f506239964 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 23 Oct 2025 21:37:40 +0200 Subject: [PATCH 119/194] Remove shutdown from most tests, disable the rest for now --- src/embed_tests/Events.cs | 12 ------------ src/embed_tests/Modules.cs | 12 ------------ .../StateSerialization/MethodSerialization.cs | 6 +++++- src/embed_tests/TestFinalizer.cs | 1 + src/embed_tests/TestPyInt.cs | 12 ------------ src/embed_tests/TestPyIter.cs | 12 ------------ src/embed_tests/TestPyList.cs | 12 ------------ src/embed_tests/TestPyNumber.cs | 12 ------------ src/embed_tests/TestPyObject.cs | 12 ------------ src/embed_tests/TestPySequence.cs | 12 ------------ src/embed_tests/TestPyString.cs | 12 ------------ src/embed_tests/TestPyTuple.cs | 12 ------------ src/embed_tests/TestPyType.cs | 12 ------------ src/embed_tests/TestPyWith.cs | 12 ------------ src/embed_tests/TestPythonEngineProperties.cs | 15 ++++----------- src/embed_tests/TestPythonException.cs | 12 ------------ src/embed_tests/TestRuntime.cs | 1 + src/embed_tests/pyimport.cs | 3 ++- src/embed_tests/pyinitialize.cs | 3 +-- 19 files changed, 14 insertions(+), 171 deletions(-) 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/Modules.cs b/src/embed_tests/Modules.cs index fc9b8f398..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. /// 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/TestFinalizer.cs b/src/embed_tests/TestFinalizer.cs index 9ef3e25b6..89dcf137e 100644 --- a/src/embed_tests/TestFinalizer.cs +++ b/src/embed_tests/TestFinalizer.cs @@ -86,6 +86,7 @@ public void CollectBasicObject() } [Test] + [Ignore("Requires explicit shutdown")] [Obsolete("GC tests are not guaranteed")] public void CollectOnShutdown() { diff --git a/src/embed_tests/TestPyInt.cs b/src/embed_tests/TestPyInt.cs index d2767e664..36319cf1a 100644 --- a/src/embed_tests/TestPyInt.cs +++ b/src/embed_tests/TestPyInt.cs @@ -10,18 +10,6 @@ namespace Python.EmbeddingTest { public class TestPyInt { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestCtorInt() { diff --git a/src/embed_tests/TestPyIter.cs b/src/embed_tests/TestPyIter.cs index 7428da979..5da660242 100644 --- a/src/embed_tests/TestPyIter.cs +++ b/src/embed_tests/TestPyIter.cs @@ -9,18 +9,6 @@ namespace Python.EmbeddingTest { class TestPyIter { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void KeepOldObjects() { diff --git a/src/embed_tests/TestPyList.cs b/src/embed_tests/TestPyList.cs index eee129f2d..a380f0b2d 100644 --- a/src/embed_tests/TestPyList.cs +++ b/src/embed_tests/TestPyList.cs @@ -7,18 +7,6 @@ namespace Python.EmbeddingTest { public class TestPyList { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestStringIsListType() { diff --git a/src/embed_tests/TestPyNumber.cs b/src/embed_tests/TestPyNumber.cs index 0261c15c1..d8e275521 100644 --- a/src/embed_tests/TestPyNumber.cs +++ b/src/embed_tests/TestPyNumber.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPyNumber { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void IsNumberTypeTrue() { diff --git a/src/embed_tests/TestPyObject.cs b/src/embed_tests/TestPyObject.cs index 2f27eba1b..f762b94e9 100644 --- a/src/embed_tests/TestPyObject.cs +++ b/src/embed_tests/TestPyObject.cs @@ -8,18 +8,6 @@ namespace Python.EmbeddingTest { public class TestPyObject { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestGetDynamicMemberNames() { diff --git a/src/embed_tests/TestPySequence.cs b/src/embed_tests/TestPySequence.cs index dc35a2633..339ea1e83 100644 --- a/src/embed_tests/TestPySequence.cs +++ b/src/embed_tests/TestPySequence.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPySequence { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestIsSequenceTrue() { diff --git a/src/embed_tests/TestPyString.cs b/src/embed_tests/TestPyString.cs index 35c6339ee..a1fdd6079 100644 --- a/src/embed_tests/TestPyString.cs +++ b/src/embed_tests/TestPyString.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPyString { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestStringCtor() { diff --git a/src/embed_tests/TestPyTuple.cs b/src/embed_tests/TestPyTuple.cs index 5d76116aa..3a3fbf2a0 100644 --- a/src/embed_tests/TestPyTuple.cs +++ b/src/embed_tests/TestPyTuple.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPyTuple { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - /// /// Test IsTupleType without having to Initialize a tuple. /// PyTuple constructor use IsTupleType. This decouples the tests. diff --git a/src/embed_tests/TestPyType.cs b/src/embed_tests/TestPyType.cs index d98dfda2e..c29032a8a 100644 --- a/src/embed_tests/TestPyType.cs +++ b/src/embed_tests/TestPyType.cs @@ -10,18 +10,6 @@ namespace Python.EmbeddingTest { public class TestPyType { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void CanCreateHeapType() { diff --git a/src/embed_tests/TestPyWith.cs b/src/embed_tests/TestPyWith.cs index d1c9aac28..fbce811da 100644 --- a/src/embed_tests/TestPyWith.cs +++ b/src/embed_tests/TestPyWith.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPyWith { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - /// /// Test that exception is raised in context manager that ignores it. /// diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs index be91d7f45..c6b4a2024 100644 --- a/src/embed_tests/TestPythonEngineProperties.cs +++ b/src/embed_tests/TestPythonEngineProperties.cs @@ -9,7 +9,6 @@ public class TestPythonEngineProperties [Test] public static void GetBuildinfoDoesntCrash() { - PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.BuildInfo; @@ -22,7 +21,6 @@ public static void GetBuildinfoDoesntCrash() [Test] public static void GetCompilerDoesntCrash() { - PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.Compiler; @@ -36,7 +34,6 @@ public static void GetCompilerDoesntCrash() [Test] public static void GetCopyrightDoesntCrash() { - PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.Copyright; @@ -49,7 +46,6 @@ public static void GetCopyrightDoesntCrash() [Test] public static void GetPlatformDoesntCrash() { - PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.Platform; @@ -62,7 +58,6 @@ public static void GetPlatformDoesntCrash() [Test] public static void GetVersionDoesntCrash() { - PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.Version; @@ -75,21 +70,17 @@ public static void GetVersionDoesntCrash() [Test] public static void GetPythonPathDefault() { - PythonEngine.Initialize(); string s = PythonEngine.PythonPath; StringAssert.Contains("python", s.ToLower()); - PythonEngine.Shutdown(); } [Test] public static void GetProgramNameDefault() { - PythonEngine.Initialize(); string s = PythonEngine.ProgramName; Assert.NotNull(s); - PythonEngine.Shutdown(); } /// @@ -101,13 +92,12 @@ public static void GetPythonHomeDefault() { string envPythonHome = Environment.GetEnvironmentVariable("PYTHONHOME") ?? ""; - PythonEngine.Initialize(); string enginePythonHome = PythonEngine.PythonHome; Assert.AreEqual(envPythonHome, enginePythonHome); - PythonEngine.Shutdown(); } + [Ignore("Only works if we can shutdown and re-init the interpreter")] [Test] public void SetPythonHome() { @@ -130,6 +120,7 @@ public void SetPythonHome() PythonEngine.PythonHome = pythonHomeBackup; } + [Ignore("Only works if we can shutdown and re-init the interpreter")] [Test] public void SetPythonHomeTwice() { @@ -172,6 +163,7 @@ public void SetPythonHomeEmptyString() PythonEngine.Shutdown(); } + [Ignore("Only works if we can shutdown and re-init the interpreter")] [Test] public void SetProgramName() { @@ -193,6 +185,7 @@ public void SetProgramName() PythonEngine.ProgramName = programNameBackup; } + [Ignore("Only works if we can shutdown and re-init the interpreter")] [Test] public void SetPythonPath() { diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index a248b6a1f..91a412749 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPythonException { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestMessage() { diff --git a/src/embed_tests/TestRuntime.cs b/src/embed_tests/TestRuntime.cs index 77696fd96..88cef1557 100644 --- a/src/embed_tests/TestRuntime.cs +++ b/src/embed_tests/TestRuntime.cs @@ -5,6 +5,7 @@ namespace Python.EmbeddingTest { + [Ignore("Only works if we can shutdown and re-initialize the Python runtime")] public class TestRuntime { [OneTimeSetUp] diff --git a/src/embed_tests/pyimport.cs b/src/embed_tests/pyimport.cs index 551df1915..c774af345 100644 --- a/src/embed_tests/pyimport.cs +++ b/src/embed_tests/pyimport.cs @@ -42,7 +42,8 @@ public void SetUp() [OneTimeTearDown] public void Dispose() { - // TODO Undo the above + using var _ = Py.GIL(); + Py.Import("sys").GetAttr("path").InvokeMethod("remove", new PyString(TestPath)); } /// diff --git a/src/embed_tests/pyinitialize.cs b/src/embed_tests/pyinitialize.cs index cde463e61..dbb8f0bbe 100644 --- a/src/embed_tests/pyinitialize.cs +++ b/src/embed_tests/pyinitialize.cs @@ -2,9 +2,9 @@ using NUnit.Framework; using Python.Runtime; -#if false namespace Python.EmbeddingTest { + [Ignore("Only works if we can re-initialize the Python engine")] public class PyInitializeTest { /// @@ -156,4 +156,3 @@ public void ShutdownHandlers() public class ImportClassShutdownRefcountClass { } } -#endif \ No newline at end of file From 595c5fb2ccc6036e45c1f09846c20e3d0730e6a5 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 26 Oct 2025 18:50:59 +0100 Subject: [PATCH 120/194] Use python -m pytest, path seemingly not properly updated --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ca2562d04..ebf84bc77 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -78,14 +78,14 @@ jobs: - name: Python Tests (Mono) if: ${{ matrix.os.category != 'windows' }} - run: pytest --runtime mono + run: python -m pytest --runtime mono - name: Python Tests (.NET Core) - run: pytest --runtime coreclr + run: python -m pytest --runtime coreclr - name: Python Tests (.NET Framework) if: ${{ matrix.os.category == 'windows' }} - run: pytest --runtime netfx + run: python -m pytest --runtime netfx - name: Python tests run from .NET run: dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/ From 9bd783995243cd34fd33a7d399784a5561ab8ffe Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 26 Oct 2025 18:52:04 +0100 Subject: [PATCH 121/194] Remove unused architecture from uv env activation --- .github/workflows/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ebf84bc77..6c5d84dcb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,7 +58,6 @@ jobs: - name: Set up Python ${{ matrix.python }} uses: astral-sh/setup-uv@v7 with: - architecture: ${{ matrix.os.platform }} python-version: ${{ matrix.python }} activate-environment: true enable-cache: true From 5272e346944e87ccc0ddcfd2a4f17a4b15f3883e Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 26 Oct 2025 18:54:21 +0100 Subject: [PATCH 122/194] Synchronize the environment --- .github/workflows/main.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6c5d84dcb..286d43559 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,12 +58,17 @@ jobs: - name: Set up Python ${{ matrix.python }} uses: astral-sh/setup-uv@v7 with: + architecture: ${{ matrix.os.platform }} python-version: ${{ matrix.python }} + cache-python: true activate-environment: true enable-cache: true - name: Synchronize the virtual environment - run: uv sync + run: uv sync --managed-python + + - name: Show pyvenv.cfg + run: cat .venv/pyvenv.cfg - name: Embedding tests (Mono/.NET Framework) run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net472 --logger "console;verbosity=detailed" src/embed_tests/ @@ -77,14 +82,14 @@ jobs: - name: Python Tests (Mono) if: ${{ matrix.os.category != 'windows' }} - run: python -m pytest --runtime mono + run: pytest --runtime mono - name: Python Tests (.NET Core) - run: python -m pytest --runtime coreclr + run: pytest --runtime coreclr - name: Python Tests (.NET Framework) if: ${{ matrix.os.category == 'windows' }} - run: python -m pytest --runtime netfx + run: pytest --runtime netfx - name: Python tests run from .NET run: dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/ From bd33b3fe934971650263026a90bee5656ba1b017 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 7 Dec 2025 15:12:32 +0100 Subject: [PATCH 123/194] Include the probed PythonDLL value in the exception --- src/runtime/Runtime.Delegates.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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); } } From a38eddcd71375a7478aed1a650c8d15245ab4d8b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 7 Dec 2025 23:11:00 +0100 Subject: [PATCH 124/194] Use the actual pytest runner --- .github/workflows/main.yml | 4 ++ Directory.Build.props | 1 + .../Python.PythonTestsRunner.csproj | 8 ++++ src/python_tests_runner/PythonTestRunner.cs | 47 ++++++------------- src/runtime/Util/PythonEnvironment.cs | 2 +- 5 files changed, 29 insertions(+), 33 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 286d43559..53c0934ae 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -92,4 +92,8 @@ jobs: run: pytest --runtime netfx - name: Python tests run from .NET + # For some reason, it won't find pytest on the Windows + 3.10 + # combination, which hints that it does not handle the venv properly in + # this combination. + if: ${{ matrix.os.category != 'windows' || matrix.python != '3.10' }} run: dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/ diff --git a/Directory.Build.props b/Directory.Build.props index 5f8157f2e..85e4039b9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,6 +9,7 @@ $([System.IO.File]::ReadAllText("$(MSBuildThisFileDirectory)version.txt").Trim()) $(FullVersion.Split('-', 2)[0]) $(FullVersion.Split('-', 2)[1]) + $(MSBuildThisFileDirectory) diff --git a/src/python_tests_runner/Python.PythonTestsRunner.csproj b/src/python_tests_runner/Python.PythonTestsRunner.csproj index e55c1af37..5fc55d158 100644 --- a/src/python_tests_runner/Python.PythonTestsRunner.csproj +++ b/src/python_tests_runner/Python.PythonTestsRunner.csproj @@ -23,4 +23,12 @@ + + + + diff --git a/src/python_tests_runner/PythonTestRunner.cs b/src/python_tests_runner/PythonTestRunner.cs index f97cc5aec..3df22ec2e 100644 --- a/src/python_tests_runner/PythonTestRunner.cs +++ b/src/python_tests_runner/PythonTestRunner.cs @@ -8,22 +8,29 @@ using NUnit.Framework; using Python.Runtime; -using Python.Test; namespace Python.PythonTestsRunner { public class PythonTestRunner { + string OriginalDirectory; + [OneTimeSetUp] public void SetUp() { PythonEngine.Initialize(); + OriginalDirectory = Environment.CurrentDirectory; + + var codeDir = File.ReadAllText("tests_location.txt").Trim(); + TestContext.Progress.WriteLine($"Changing working directory to {codeDir}"); + Environment.CurrentDirectory = codeDir; } [OneTimeTearDown] public void Dispose() { PythonEngine.Shutdown(); + Environment.CurrentDirectory = OriginalDirectory; } /// @@ -46,39 +53,15 @@ static IEnumerable PythonTestCases() [TestCaseSource(nameof(PythonTestCases))] public void RunPythonTest(string testFile, string testName) { - // Find the tests directory - string folder = typeof(PythonTestRunner).Assembly.Location; - while (Path.GetFileName(folder) != "src") + using dynamic pytest = Py.Import("pytest"); + + using var args = new PyList(); + args.Append(new PyString($"{testFile}.py::{testName}")); + int res = pytest.main(args); + if (res != 0) { - folder = Path.GetDirectoryName(folder); + Assert.Fail($"Python test {testFile}.{testName} failed"); } - folder = Path.Combine(folder, "..", "tests"); - string path = Path.Combine(folder, testFile + ".py"); - if (!File.Exists(path)) throw new FileNotFoundException("Cannot find test file", path); - - // We could use 'import' below, but importlib gives more helpful error messages than 'import' - // https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly - // Because the Python tests sometimes have relative imports, the module name must be inside the tests package - PythonEngine.Exec($@" -import sys -import os -sys.path.append(os.path.dirname(r'{folder}')) -sys.path.append(os.path.join(r'{folder}', 'fixtures')) -import clr -clr.AddReference('Python.Test') -import tests -module_name = 'tests.{testFile}' -file_path = r'{path}' -import importlib.util -spec = importlib.util.spec_from_file_location(module_name, file_path) -module = importlib.util.module_from_spec(spec) -sys.modules[module_name] = module -try: - spec.loader.exec_module(module) -except ImportError as error: - raise ImportError(str(error) + ' when sys.path=' + os.pathsep.join(sys.path)) -module.{testName}() -"); } } } diff --git a/src/runtime/Util/PythonEnvironment.cs b/src/runtime/Util/PythonEnvironment.cs index 701db3c93..b1ebc7fa5 100644 --- a/src/runtime/Util/PythonEnvironment.cs +++ b/src/runtime/Util/PythonEnvironment.cs @@ -161,7 +161,7 @@ private static Dictionary TryParse(string venvCfg) private static string ProgramNameFromPath(string path) { - if (Runtime.IsWindows) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return Path.Combine(path, "Scripts", "python.exe"); } From 2f24a35d9912df97a9463fdc011b6fc7d8d1d3e7 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 8 Dec 2025 10:34:50 +0100 Subject: [PATCH 125/194] Move tests that require reinit and only run on .NET Framework --- .../NeedsReinit/StopAndRestartEngine.cs | 28 ++++ .../{ => NeedsReinit}/TestDomainReload.cs | 7 +- .../TestPyInitialize.cs} | 6 +- .../NeedsReinit/TestPythonEngineProperties.cs | 133 ++++++++++++++++++ .../{ => NeedsReinit}/TestRuntime.cs | 16 +-- src/embed_tests/Python.EmbeddingTest.csproj | 5 + src/embed_tests/TestPythonEngineProperties.cs | 127 ----------------- 7 files changed, 177 insertions(+), 145 deletions(-) create mode 100644 src/embed_tests/NeedsReinit/StopAndRestartEngine.cs rename src/embed_tests/{ => NeedsReinit}/TestDomainReload.cs (99%) rename src/embed_tests/{pyinitialize.cs => NeedsReinit/TestPyInitialize.cs} (97%) create mode 100644 src/embed_tests/NeedsReinit/TestPythonEngineProperties.cs rename src/embed_tests/{ => NeedsReinit}/TestRuntime.cs (92%) 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 97% rename from src/embed_tests/pyinitialize.cs rename to src/embed_tests/NeedsReinit/TestPyInitialize.cs index dbb8f0bbe..1ef4127b8 100644 --- a/src/embed_tests/pyinitialize.cs +++ b/src/embed_tests/NeedsReinit/TestPyInitialize.cs @@ -2,10 +2,10 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest.NeedsReinit { - [Ignore("Only works if we can re-initialize the Python engine")] - public class PyInitializeTest + [Category("NeedsReinit")] + public class TestPyInitialize : StopAndRestartEngine { /// /// Tests issue with multiple simple Initialize/Shutdowns. 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 92% rename from src/embed_tests/TestRuntime.cs rename to src/embed_tests/NeedsReinit/TestRuntime.cs index 88cef1557..193bf57d3 100644 --- a/src/embed_tests/TestRuntime.cs +++ b/src/embed_tests/NeedsReinit/TestRuntime.cs @@ -3,21 +3,11 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest.NeedsReinit { - [Ignore("Only works if we can shutdown and re-initialize the Python runtime")] - 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/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index eb2048c76..36b9a31f9 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -14,6 +14,11 @@ + + + + + $(DefineConstants);$(ConfiguredConstants) diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs index c6b4a2024..485931cfb 100644 --- a/src/embed_tests/TestPythonEngineProperties.cs +++ b/src/embed_tests/TestPythonEngineProperties.cs @@ -96,132 +96,5 @@ public static void GetPythonHomeDefault() Assert.AreEqual(envPythonHome, enginePythonHome); } - - [Ignore("Only works if we can shutdown and re-init the interpreter")] - [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; - } - - [Ignore("Only works if we can shutdown and re-init the interpreter")] - [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(); - } - - [Ignore("Only works if we can shutdown and re-init the interpreter")] - [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; - } - - [Ignore("Only works if we can shutdown and re-init the interpreter")] - [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(); - } } } From 9a3c04e3520d68d38d0fef3d0cf189bfc744f0b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:22:17 +0100 Subject: [PATCH 126/194] Bump NUnit3TestAdapter from 5.2.0 to 6.0.0 (#2667) --- updated-dependencies: - dependency-name: NUnit3TestAdapter dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: NUnit3TestAdapter dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/embed_tests/Python.EmbeddingTest.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 36b9a31f9..28076120a 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -29,7 +29,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive From fd7c7e1cbd8e1d7bdb2ec2423c3cf9f95a3abed1 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 9 Dec 2025 08:01:57 +0100 Subject: [PATCH 127/194] Fix line endings --- doc/make.bat | 70 +++++++------- src/runtime/Codecs/IterableDecoder.cs | 110 +++++++++++----------- src/runtime/Codecs/ListDecoder.cs | 100 ++++++++++---------- src/runtime/Codecs/SequenceDecoder.cs | 96 ++++++++++---------- src/runtime/Types/ManagedTypes.cd | 2 +- tests/domain_tests/App.config | 2 +- tests/test_mp_length.py | 126 +++++++++++++------------- 7 files changed, 253 insertions(+), 253 deletions(-) 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/src/runtime/Codecs/IterableDecoder.cs b/src/runtime/Codecs/IterableDecoder.cs index bcc2eca01..b864850d5 100644 --- a/src/runtime/Codecs/IterableDecoder.cs +++ b/src/runtime/Codecs/IterableDecoder.cs @@ -1,55 +1,55 @@ -using System; -using System.Collections.Generic; - -namespace Python.Runtime.Codecs -{ - public class IterableDecoder : IPyObjectDecoder - { - internal static bool IsIterable(Type targetType) - { - //if it is a plain IEnumerable, we can decode it using sequence protocol. - if (targetType == typeof(System.Collections.IEnumerable)) - return true; - - if (!targetType.IsGenericType) - return false; - - return targetType.GetGenericTypeDefinition() == typeof(IEnumerable<>); - } - - internal static bool IsIterable(PyType objectType) - { - return objectType.HasAttr("__iter__"); - } - - public bool CanDecode(PyType objectType, Type targetType) - { - return IsIterable(objectType) && IsIterable(targetType); - } - - public bool TryDecode(PyObject pyObj, out T value) - { - //first see if T is a plan IEnumerable - if (typeof(T) == typeof(System.Collections.IEnumerable)) - { - object enumerable = new CollectionWrappers.IterableWrapper(pyObj); - value = (T)enumerable; - return true; - } - - var elementType = typeof(T).GetGenericArguments()[0]; - var collectionType = typeof(CollectionWrappers.IterableWrapper<>).MakeGenericType(elementType); - - var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); - value = (T)instance; - return true; - } - - public static IterableDecoder Instance { get; } = new IterableDecoder(); - - public static void Register() - { - PyObjectConversions.RegisterDecoder(Instance); - } - } -} +using System; +using System.Collections.Generic; + +namespace Python.Runtime.Codecs +{ + public class IterableDecoder : IPyObjectDecoder + { + internal static bool IsIterable(Type targetType) + { + //if it is a plain IEnumerable, we can decode it using sequence protocol. + if (targetType == typeof(System.Collections.IEnumerable)) + return true; + + if (!targetType.IsGenericType) + return false; + + return targetType.GetGenericTypeDefinition() == typeof(IEnumerable<>); + } + + internal static bool IsIterable(PyType objectType) + { + return objectType.HasAttr("__iter__"); + } + + public bool CanDecode(PyType objectType, Type targetType) + { + return IsIterable(objectType) && IsIterable(targetType); + } + + public bool TryDecode(PyObject pyObj, out T value) + { + //first see if T is a plan IEnumerable + if (typeof(T) == typeof(System.Collections.IEnumerable)) + { + object enumerable = new CollectionWrappers.IterableWrapper(pyObj); + value = (T)enumerable; + return true; + } + + var elementType = typeof(T).GetGenericArguments()[0]; + var collectionType = typeof(CollectionWrappers.IterableWrapper<>).MakeGenericType(elementType); + + var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); + value = (T)instance; + return true; + } + + public static IterableDecoder Instance { get; } = new IterableDecoder(); + + public static void Register() + { + PyObjectConversions.RegisterDecoder(Instance); + } + } +} diff --git a/src/runtime/Codecs/ListDecoder.cs b/src/runtime/Codecs/ListDecoder.cs index 70ff33aaa..5da82851f 100644 --- a/src/runtime/Codecs/ListDecoder.cs +++ b/src/runtime/Codecs/ListDecoder.cs @@ -1,50 +1,50 @@ -using System; -using System.Collections.Generic; - -namespace Python.Runtime.Codecs -{ - public class ListDecoder : IPyObjectDecoder - { - private static bool IsList(Type targetType) - { - if (!targetType.IsGenericType) - return false; - - return targetType.GetGenericTypeDefinition() == typeof(IList<>); - } - - private static bool IsList(PyType objectType) - { - //TODO accept any python object that implements the sequence and list protocols - //must implement sequence protocol to fully implement list protocol - //if (!SequenceDecoder.IsSequence(objectType)) return false; - - //returns wheter the type is a list. - return PythonReferenceComparer.Instance.Equals(objectType, Runtime.PyListType); - } - - public bool CanDecode(PyType objectType, Type targetType) - { - return IsList(objectType) && IsList(targetType); - } - - public bool TryDecode(PyObject pyObj, out T value) - { - if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); - - var elementType = typeof(T).GetGenericArguments()[0]; - Type collectionType = typeof(CollectionWrappers.ListWrapper<>).MakeGenericType(elementType); - - var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); - value = (T)instance; - return true; - } - - public static ListDecoder Instance { get; } = new ListDecoder(); - - public static void Register() - { - PyObjectConversions.RegisterDecoder(Instance); - } - } -} +using System; +using System.Collections.Generic; + +namespace Python.Runtime.Codecs +{ + public class ListDecoder : IPyObjectDecoder + { + private static bool IsList(Type targetType) + { + if (!targetType.IsGenericType) + return false; + + return targetType.GetGenericTypeDefinition() == typeof(IList<>); + } + + private static bool IsList(PyType objectType) + { + //TODO accept any python object that implements the sequence and list protocols + //must implement sequence protocol to fully implement list protocol + //if (!SequenceDecoder.IsSequence(objectType)) return false; + + //returns wheter the type is a list. + return PythonReferenceComparer.Instance.Equals(objectType, Runtime.PyListType); + } + + public bool CanDecode(PyType objectType, Type targetType) + { + return IsList(objectType) && IsList(targetType); + } + + public bool TryDecode(PyObject pyObj, out T value) + { + if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); + + var elementType = typeof(T).GetGenericArguments()[0]; + Type collectionType = typeof(CollectionWrappers.ListWrapper<>).MakeGenericType(elementType); + + var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); + value = (T)instance; + return true; + } + + public static ListDecoder Instance { get; } = new ListDecoder(); + + public static void Register() + { + PyObjectConversions.RegisterDecoder(Instance); + } + } +} diff --git a/src/runtime/Codecs/SequenceDecoder.cs b/src/runtime/Codecs/SequenceDecoder.cs index a539297cd..c5ded4958 100644 --- a/src/runtime/Codecs/SequenceDecoder.cs +++ b/src/runtime/Codecs/SequenceDecoder.cs @@ -1,52 +1,52 @@ -using System; -using System.Collections.Generic; - -namespace Python.Runtime.Codecs -{ - public class SequenceDecoder : IPyObjectDecoder - { - internal static bool IsSequence(Type targetType) - { - if (!targetType.IsGenericType) - return false; - - return targetType.GetGenericTypeDefinition() == typeof(ICollection<>); - } - - internal static bool IsSequence(PyType objectType) - { - //must implement iterable protocol to fully implement sequence protocol - if (!IterableDecoder.IsIterable(objectType)) return false; - +using System; +using System.Collections.Generic; + +namespace Python.Runtime.Codecs +{ + public class SequenceDecoder : IPyObjectDecoder + { + internal static bool IsSequence(Type targetType) + { + if (!targetType.IsGenericType) + return false; + + return targetType.GetGenericTypeDefinition() == typeof(ICollection<>); + } + + internal static bool IsSequence(PyType objectType) + { + //must implement iterable protocol to fully implement sequence protocol + if (!IterableDecoder.IsIterable(objectType)) return false; + //returns wheter it implements the sequence protocol //according to python doc this needs to exclude dict subclasses //but I don't know how to look for that given the objectType //rather than the instance. - return objectType.HasAttr("__getitem__"); - } - - public bool CanDecode(PyType objectType, Type targetType) - { - return IsSequence(objectType) && IsSequence(targetType); - } - - public bool TryDecode(PyObject pyObj, out T value) - { - if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); - - var elementType = typeof(T).GetGenericArguments()[0]; - Type collectionType = typeof(CollectionWrappers.SequenceWrapper<>).MakeGenericType(elementType); - - var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); - value = (T)instance; - return true; - } - - public static SequenceDecoder Instance { get; } = new SequenceDecoder(); - - public static void Register() - { - PyObjectConversions.RegisterDecoder(Instance); - } - } -} + return objectType.HasAttr("__getitem__"); + } + + public bool CanDecode(PyType objectType, Type targetType) + { + return IsSequence(objectType) && IsSequence(targetType); + } + + public bool TryDecode(PyObject pyObj, out T value) + { + if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); + + var elementType = typeof(T).GetGenericArguments()[0]; + Type collectionType = typeof(CollectionWrappers.SequenceWrapper<>).MakeGenericType(elementType); + + var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); + value = (T)instance; + return true; + } + + public static SequenceDecoder Instance { get; } = new SequenceDecoder(); + + public static void Register() + { + PyObjectConversions.RegisterDecoder(Instance); + } + } +} diff --git a/src/runtime/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/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/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 From 08e1b3743f0f816733bf7d26dd7b24b1caa69d73 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 9 Dec 2025 08:03:16 +0100 Subject: [PATCH 128/194] Add previous commit to ignore file --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs 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 From 1b43410954936d0062a5a98543206e833cd057ca Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 3 Feb 2026 19:29:30 +0000 Subject: [PATCH 129/194] Merge pull request #2684 from losttech/net10 Switch to .NET SDK 10 --- .github/workflows/main.yml | 4 ++-- src/embed_tests/Python.EmbeddingTest.csproj | 2 +- src/python_tests_runner/Python.PythonTestsRunner.csproj | 2 +- src/runtime/AssemblyManager.cs | 5 +++++ src/testing/Python.Test.csproj | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 53c0934ae..76c7a0d13 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -53,7 +53,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.x' + dotnet-version: '10.0.x' - name: Set up Python ${{ matrix.python }} uses: astral-sh/setup-uv@v7 @@ -77,7 +77,7 @@ jobs: MONO_THREADS_SUSPEND: preemptive # https://github.com/mono/mono/issues/21466 - name: Embedding tests (.NET Core) - run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net8.0 --logger "console;verbosity=detailed" src/embed_tests/ + run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net10.0 --logger "console;verbosity=detailed" src/embed_tests/ if: always() - name: Python Tests (Mono) diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 28076120a..b3e7fe86e 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -1,7 +1,7 @@ - net472;net8.0 + net472;net10.0 ..\pythonnet.snk true diff --git a/src/python_tests_runner/Python.PythonTestsRunner.csproj b/src/python_tests_runner/Python.PythonTestsRunner.csproj index 5fc55d158..80e8c0071 100644 --- a/src/python_tests_runner/Python.PythonTestsRunner.csproj +++ b/src/python_tests_runner/Python.PythonTestsRunner.csproj @@ -1,7 +1,7 @@ - net472;net8.0 + net472;net10.0 diff --git a/src/runtime/AssemblyManager.cs b/src/runtime/AssemblyManager.cs index 82658bf50..dcc5aa2f0 100644 --- a/src/runtime/AssemblyManager.cs +++ b/src/runtime/AssemblyManager.cs @@ -123,6 +123,11 @@ private static void AssemblyLoadHandler(object ob, AssemblyLoadEventArgs args) internal static AssemblyName? TryParseAssemblyName(string name) { + // workaround for https://github.com/dotnet/runtime/issues/123951 + if (name.IndexOfAny(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }) >= 0) + { + return null; + } try { return new AssemblyName(name); diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index b7ba6cd4e..c2455b1a5 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -1,6 +1,6 @@ - netstandard2.0;net8.0 + netstandard2.0;net10.0 true true ..\pythonnet.snk From 1a1a9ab5fc05a4a752f5ff0b1bb7962a26f409eb Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 16 Feb 2026 09:20:49 +0100 Subject: [PATCH 130/194] Drop unused requirements.txt --- requirements.txt | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 requirements.txt 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.* From 4ca65d64756235052789363e5582a5a865059294 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 09:55:05 +0100 Subject: [PATCH 131/194] Bump astral-sh/setup-uv from 6 to 7 (#2656) Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 6 to 7. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v6...v7) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c700af49..f2611e77d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,7 +56,7 @@ jobs: dotnet-version: '8.0.x' - name: Set up Python ${{ matrix.python }} - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: architecture: ${{ matrix.os.platform }} python-version: ${{ matrix.python }} From 0856eb42a0b27ba584a5f688f43abfe227a3d07a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:19:13 +0100 Subject: [PATCH 132/194] Bump actions/checkout from 5 to 6 (#2663) Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 2 +- .github/workflows/nuget-preview.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2b1dda732..3937d85e0 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@v5 + - uses: actions/checkout@v6 - name: Doxygen Action uses: mattnotmitt/doxygen-action@1.12.0 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f2611e77d..ca2562d04 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,7 +48,7 @@ jobs: mono-version: latest - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5 diff --git a/.github/workflows/nuget-preview.yml b/.github/workflows/nuget-preview.yml index 4d1476560..6de97d50e 100644 --- a/.github/workflows/nuget-preview.yml +++ b/.github/workflows/nuget-preview.yml @@ -21,7 +21,7 @@ jobs: echo "DATE_VER=$(date "+%Y-%m-%d")" >> $GITHUB_ENV - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup .NET uses: actions/setup-dotnet@v5 From a7d0842c1bff9b422ebed5766261d980549a7867 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 22 Oct 2025 22:11:27 +0200 Subject: [PATCH 133/194] Only init/shutdown Python once --- Directory.Build.props | 2 +- src/embed_tests/CallableObject.cs | 25 +++++---- src/embed_tests/ClassManagerTests.cs | 12 ---- src/embed_tests/CodecGroups.cs | 62 ++++++++++----------- src/embed_tests/Codecs.cs | 12 ---- src/embed_tests/{dynamic.cs => Dynamic.cs} | 12 ---- src/embed_tests/ExtensionTypes.cs | 14 +---- src/embed_tests/GlobalTestsSetup.cs | 1 + src/embed_tests/Inheritance.cs | 52 ++++++++++------- src/embed_tests/Inspect.cs | 14 +---- src/embed_tests/Modules.cs | 48 ++++++++-------- src/embed_tests/NumPyTests.cs | 5 +- src/embed_tests/Python.EmbeddingTest.csproj | 4 ++ src/embed_tests/References.cs | 16 +----- src/embed_tests/TestCallbacks.cs | 12 +--- src/embed_tests/TestConverter.cs | 46 ++++++--------- src/embed_tests/TestCustomMarshal.cs | 18 +----- src/embed_tests/TestFinalizer.cs | 27 +++++---- src/embed_tests/TestGILState.cs | 12 ---- src/embed_tests/TestInstanceWrapping.cs | 16 +----- src/embed_tests/TestInterrupt.cs | 12 ++-- src/embed_tests/TestNamedArguments.cs | 16 +----- src/embed_tests/TestNativeTypeOffset.cs | 12 ---- src/embed_tests/TestOperator.cs | 3 +- src/embed_tests/TestPyBuffer.cs | 21 ++++--- src/embed_tests/TestPyFloat.cs | 32 ++++------- src/embed_tests/pyimport.cs | 18 +++--- src/embed_tests/pyinitialize.cs | 6 +- src/embed_tests/pyrunstring.cs | 20 ++----- 29 files changed, 193 insertions(+), 357 deletions(-) rename src/embed_tests/{dynamic.cs => Dynamic.cs} (95%) diff --git a/Directory.Build.props b/Directory.Build.props index 0288a4d64..5f8157f2e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ 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]) 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..cecc6825d 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -8,18 +8,6 @@ namespace Python.EmbeddingTest { public class Codecs { - [SetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [TearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TupleConversionsGeneric() { 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/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..fc9b8f398 100644 --- a/src/embed_tests/Modules.cs +++ b/src/embed_tests/Modules.cs @@ -50,7 +50,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 +66,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 +83,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 +102,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 +123,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 +134,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 +219,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 +235,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 +266,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 +289,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 +345,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 +420,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 +434,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 +448,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/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 da6799912..cb3b52052 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -20,6 +20,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + all runtime; build; native; contentfiles; analyzers; buildtransitive 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/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(); - Assert.IsTrue(PythonReferenceComparer.Instance.Equals(i, ni)); + Assert.That(PythonReferenceComparer.Instance.Equals(i, ni), Is.True); } [Test] @@ -158,7 +146,7 @@ public void ToPyList() list.Append("hello".ToPython()); list.Append("world".ToPython()); var back = list.ToPython().As(); - Assert.AreEqual(list.Length(), back.Length()); + Assert.That(back.Length(), Is.EqualTo(list.Length())); } [Test] @@ -182,7 +170,7 @@ public void RawPyObjectProxy() const string handlePropertyName = nameof(PyObject.Handle); #pragma warning restore CS0612 // Type or member is obsolete var proxiedHandle = pyObjectProxy.GetAttr(handlePropertyName).As(); - Assert.AreEqual(pyObject.DangerousGetAddressOrNull(), proxiedHandle); + Assert.That(proxiedHandle, Is.EqualTo(pyObject.DangerousGetAddressOrNull())); } [Test] @@ -191,7 +179,7 @@ public void GenericToPython() int i = 42; var pyObject = i.ToPythonAs(); var type = pyObject.GetPythonType(); - Assert.AreEqual(nameof(IConvertible), type.Name); + Assert.That(type.Name, Is.EqualTo(nameof(IConvertible))); } // regression for https://github.com/pythonnet/pythonnet/issues/451 @@ -207,7 +195,7 @@ class PyGetListImpl(test.GetListImpl): var pyImpl = scope.Get("PyGetListImpl"); dynamic inst = pyImpl.Invoke(); List result = inst.GetList(); - CollectionAssert.AreEqual(new[] { "testing" }, result); + Assert.That(result, Is.EqualTo(new[] { "testing" }).AsCollection); } } diff --git a/src/embed_tests/TestCustomMarshal.cs b/src/embed_tests/TestCustomMarshal.cs index 312863d0c..3bcb6b2e6 100644 --- a/src/embed_tests/TestCustomMarshal.cs +++ b/src/embed_tests/TestCustomMarshal.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestCustomMarshal { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public static void GetManagedStringTwice() { @@ -27,9 +15,9 @@ public static void GetManagedStringTwice() string s1 = Runtime.Runtime.GetManagedString(op.BorrowOrThrow()); string s2 = Runtime.Runtime.GetManagedString(op.Borrow()); - Assert.AreEqual(1, Runtime.Runtime.Refcount32(op.Borrow())); - Assert.AreEqual(expected, s1); - Assert.AreEqual(expected, s2); + Assert.That(Runtime.Runtime.Refcount32(op.Borrow()), Is.EqualTo(1)); + Assert.That(s1, Is.EqualTo(expected)); + Assert.That(s2, Is.EqualTo(expected)); } } } diff --git a/src/embed_tests/TestFinalizer.cs b/src/embed_tests/TestFinalizer.cs index b748a2244..9ef3e25b6 100644 --- a/src/embed_tests/TestFinalizer.cs +++ b/src/embed_tests/TestFinalizer.cs @@ -17,7 +17,6 @@ public class TestFinalizer public void SetUp() { _oldThreshold = Finalizer.Instance.Threshold; - PythonEngine.Initialize(); Exceptions.Clear(); } @@ -25,7 +24,6 @@ public void SetUp() public void TearDown() { Finalizer.Instance.Threshold = _oldThreshold; - PythonEngine.Shutdown(); } private static void FullGCCollect() @@ -38,7 +36,7 @@ private static void FullGCCollect() [Obsolete("GC tests are not guaranteed")] public void CollectBasicObject() { - Assert.IsTrue(Finalizer.Instance.Enable); + Assert.That(Finalizer.Instance.Enable, Is.True); Finalizer.Instance.Threshold = 1; bool called = false; @@ -49,7 +47,7 @@ public void CollectBasicObject() called = true; }; - Assert.IsFalse(called, "The event handler was called before it was installed"); + Assert.That(called, Is.False, "The event handler was called before it was installed"); Finalizer.Instance.BeforeCollect += handler; IntPtr pyObj = MakeAGarbage(out var shortWeak, out var longWeak); @@ -60,10 +58,10 @@ public void CollectBasicObject() "The referenced object is alive although it should have been collected", shortWeak ); - Assert.IsTrue( + Assert.That( longWeak.IsAlive, - "The reference object is not alive although it should still be", - longWeak + Is.True, + $"The reference object is not alive although it should still be" ); { @@ -83,7 +81,7 @@ public void CollectBasicObject() { Finalizer.Instance.BeforeCollect -= handler; } - Assert.IsTrue(called, "The event handler was not called during finalization"); + Assert.That(called, Is.True, "The event handler was not called during finalization"); Assert.GreaterOrEqual(objectCount, 1); } @@ -93,10 +91,11 @@ public void CollectOnShutdown() { IntPtr op = MakeAGarbage(out var shortWeak, out var longWeak); FullGCCollect(); - Assert.IsFalse(shortWeak.IsAlive); + Assert.That(shortWeak.IsAlive, Is.False); List garbage = Finalizer.Instance.GetCollectedObjects(); Assert.IsNotEmpty(garbage, "The garbage object should be collected"); - Assert.IsTrue(garbage.Contains(op), + Assert.That(garbage.Contains(op), + Is.True, "Garbage should contains the collected object"); PythonEngine.Shutdown(); @@ -133,7 +132,7 @@ private static IntPtr MakeAGarbage(out WeakReference shortWeak, out WeakReferenc handle = obj.Handle; }); garbageGen.Start(); - Assert.IsTrue(garbageGen.Join(TimeSpan.FromSeconds(5)), "Garbage creation timed out"); + Assert.That(garbageGen.Join(TimeSpan.FromSeconds(5)), Is.True, "Garbage creation timed out"); shortWeak = @short; longWeak = @long; return handle; @@ -209,8 +208,8 @@ public void ValidateRefCount() Finalizer.IncorrectRefCntHandler handler = (s, e) => { called = true; - Assert.AreEqual(ptr, e.Handle); - Assert.AreEqual(2, e.ImpactedObjects.Count); + Assert.That(e.Handle, Is.EqualTo(ptr)); + Assert.That(e.ImpactedObjects.Count, Is.EqualTo(2)); // Fix for this test, don't do this on general environment #pragma warning disable CS0618 // Type or member is obsolete Runtime.Runtime.XIncref(e.Reference); @@ -223,7 +222,7 @@ public void ValidateRefCount() ptr = CreateStringGarbage(); FullGCCollect(); Assert.Throws(() => Finalizer.Instance.Collect()); - Assert.IsTrue(called); + Assert.That(called, Is.True); } finally { diff --git a/src/embed_tests/TestGILState.cs b/src/embed_tests/TestGILState.cs index bf6f02dc6..ba2ab500f 100644 --- a/src/embed_tests/TestGILState.cs +++ b/src/embed_tests/TestGILState.cs @@ -17,17 +17,5 @@ public void CanDisposeMultipleTimes() gilState.Dispose(); } } - - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } } } diff --git a/src/embed_tests/TestInstanceWrapping.cs b/src/embed_tests/TestInstanceWrapping.cs index 0a441c823..c6996fd87 100644 --- a/src/embed_tests/TestInstanceWrapping.cs +++ b/src/embed_tests/TestInstanceWrapping.cs @@ -7,18 +7,6 @@ namespace Python.EmbeddingTest { public class TestInstanceWrapping { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - // regression test for https://github.com/pythonnet/pythonnet/issues/811 [Test] public void OverloadResolution_UnknownToObject() @@ -30,7 +18,7 @@ public void OverloadResolution_UnknownToObject() dynamic callWithSelf = PythonEngine.Eval("lambda o: o.ObjOrClass(object())"); callWithSelf(o); - Assert.AreEqual(Overloaded.Object, overloaded.Value); + Assert.That(overloaded.Value, Is.EqualTo(Overloaded.Object)); } } @@ -41,7 +29,7 @@ public void WeakRefIsNone_AfterObjectIsGone() var ub = new UriBuilder().ToPython(); using var weakref = makeref.Invoke(ub); ub.Dispose(); - Assert.IsTrue(weakref.Invoke().IsNone()); + Assert.That(weakref.Invoke().IsNone(), Is.True); } class Base {} diff --git a/src/embed_tests/TestInterrupt.cs b/src/embed_tests/TestInterrupt.cs index e6546adb2..d48f7c73b 100644 --- a/src/embed_tests/TestInterrupt.cs +++ b/src/embed_tests/TestInterrupt.cs @@ -15,7 +15,6 @@ public class TestInterrupt [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); // workaround for assert tlock.locked() warning threading = Py.Import("threading"); } @@ -24,7 +23,6 @@ public void SetUp() public void Dispose() { threading.Dispose(); - PythonEngine.Shutdown(); } [Test] @@ -50,9 +48,9 @@ public void PythonThreadIDStable() } PythonEngine.EndAllowThreads(threadState); - Assert.IsTrue(asyncCall.Wait(TimeSpan.FromSeconds(5)), "Async thread has not finished in time"); + Assert.That(asyncCall.Wait(TimeSpan.FromSeconds(5)), Is.True, "Async thread has not finished in time"); - Assert.AreEqual(pythonThreadID, pythonThreadID2); + Assert.That(pythonThreadID2, Is.EqualTo(pythonThreadID)); Assert.NotZero(pythonThreadID); } @@ -86,13 +84,13 @@ import time PythonEngine.EndAllowThreads(threadState); int interruptReturnValue = PythonEngine.Interrupt((ulong)Interlocked.Read(ref pythonThreadID)); - Assert.AreEqual(1, interruptReturnValue); + Assert.That(interruptReturnValue, Is.EqualTo(1)); threadState = PythonEngine.BeginAllowThreads(); - Assert.IsTrue(asyncCall.Wait(TimeSpan.FromSeconds(5)), "Async thread was not interrupted in time"); + Assert.That(asyncCall.Wait(TimeSpan.FromSeconds(5)), Is.True, "Async thread was not interrupted in time"); PythonEngine.EndAllowThreads(threadState); - Assert.AreEqual(0, asyncCall.Result); + Assert.That(asyncCall.Result, Is.EqualTo(0)); } } } diff --git a/src/embed_tests/TestNamedArguments.cs b/src/embed_tests/TestNamedArguments.cs index c86302038..16d7d5b8f 100644 --- a/src/embed_tests/TestNamedArguments.cs +++ b/src/embed_tests/TestNamedArguments.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestNamedArguments { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - /// /// Test named arguments support through Py.kw method /// @@ -27,7 +15,7 @@ public void TestKeywordArgs() dynamic a = CreateTestClass(); var result = (int)a.Test3(2, Py.kw("a4", 8)); - Assert.AreEqual(12, result); + Assert.That(result, Is.EqualTo(12)); } @@ -40,7 +28,7 @@ public void TestNamedArgs() dynamic a = CreateTestClass(); var result = (int)a.Test3(2, a4: 8); - Assert.AreEqual(12, result); + Assert.That(result, Is.EqualTo(12)); } diff --git a/src/embed_tests/TestNativeTypeOffset.cs b/src/embed_tests/TestNativeTypeOffset.cs index d692c24e6..61b6903c5 100644 --- a/src/embed_tests/TestNativeTypeOffset.cs +++ b/src/embed_tests/TestNativeTypeOffset.cs @@ -13,18 +13,6 @@ namespace Python.EmbeddingTest { public class TestNativeTypeOffset { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - /// /// Tests that installation has generated code for NativeTypeOffset and that it can be loaded. /// diff --git a/src/embed_tests/TestOperator.cs b/src/embed_tests/TestOperator.cs index 6bfb81bdb..ab71ed9b3 100644 --- a/src/embed_tests/TestOperator.cs +++ b/src/embed_tests/TestOperator.cs @@ -14,14 +14,13 @@ public class TestOperator [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); OwnIntCodec.Setup(); } [OneTimeTearDown] public void Dispose() { - PythonEngine.Shutdown(); + PyObjectConversions.Reset(); } // Mock Integer class to test math ops on non-native dotnet types diff --git a/src/embed_tests/TestPyBuffer.cs b/src/embed_tests/TestPyBuffer.cs index 1b4e28d12..89ddf9370 100644 --- a/src/embed_tests/TestPyBuffer.cs +++ b/src/embed_tests/TestPyBuffer.cs @@ -11,14 +11,13 @@ class TestPyBuffer [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); TupleCodec.Register(); } [OneTimeTearDown] public void Dispose() { - PythonEngine.Shutdown(); + PyObjectConversions.Reset(); } [Test] @@ -38,7 +37,7 @@ public void TestBufferWrite() } string result = pythonArray.InvokeMethod("decode", "utf-8".ToPython()).As(); - Assert.IsTrue(result == bufferTestString2); + Assert.That(result == bufferTestString2, Is.True); } [Test] @@ -58,7 +57,7 @@ public void TestBufferRead() } string result = new UTF8Encoding().GetString(managedArray); - Assert.IsTrue(result == " " + bufferTestString.Substring(1)); + Assert.That(result == " " + bufferTestString.Substring(1), Is.True); } [Test] @@ -67,8 +66,8 @@ public void ArrayHasBuffer() var array = new[,] {{1, 2}, {3,4}}; var memoryView = PythonEngine.Eval("memoryview"); var mem = memoryView.Invoke(array.ToPython()); - Assert.AreEqual(1, mem[(0, 0).ToPython()].As()); - Assert.AreEqual(array[1,0], mem[(1, 0).ToPython()].As()); + Assert.That(mem[(0, 0).ToPython()].As(), Is.EqualTo(1)); + Assert.That(mem[(1, 0).ToPython()].As(), Is.EqualTo(array[1, 0])); } [Test] @@ -77,14 +76,14 @@ public void RefCount() using var _ = Py.GIL(); using var arr = ByteArrayFromAsciiString("hello world! !$%&/()=?"); - Assert.AreEqual(1, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(1)); using (PyBuffer buf = arr.GetBuffer()) { - Assert.AreEqual(2, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(2)); } - Assert.AreEqual(1, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(1)); } [Test] @@ -99,7 +98,7 @@ public void Finalization() using var _ = Py.GIL(); using var arr = ByteArrayFromAsciiString("hello world! !$%&/()=?"); - Assert.AreEqual(1, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(1)); MakeBufAndLeak(arr); @@ -107,7 +106,7 @@ public void Finalization() GC.WaitForPendingFinalizers(); Finalizer.Instance.Collect(); - Assert.AreEqual(1, arr.Refcount); + Assert.That(arr.Refcount, Is.EqualTo(1)); } [Test] diff --git a/src/embed_tests/TestPyFloat.cs b/src/embed_tests/TestPyFloat.cs index 89e29e5fd..c6111f180 100644 --- a/src/embed_tests/TestPyFloat.cs +++ b/src/embed_tests/TestPyFloat.cs @@ -9,18 +9,6 @@ namespace Python.EmbeddingTest /// public class TestPyFloat { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void FloatCtor() { @@ -132,14 +120,14 @@ public void CompareTo() { var v = new PyFloat(42); - Assert.AreEqual(0, v.CompareTo(42f)); - Assert.AreEqual(0, v.CompareTo(42d)); + Assert.That(v.CompareTo(42f), Is.EqualTo(0)); + Assert.That(v.CompareTo(42d), Is.EqualTo(0)); - Assert.AreEqual(1, v.CompareTo(41f)); - Assert.AreEqual(1, v.CompareTo(41d)); + Assert.That(v.CompareTo(41f), Is.EqualTo(1)); + Assert.That(v.CompareTo(41d), Is.EqualTo(1)); - Assert.AreEqual(-1, v.CompareTo(43f)); - Assert.AreEqual(-1, v.CompareTo(43d)); + Assert.That(v.CompareTo(43f), Is.EqualTo(-1)); + Assert.That(v.CompareTo(43d), Is.EqualTo(-1)); } [Test] @@ -147,11 +135,11 @@ public void Equals() { var v = new PyFloat(42); - Assert.IsTrue(v.Equals(42f)); - Assert.IsTrue(v.Equals(42d)); + Assert.That(v.Equals(42f), Is.True); + Assert.That(v.Equals(42d), Is.True); - Assert.IsFalse(v.Equals(41f)); - Assert.IsFalse(v.Equals(41d)); + Assert.That(v.Equals(41f), Is.False); + Assert.That(v.Equals(41d), Is.False); } } } diff --git a/src/embed_tests/pyimport.cs b/src/embed_tests/pyimport.cs index b828d5315..551df1915 100644 --- a/src/embed_tests/pyimport.cs +++ b/src/embed_tests/pyimport.cs @@ -21,28 +21,28 @@ namespace Python.EmbeddingTest /// public class PyImportTest { + string TestPath; + [OneTimeSetUp] public void SetUp() { - PythonEngine.Initialize(); - /* Append the tests directory to sys.path * using reflection to circumvent the private * modifiers placed on most Runtime methods. */ - string testPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "fixtures"); - TestContext.Out.WriteLine(testPath); + TestPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "fixtures"); + TestContext.Out.WriteLine(TestPath); - using var str = Runtime.Runtime.PyString_FromString(testPath); - Assert.IsFalse(str.IsNull()); + using var str = Runtime.Runtime.PyString_FromString(TestPath); + Assert.That(str.IsNull(), Is.False); BorrowedReference path = Runtime.Runtime.PySys_GetObject("path"); - Assert.IsFalse(path.IsNull); + Assert.That(path.IsNull, Is.False); Runtime.Runtime.PyList_Append(path, str.Borrow()); } [OneTimeTearDown] public void Dispose() { - PythonEngine.Shutdown(); + // TODO Undo the above } /// @@ -89,7 +89,7 @@ public void BadAssembly() path = @"C:\Windows\System32\kernel32.dll"; } - Assert.IsTrue(File.Exists(path), $"Test DLL {path} does not exist!"); + Assert.That(File.Exists(path), Is.True, $"Test DLL {path} does not exist!"); string code = $@" import clr diff --git a/src/embed_tests/pyinitialize.cs b/src/embed_tests/pyinitialize.cs index 25dafb686..cde463e61 100644 --- a/src/embed_tests/pyinitialize.cs +++ b/src/embed_tests/pyinitialize.cs @@ -2,6 +2,7 @@ using NUnit.Framework; using Python.Runtime; +#if false namespace Python.EmbeddingTest { public class PyInitializeTest @@ -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])); } } } @@ -155,3 +156,4 @@ public void ShutdownHandlers() public class ImportClassShutdownRefcountClass { } } +#endif \ No newline at end of file diff --git a/src/embed_tests/pyrunstring.cs b/src/embed_tests/pyrunstring.cs index 57c133c00..fb1302800 100644 --- a/src/embed_tests/pyrunstring.cs +++ b/src/embed_tests/pyrunstring.cs @@ -6,26 +6,14 @@ namespace Python.EmbeddingTest { public class RunStringTest { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestRunSimpleString() { int aa = PythonEngine.RunSimpleString("import sys"); - Assert.AreEqual(0, aa); + Assert.That(aa, Is.EqualTo(0)); int bb = PythonEngine.RunSimpleString("import 1234"); - Assert.AreEqual(-1, bb); + Assert.That(bb, Is.EqualTo(-1)); } [Test] @@ -39,7 +27,7 @@ public void TestEval() object b = PythonEngine.Eval("sys.attr1 + a + 1", null, locals) .AsManagedObject(typeof(int)); - Assert.AreEqual(111, b); + Assert.That(b, Is.EqualTo(111)); } [Test] @@ -53,7 +41,7 @@ public void TestExec() PythonEngine.Exec("c = sys.attr1 + a + 1", null, locals); object c = locals.GetItem("c").AsManagedObject(typeof(int)); - Assert.AreEqual(111, c); + Assert.That(c, Is.EqualTo(111)); } [Test] From f6ff4314d5bd9e3ae1407d8a7d54443c37cf98ca Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 23 Oct 2025 11:47:03 +0200 Subject: [PATCH 134/194] Disable NUnit analyzer for now --- src/embed_tests/Python.EmbeddingTest.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index cb3b52052..eb2048c76 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -20,10 +20,10 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 9fc08f341448c5bdfd6477f273a6b295364b92a1 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 23 Oct 2025 11:47:25 +0200 Subject: [PATCH 135/194] Reset conversions after each codec test --- src/embed_tests/Codecs.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index cecc6825d..d4d22dcac 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -8,6 +8,12 @@ namespace Python.EmbeddingTest { public class Codecs { + [TearDown] + public void TearDown() + { + PyObjectConversions.Reset(); + } + [Test] public void TupleConversionsGeneric() { From a8e50208c5267de6dc2035ce2f69c005ec05ac1d Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 23 Oct 2025 21:37:40 +0200 Subject: [PATCH 136/194] Remove shutdown from most tests, disable the rest for now --- src/embed_tests/Events.cs | 12 ------------ src/embed_tests/Modules.cs | 12 ------------ .../StateSerialization/MethodSerialization.cs | 6 +++++- src/embed_tests/TestFinalizer.cs | 1 + src/embed_tests/TestPyInt.cs | 12 ------------ src/embed_tests/TestPyIter.cs | 12 ------------ src/embed_tests/TestPyList.cs | 12 ------------ src/embed_tests/TestPyNumber.cs | 12 ------------ src/embed_tests/TestPyObject.cs | 12 ------------ src/embed_tests/TestPySequence.cs | 12 ------------ src/embed_tests/TestPyString.cs | 12 ------------ src/embed_tests/TestPyTuple.cs | 12 ------------ src/embed_tests/TestPyType.cs | 12 ------------ src/embed_tests/TestPyWith.cs | 12 ------------ src/embed_tests/TestPythonEngineProperties.cs | 15 ++++----------- src/embed_tests/TestPythonException.cs | 12 ------------ src/embed_tests/TestRuntime.cs | 1 + src/embed_tests/pyimport.cs | 3 ++- src/embed_tests/pyinitialize.cs | 3 +-- 19 files changed, 14 insertions(+), 171 deletions(-) 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/Modules.cs b/src/embed_tests/Modules.cs index fc9b8f398..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. /// 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/TestFinalizer.cs b/src/embed_tests/TestFinalizer.cs index 9ef3e25b6..89dcf137e 100644 --- a/src/embed_tests/TestFinalizer.cs +++ b/src/embed_tests/TestFinalizer.cs @@ -86,6 +86,7 @@ public void CollectBasicObject() } [Test] + [Ignore("Requires explicit shutdown")] [Obsolete("GC tests are not guaranteed")] public void CollectOnShutdown() { diff --git a/src/embed_tests/TestPyInt.cs b/src/embed_tests/TestPyInt.cs index d2767e664..36319cf1a 100644 --- a/src/embed_tests/TestPyInt.cs +++ b/src/embed_tests/TestPyInt.cs @@ -10,18 +10,6 @@ namespace Python.EmbeddingTest { public class TestPyInt { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestCtorInt() { diff --git a/src/embed_tests/TestPyIter.cs b/src/embed_tests/TestPyIter.cs index 7428da979..5da660242 100644 --- a/src/embed_tests/TestPyIter.cs +++ b/src/embed_tests/TestPyIter.cs @@ -9,18 +9,6 @@ namespace Python.EmbeddingTest { class TestPyIter { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void KeepOldObjects() { diff --git a/src/embed_tests/TestPyList.cs b/src/embed_tests/TestPyList.cs index eee129f2d..a380f0b2d 100644 --- a/src/embed_tests/TestPyList.cs +++ b/src/embed_tests/TestPyList.cs @@ -7,18 +7,6 @@ namespace Python.EmbeddingTest { public class TestPyList { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestStringIsListType() { diff --git a/src/embed_tests/TestPyNumber.cs b/src/embed_tests/TestPyNumber.cs index 0261c15c1..d8e275521 100644 --- a/src/embed_tests/TestPyNumber.cs +++ b/src/embed_tests/TestPyNumber.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPyNumber { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void IsNumberTypeTrue() { diff --git a/src/embed_tests/TestPyObject.cs b/src/embed_tests/TestPyObject.cs index 2f27eba1b..f762b94e9 100644 --- a/src/embed_tests/TestPyObject.cs +++ b/src/embed_tests/TestPyObject.cs @@ -8,18 +8,6 @@ namespace Python.EmbeddingTest { public class TestPyObject { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestGetDynamicMemberNames() { diff --git a/src/embed_tests/TestPySequence.cs b/src/embed_tests/TestPySequence.cs index dc35a2633..339ea1e83 100644 --- a/src/embed_tests/TestPySequence.cs +++ b/src/embed_tests/TestPySequence.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPySequence { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestIsSequenceTrue() { diff --git a/src/embed_tests/TestPyString.cs b/src/embed_tests/TestPyString.cs index 35c6339ee..a1fdd6079 100644 --- a/src/embed_tests/TestPyString.cs +++ b/src/embed_tests/TestPyString.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPyString { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestStringCtor() { diff --git a/src/embed_tests/TestPyTuple.cs b/src/embed_tests/TestPyTuple.cs index 5d76116aa..3a3fbf2a0 100644 --- a/src/embed_tests/TestPyTuple.cs +++ b/src/embed_tests/TestPyTuple.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPyTuple { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - /// /// Test IsTupleType without having to Initialize a tuple. /// PyTuple constructor use IsTupleType. This decouples the tests. diff --git a/src/embed_tests/TestPyType.cs b/src/embed_tests/TestPyType.cs index d98dfda2e..c29032a8a 100644 --- a/src/embed_tests/TestPyType.cs +++ b/src/embed_tests/TestPyType.cs @@ -10,18 +10,6 @@ namespace Python.EmbeddingTest { public class TestPyType { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void CanCreateHeapType() { diff --git a/src/embed_tests/TestPyWith.cs b/src/embed_tests/TestPyWith.cs index d1c9aac28..fbce811da 100644 --- a/src/embed_tests/TestPyWith.cs +++ b/src/embed_tests/TestPyWith.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPyWith { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - /// /// Test that exception is raised in context manager that ignores it. /// diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs index be91d7f45..c6b4a2024 100644 --- a/src/embed_tests/TestPythonEngineProperties.cs +++ b/src/embed_tests/TestPythonEngineProperties.cs @@ -9,7 +9,6 @@ public class TestPythonEngineProperties [Test] public static void GetBuildinfoDoesntCrash() { - PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.BuildInfo; @@ -22,7 +21,6 @@ public static void GetBuildinfoDoesntCrash() [Test] public static void GetCompilerDoesntCrash() { - PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.Compiler; @@ -36,7 +34,6 @@ public static void GetCompilerDoesntCrash() [Test] public static void GetCopyrightDoesntCrash() { - PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.Copyright; @@ -49,7 +46,6 @@ public static void GetCopyrightDoesntCrash() [Test] public static void GetPlatformDoesntCrash() { - PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.Platform; @@ -62,7 +58,6 @@ public static void GetPlatformDoesntCrash() [Test] public static void GetVersionDoesntCrash() { - PythonEngine.Initialize(); using (Py.GIL()) { string s = PythonEngine.Version; @@ -75,21 +70,17 @@ public static void GetVersionDoesntCrash() [Test] public static void GetPythonPathDefault() { - PythonEngine.Initialize(); string s = PythonEngine.PythonPath; StringAssert.Contains("python", s.ToLower()); - PythonEngine.Shutdown(); } [Test] public static void GetProgramNameDefault() { - PythonEngine.Initialize(); string s = PythonEngine.ProgramName; Assert.NotNull(s); - PythonEngine.Shutdown(); } /// @@ -101,13 +92,12 @@ public static void GetPythonHomeDefault() { string envPythonHome = Environment.GetEnvironmentVariable("PYTHONHOME") ?? ""; - PythonEngine.Initialize(); string enginePythonHome = PythonEngine.PythonHome; Assert.AreEqual(envPythonHome, enginePythonHome); - PythonEngine.Shutdown(); } + [Ignore("Only works if we can shutdown and re-init the interpreter")] [Test] public void SetPythonHome() { @@ -130,6 +120,7 @@ public void SetPythonHome() PythonEngine.PythonHome = pythonHomeBackup; } + [Ignore("Only works if we can shutdown and re-init the interpreter")] [Test] public void SetPythonHomeTwice() { @@ -172,6 +163,7 @@ public void SetPythonHomeEmptyString() PythonEngine.Shutdown(); } + [Ignore("Only works if we can shutdown and re-init the interpreter")] [Test] public void SetProgramName() { @@ -193,6 +185,7 @@ public void SetProgramName() PythonEngine.ProgramName = programNameBackup; } + [Ignore("Only works if we can shutdown and re-init the interpreter")] [Test] public void SetPythonPath() { diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index a248b6a1f..91a412749 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -6,18 +6,6 @@ namespace Python.EmbeddingTest { public class TestPythonException { - [OneTimeSetUp] - public void SetUp() - { - PythonEngine.Initialize(); - } - - [OneTimeTearDown] - public void Dispose() - { - PythonEngine.Shutdown(); - } - [Test] public void TestMessage() { diff --git a/src/embed_tests/TestRuntime.cs b/src/embed_tests/TestRuntime.cs index 77696fd96..88cef1557 100644 --- a/src/embed_tests/TestRuntime.cs +++ b/src/embed_tests/TestRuntime.cs @@ -5,6 +5,7 @@ namespace Python.EmbeddingTest { + [Ignore("Only works if we can shutdown and re-initialize the Python runtime")] public class TestRuntime { [OneTimeSetUp] diff --git a/src/embed_tests/pyimport.cs b/src/embed_tests/pyimport.cs index 551df1915..c774af345 100644 --- a/src/embed_tests/pyimport.cs +++ b/src/embed_tests/pyimport.cs @@ -42,7 +42,8 @@ public void SetUp() [OneTimeTearDown] public void Dispose() { - // TODO Undo the above + using var _ = Py.GIL(); + Py.Import("sys").GetAttr("path").InvokeMethod("remove", new PyString(TestPath)); } /// diff --git a/src/embed_tests/pyinitialize.cs b/src/embed_tests/pyinitialize.cs index cde463e61..dbb8f0bbe 100644 --- a/src/embed_tests/pyinitialize.cs +++ b/src/embed_tests/pyinitialize.cs @@ -2,9 +2,9 @@ using NUnit.Framework; using Python.Runtime; -#if false namespace Python.EmbeddingTest { + [Ignore("Only works if we can re-initialize the Python engine")] public class PyInitializeTest { /// @@ -156,4 +156,3 @@ public void ShutdownHandlers() public class ImportClassShutdownRefcountClass { } } -#endif \ No newline at end of file From 0a6062ea720927b99522fd9bff5884520171a589 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 26 Oct 2025 18:50:59 +0100 Subject: [PATCH 137/194] Use python -m pytest, path seemingly not properly updated --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ca2562d04..ebf84bc77 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -78,14 +78,14 @@ jobs: - name: Python Tests (Mono) if: ${{ matrix.os.category != 'windows' }} - run: pytest --runtime mono + run: python -m pytest --runtime mono - name: Python Tests (.NET Core) - run: pytest --runtime coreclr + run: python -m pytest --runtime coreclr - name: Python Tests (.NET Framework) if: ${{ matrix.os.category == 'windows' }} - run: pytest --runtime netfx + run: python -m pytest --runtime netfx - name: Python tests run from .NET run: dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/ From 6b1bb923bd337a5c78fc19fa065db574854ebb5d Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 26 Oct 2025 18:52:04 +0100 Subject: [PATCH 138/194] Remove unused architecture from uv env activation --- .github/workflows/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ebf84bc77..6c5d84dcb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,7 +58,6 @@ jobs: - name: Set up Python ${{ matrix.python }} uses: astral-sh/setup-uv@v7 with: - architecture: ${{ matrix.os.platform }} python-version: ${{ matrix.python }} activate-environment: true enable-cache: true From d4d1768d5ff2fdd5b6bce407f425678d4fdd186b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 26 Oct 2025 18:54:21 +0100 Subject: [PATCH 139/194] Synchronize the environment --- .github/workflows/main.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6c5d84dcb..286d43559 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,12 +58,17 @@ jobs: - name: Set up Python ${{ matrix.python }} uses: astral-sh/setup-uv@v7 with: + architecture: ${{ matrix.os.platform }} python-version: ${{ matrix.python }} + cache-python: true activate-environment: true enable-cache: true - name: Synchronize the virtual environment - run: uv sync + run: uv sync --managed-python + + - name: Show pyvenv.cfg + run: cat .venv/pyvenv.cfg - name: Embedding tests (Mono/.NET Framework) run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net472 --logger "console;verbosity=detailed" src/embed_tests/ @@ -77,14 +82,14 @@ jobs: - name: Python Tests (Mono) if: ${{ matrix.os.category != 'windows' }} - run: python -m pytest --runtime mono + run: pytest --runtime mono - name: Python Tests (.NET Core) - run: python -m pytest --runtime coreclr + run: pytest --runtime coreclr - name: Python Tests (.NET Framework) if: ${{ matrix.os.category == 'windows' }} - run: python -m pytest --runtime netfx + run: pytest --runtime netfx - name: Python tests run from .NET run: dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/ From 40a3db7276423fb89c2742ac6d93572f53312ba1 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 7 Dec 2025 15:12:32 +0100 Subject: [PATCH 140/194] Include the probed PythonDLL value in the exception --- src/runtime/Runtime.Delegates.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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); } } From 96f428ff0489cff438716d4e8c76fa7fbd4ccefc Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 7 Dec 2025 23:11:00 +0100 Subject: [PATCH 141/194] Use the actual pytest runner --- .github/workflows/main.yml | 4 ++ Directory.Build.props | 1 + .../Python.PythonTestsRunner.csproj | 8 ++++ src/python_tests_runner/PythonTestRunner.cs | 47 ++++++------------- src/runtime/Util/PythonEnvironment.cs | 2 +- 5 files changed, 29 insertions(+), 33 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 286d43559..53c0934ae 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -92,4 +92,8 @@ jobs: run: pytest --runtime netfx - name: Python tests run from .NET + # For some reason, it won't find pytest on the Windows + 3.10 + # combination, which hints that it does not handle the venv properly in + # this combination. + if: ${{ matrix.os.category != 'windows' || matrix.python != '3.10' }} run: dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/ diff --git a/Directory.Build.props b/Directory.Build.props index 5f8157f2e..85e4039b9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,6 +9,7 @@ $([System.IO.File]::ReadAllText("$(MSBuildThisFileDirectory)version.txt").Trim()) $(FullVersion.Split('-', 2)[0]) $(FullVersion.Split('-', 2)[1]) + $(MSBuildThisFileDirectory) diff --git a/src/python_tests_runner/Python.PythonTestsRunner.csproj b/src/python_tests_runner/Python.PythonTestsRunner.csproj index e55c1af37..5fc55d158 100644 --- a/src/python_tests_runner/Python.PythonTestsRunner.csproj +++ b/src/python_tests_runner/Python.PythonTestsRunner.csproj @@ -23,4 +23,12 @@ + + + + diff --git a/src/python_tests_runner/PythonTestRunner.cs b/src/python_tests_runner/PythonTestRunner.cs index f97cc5aec..3df22ec2e 100644 --- a/src/python_tests_runner/PythonTestRunner.cs +++ b/src/python_tests_runner/PythonTestRunner.cs @@ -8,22 +8,29 @@ using NUnit.Framework; using Python.Runtime; -using Python.Test; namespace Python.PythonTestsRunner { public class PythonTestRunner { + string OriginalDirectory; + [OneTimeSetUp] public void SetUp() { PythonEngine.Initialize(); + OriginalDirectory = Environment.CurrentDirectory; + + var codeDir = File.ReadAllText("tests_location.txt").Trim(); + TestContext.Progress.WriteLine($"Changing working directory to {codeDir}"); + Environment.CurrentDirectory = codeDir; } [OneTimeTearDown] public void Dispose() { PythonEngine.Shutdown(); + Environment.CurrentDirectory = OriginalDirectory; } /// @@ -46,39 +53,15 @@ static IEnumerable PythonTestCases() [TestCaseSource(nameof(PythonTestCases))] public void RunPythonTest(string testFile, string testName) { - // Find the tests directory - string folder = typeof(PythonTestRunner).Assembly.Location; - while (Path.GetFileName(folder) != "src") + using dynamic pytest = Py.Import("pytest"); + + using var args = new PyList(); + args.Append(new PyString($"{testFile}.py::{testName}")); + int res = pytest.main(args); + if (res != 0) { - folder = Path.GetDirectoryName(folder); + Assert.Fail($"Python test {testFile}.{testName} failed"); } - folder = Path.Combine(folder, "..", "tests"); - string path = Path.Combine(folder, testFile + ".py"); - if (!File.Exists(path)) throw new FileNotFoundException("Cannot find test file", path); - - // We could use 'import' below, but importlib gives more helpful error messages than 'import' - // https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly - // Because the Python tests sometimes have relative imports, the module name must be inside the tests package - PythonEngine.Exec($@" -import sys -import os -sys.path.append(os.path.dirname(r'{folder}')) -sys.path.append(os.path.join(r'{folder}', 'fixtures')) -import clr -clr.AddReference('Python.Test') -import tests -module_name = 'tests.{testFile}' -file_path = r'{path}' -import importlib.util -spec = importlib.util.spec_from_file_location(module_name, file_path) -module = importlib.util.module_from_spec(spec) -sys.modules[module_name] = module -try: - spec.loader.exec_module(module) -except ImportError as error: - raise ImportError(str(error) + ' when sys.path=' + os.pathsep.join(sys.path)) -module.{testName}() -"); } } } diff --git a/src/runtime/Util/PythonEnvironment.cs b/src/runtime/Util/PythonEnvironment.cs index 701db3c93..b1ebc7fa5 100644 --- a/src/runtime/Util/PythonEnvironment.cs +++ b/src/runtime/Util/PythonEnvironment.cs @@ -161,7 +161,7 @@ private static Dictionary TryParse(string venvCfg) private static string ProgramNameFromPath(string path) { - if (Runtime.IsWindows) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return Path.Combine(path, "Scripts", "python.exe"); } From 5459ac75d4292c503cd37f7cf11a7556124c6651 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 8 Dec 2025 10:34:50 +0100 Subject: [PATCH 142/194] Move tests that require reinit and only run on .NET Framework --- .../NeedsReinit/StopAndRestartEngine.cs | 28 ++++ .../{ => NeedsReinit}/TestDomainReload.cs | 7 +- .../TestPyInitialize.cs} | 6 +- .../NeedsReinit/TestPythonEngineProperties.cs | 133 ++++++++++++++++++ .../{ => NeedsReinit}/TestRuntime.cs | 16 +-- src/embed_tests/Python.EmbeddingTest.csproj | 5 + src/embed_tests/TestPythonEngineProperties.cs | 127 ----------------- 7 files changed, 177 insertions(+), 145 deletions(-) create mode 100644 src/embed_tests/NeedsReinit/StopAndRestartEngine.cs rename src/embed_tests/{ => NeedsReinit}/TestDomainReload.cs (99%) rename src/embed_tests/{pyinitialize.cs => NeedsReinit/TestPyInitialize.cs} (97%) create mode 100644 src/embed_tests/NeedsReinit/TestPythonEngineProperties.cs rename src/embed_tests/{ => NeedsReinit}/TestRuntime.cs (92%) 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 97% rename from src/embed_tests/pyinitialize.cs rename to src/embed_tests/NeedsReinit/TestPyInitialize.cs index dbb8f0bbe..1ef4127b8 100644 --- a/src/embed_tests/pyinitialize.cs +++ b/src/embed_tests/NeedsReinit/TestPyInitialize.cs @@ -2,10 +2,10 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest.NeedsReinit { - [Ignore("Only works if we can re-initialize the Python engine")] - public class PyInitializeTest + [Category("NeedsReinit")] + public class TestPyInitialize : StopAndRestartEngine { /// /// Tests issue with multiple simple Initialize/Shutdowns. 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 92% rename from src/embed_tests/TestRuntime.cs rename to src/embed_tests/NeedsReinit/TestRuntime.cs index 88cef1557..193bf57d3 100644 --- a/src/embed_tests/TestRuntime.cs +++ b/src/embed_tests/NeedsReinit/TestRuntime.cs @@ -3,21 +3,11 @@ using NUnit.Framework; using Python.Runtime; -namespace Python.EmbeddingTest +namespace Python.EmbeddingTest.NeedsReinit { - [Ignore("Only works if we can shutdown and re-initialize the Python runtime")] - 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/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index eb2048c76..36b9a31f9 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -14,6 +14,11 @@ + + + + + $(DefineConstants);$(ConfiguredConstants) diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs index c6b4a2024..485931cfb 100644 --- a/src/embed_tests/TestPythonEngineProperties.cs +++ b/src/embed_tests/TestPythonEngineProperties.cs @@ -96,132 +96,5 @@ public static void GetPythonHomeDefault() Assert.AreEqual(envPythonHome, enginePythonHome); } - - [Ignore("Only works if we can shutdown and re-init the interpreter")] - [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; - } - - [Ignore("Only works if we can shutdown and re-init the interpreter")] - [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(); - } - - [Ignore("Only works if we can shutdown and re-init the interpreter")] - [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; - } - - [Ignore("Only works if we can shutdown and re-init the interpreter")] - [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(); - } } } From abb6855e36f09b628a90c6126599eb1992206bfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:22:17 +0100 Subject: [PATCH 143/194] Bump NUnit3TestAdapter from 5.2.0 to 6.0.0 (#2667) --- updated-dependencies: - dependency-name: NUnit3TestAdapter dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: NUnit3TestAdapter dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/embed_tests/Python.EmbeddingTest.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 36b9a31f9..28076120a 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -29,7 +29,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 98827881e86dbbf6679456ed353bdb24743d77a8 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 9 Dec 2025 08:01:57 +0100 Subject: [PATCH 144/194] Fix line endings --- doc/make.bat | 70 +++++++------- src/runtime/Codecs/IterableDecoder.cs | 110 +++++++++++----------- src/runtime/Codecs/ListDecoder.cs | 100 ++++++++++---------- src/runtime/Codecs/SequenceDecoder.cs | 96 ++++++++++---------- src/runtime/Types/ManagedTypes.cd | 2 +- tests/domain_tests/App.config | 2 +- tests/test_mp_length.py | 126 +++++++++++++------------- 7 files changed, 253 insertions(+), 253 deletions(-) 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/src/runtime/Codecs/IterableDecoder.cs b/src/runtime/Codecs/IterableDecoder.cs index bcc2eca01..b864850d5 100644 --- a/src/runtime/Codecs/IterableDecoder.cs +++ b/src/runtime/Codecs/IterableDecoder.cs @@ -1,55 +1,55 @@ -using System; -using System.Collections.Generic; - -namespace Python.Runtime.Codecs -{ - public class IterableDecoder : IPyObjectDecoder - { - internal static bool IsIterable(Type targetType) - { - //if it is a plain IEnumerable, we can decode it using sequence protocol. - if (targetType == typeof(System.Collections.IEnumerable)) - return true; - - if (!targetType.IsGenericType) - return false; - - return targetType.GetGenericTypeDefinition() == typeof(IEnumerable<>); - } - - internal static bool IsIterable(PyType objectType) - { - return objectType.HasAttr("__iter__"); - } - - public bool CanDecode(PyType objectType, Type targetType) - { - return IsIterable(objectType) && IsIterable(targetType); - } - - public bool TryDecode(PyObject pyObj, out T value) - { - //first see if T is a plan IEnumerable - if (typeof(T) == typeof(System.Collections.IEnumerable)) - { - object enumerable = new CollectionWrappers.IterableWrapper(pyObj); - value = (T)enumerable; - return true; - } - - var elementType = typeof(T).GetGenericArguments()[0]; - var collectionType = typeof(CollectionWrappers.IterableWrapper<>).MakeGenericType(elementType); - - var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); - value = (T)instance; - return true; - } - - public static IterableDecoder Instance { get; } = new IterableDecoder(); - - public static void Register() - { - PyObjectConversions.RegisterDecoder(Instance); - } - } -} +using System; +using System.Collections.Generic; + +namespace Python.Runtime.Codecs +{ + public class IterableDecoder : IPyObjectDecoder + { + internal static bool IsIterable(Type targetType) + { + //if it is a plain IEnumerable, we can decode it using sequence protocol. + if (targetType == typeof(System.Collections.IEnumerable)) + return true; + + if (!targetType.IsGenericType) + return false; + + return targetType.GetGenericTypeDefinition() == typeof(IEnumerable<>); + } + + internal static bool IsIterable(PyType objectType) + { + return objectType.HasAttr("__iter__"); + } + + public bool CanDecode(PyType objectType, Type targetType) + { + return IsIterable(objectType) && IsIterable(targetType); + } + + public bool TryDecode(PyObject pyObj, out T value) + { + //first see if T is a plan IEnumerable + if (typeof(T) == typeof(System.Collections.IEnumerable)) + { + object enumerable = new CollectionWrappers.IterableWrapper(pyObj); + value = (T)enumerable; + return true; + } + + var elementType = typeof(T).GetGenericArguments()[0]; + var collectionType = typeof(CollectionWrappers.IterableWrapper<>).MakeGenericType(elementType); + + var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); + value = (T)instance; + return true; + } + + public static IterableDecoder Instance { get; } = new IterableDecoder(); + + public static void Register() + { + PyObjectConversions.RegisterDecoder(Instance); + } + } +} diff --git a/src/runtime/Codecs/ListDecoder.cs b/src/runtime/Codecs/ListDecoder.cs index 70ff33aaa..5da82851f 100644 --- a/src/runtime/Codecs/ListDecoder.cs +++ b/src/runtime/Codecs/ListDecoder.cs @@ -1,50 +1,50 @@ -using System; -using System.Collections.Generic; - -namespace Python.Runtime.Codecs -{ - public class ListDecoder : IPyObjectDecoder - { - private static bool IsList(Type targetType) - { - if (!targetType.IsGenericType) - return false; - - return targetType.GetGenericTypeDefinition() == typeof(IList<>); - } - - private static bool IsList(PyType objectType) - { - //TODO accept any python object that implements the sequence and list protocols - //must implement sequence protocol to fully implement list protocol - //if (!SequenceDecoder.IsSequence(objectType)) return false; - - //returns wheter the type is a list. - return PythonReferenceComparer.Instance.Equals(objectType, Runtime.PyListType); - } - - public bool CanDecode(PyType objectType, Type targetType) - { - return IsList(objectType) && IsList(targetType); - } - - public bool TryDecode(PyObject pyObj, out T value) - { - if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); - - var elementType = typeof(T).GetGenericArguments()[0]; - Type collectionType = typeof(CollectionWrappers.ListWrapper<>).MakeGenericType(elementType); - - var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); - value = (T)instance; - return true; - } - - public static ListDecoder Instance { get; } = new ListDecoder(); - - public static void Register() - { - PyObjectConversions.RegisterDecoder(Instance); - } - } -} +using System; +using System.Collections.Generic; + +namespace Python.Runtime.Codecs +{ + public class ListDecoder : IPyObjectDecoder + { + private static bool IsList(Type targetType) + { + if (!targetType.IsGenericType) + return false; + + return targetType.GetGenericTypeDefinition() == typeof(IList<>); + } + + private static bool IsList(PyType objectType) + { + //TODO accept any python object that implements the sequence and list protocols + //must implement sequence protocol to fully implement list protocol + //if (!SequenceDecoder.IsSequence(objectType)) return false; + + //returns wheter the type is a list. + return PythonReferenceComparer.Instance.Equals(objectType, Runtime.PyListType); + } + + public bool CanDecode(PyType objectType, Type targetType) + { + return IsList(objectType) && IsList(targetType); + } + + public bool TryDecode(PyObject pyObj, out T value) + { + if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); + + var elementType = typeof(T).GetGenericArguments()[0]; + Type collectionType = typeof(CollectionWrappers.ListWrapper<>).MakeGenericType(elementType); + + var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); + value = (T)instance; + return true; + } + + public static ListDecoder Instance { get; } = new ListDecoder(); + + public static void Register() + { + PyObjectConversions.RegisterDecoder(Instance); + } + } +} diff --git a/src/runtime/Codecs/SequenceDecoder.cs b/src/runtime/Codecs/SequenceDecoder.cs index a539297cd..c5ded4958 100644 --- a/src/runtime/Codecs/SequenceDecoder.cs +++ b/src/runtime/Codecs/SequenceDecoder.cs @@ -1,52 +1,52 @@ -using System; -using System.Collections.Generic; - -namespace Python.Runtime.Codecs -{ - public class SequenceDecoder : IPyObjectDecoder - { - internal static bool IsSequence(Type targetType) - { - if (!targetType.IsGenericType) - return false; - - return targetType.GetGenericTypeDefinition() == typeof(ICollection<>); - } - - internal static bool IsSequence(PyType objectType) - { - //must implement iterable protocol to fully implement sequence protocol - if (!IterableDecoder.IsIterable(objectType)) return false; - +using System; +using System.Collections.Generic; + +namespace Python.Runtime.Codecs +{ + public class SequenceDecoder : IPyObjectDecoder + { + internal static bool IsSequence(Type targetType) + { + if (!targetType.IsGenericType) + return false; + + return targetType.GetGenericTypeDefinition() == typeof(ICollection<>); + } + + internal static bool IsSequence(PyType objectType) + { + //must implement iterable protocol to fully implement sequence protocol + if (!IterableDecoder.IsIterable(objectType)) return false; + //returns wheter it implements the sequence protocol //according to python doc this needs to exclude dict subclasses //but I don't know how to look for that given the objectType //rather than the instance. - return objectType.HasAttr("__getitem__"); - } - - public bool CanDecode(PyType objectType, Type targetType) - { - return IsSequence(objectType) && IsSequence(targetType); - } - - public bool TryDecode(PyObject pyObj, out T value) - { - if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); - - var elementType = typeof(T).GetGenericArguments()[0]; - Type collectionType = typeof(CollectionWrappers.SequenceWrapper<>).MakeGenericType(elementType); - - var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); - value = (T)instance; - return true; - } - - public static SequenceDecoder Instance { get; } = new SequenceDecoder(); - - public static void Register() - { - PyObjectConversions.RegisterDecoder(Instance); - } - } -} + return objectType.HasAttr("__getitem__"); + } + + public bool CanDecode(PyType objectType, Type targetType) + { + return IsSequence(objectType) && IsSequence(targetType); + } + + public bool TryDecode(PyObject pyObj, out T value) + { + if (pyObj == null) throw new ArgumentNullException(nameof(pyObj)); + + var elementType = typeof(T).GetGenericArguments()[0]; + Type collectionType = typeof(CollectionWrappers.SequenceWrapper<>).MakeGenericType(elementType); + + var instance = Activator.CreateInstance(collectionType, new[] { pyObj }); + value = (T)instance; + return true; + } + + public static SequenceDecoder Instance { get; } = new SequenceDecoder(); + + public static void Register() + { + PyObjectConversions.RegisterDecoder(Instance); + } + } +} diff --git a/src/runtime/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/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/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 From dc5c6c4c39da2be3e0821f3436e3d449066e8614 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 9 Dec 2025 08:03:16 +0100 Subject: [PATCH 145/194] Add previous commit to ignore file --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs 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 From caac33d258e327bba18d6436a62d33d7fcd08859 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 9 Aug 2025 22:52:27 +0200 Subject: [PATCH 146/194] Initial 3.14 commit --- .github/workflows/main.yml | 2 +- pyproject.toml | 2 +- src/runtime/Native/TypeOffset314.cs | 147 ++++++++++++++++++++++++++++ src/runtime/PythonEngine.cs | 2 +- 4 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 src/runtime/Native/TypeOffset314.cs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 53c0934ae..e1507279e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,7 +38,7 @@ jobs: platform: x64 instance: macos-13 - python: ["3.10", "3.11", "3.12", "3.13"] + python: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - name: Set Environment on macOS diff --git a/pyproject.toml b/pyproject.toml index e3fba0dda..664df8b01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "clr_loader>=0.2.7,<0.3.0" ] -requires-python = ">=3.10, <3.14" +requires-python = ">=3.10, <3.15" classifiers = [ "Development Status :: 5 - Production/Stable", diff --git a/src/runtime/Native/TypeOffset314.cs b/src/runtime/Native/TypeOffset314.cs new file mode 100644 index 000000000..6ec9a621e --- /dev/null +++ b/src/runtime/Native/TypeOffset314.cs @@ -0,0 +1,147 @@ + +// Auto-generated by geninterop.py. +// DO NOT MODIFY BY HAND. + +// Python 3.14: ABI flags: '' + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +using Python.Runtime.Native; + +namespace Python.Runtime +{ + [SuppressMessage("Style", "IDE1006:Naming Styles", + Justification = "Following CPython", + Scope = "type")] + + [StructLayout(LayoutKind.Sequential)] + internal class TypeOffset314 : GeneratedTypeOffsets, ITypeOffsets + { + public TypeOffset314() { } + // Auto-generated from PyHeapTypeObject in Python.h + public int ob_refcnt_full { get; private set; } + public int ob_type { get; private set; } + public int ob_size { get; private set; } + public int tp_name { get; private set; } + public int tp_basicsize { get; private set; } + public int tp_itemsize { get; private set; } + public int tp_dealloc { get; private set; } + public int tp_vectorcall_offset { get; private set; } + public int tp_getattr { get; private set; } + public int tp_setattr { get; private set; } + public int tp_as_async { get; private set; } + public int tp_repr { get; private set; } + public int tp_as_number { get; private set; } + public int tp_as_sequence { get; private set; } + public int tp_as_mapping { get; private set; } + public int tp_hash { get; private set; } + public int tp_call { get; private set; } + public int tp_str { get; private set; } + public int tp_getattro { get; private set; } + public int tp_setattro { get; private set; } + public int tp_as_buffer { get; private set; } + public int tp_flags { get; private set; } + public int tp_doc { get; private set; } + public int tp_traverse { get; private set; } + public int tp_clear { get; private set; } + public int tp_richcompare { get; private set; } + public int tp_weaklistoffset { get; private set; } + public int tp_iter { get; private set; } + public int tp_iternext { get; private set; } + public int tp_methods { get; private set; } + public int tp_members { get; private set; } + public int tp_getset { get; private set; } + public int tp_base { get; private set; } + public int tp_dict { get; private set; } + public int tp_descr_get { get; private set; } + public int tp_descr_set { get; private set; } + public int tp_dictoffset { get; private set; } + public int tp_init { get; private set; } + public int tp_alloc { get; private set; } + public int tp_new { get; private set; } + public int tp_free { get; private set; } + public int tp_is_gc { get; private set; } + public int tp_bases { get; private set; } + public int tp_mro { get; private set; } + public int tp_cache { get; private set; } + public int tp_subclasses { get; private set; } + public int tp_weaklist { get; private set; } + public int tp_del { get; private set; } + public int tp_version_tag { get; private set; } + public int tp_finalize { get; private set; } + public int tp_vectorcall { get; private set; } + public int tp_watched { get; private set; } + public int tp_versions_used { get; private set; } + public int am_await { get; private set; } + public int am_aiter { get; private set; } + public int am_anext { get; private set; } + public int am_send { get; private set; } + public int nb_add { get; private set; } + public int nb_subtract { get; private set; } + public int nb_multiply { get; private set; } + public int nb_remainder { get; private set; } + public int nb_divmod { get; private set; } + public int nb_power { get; private set; } + public int nb_negative { get; private set; } + public int nb_positive { get; private set; } + public int nb_absolute { get; private set; } + public int nb_bool { get; private set; } + public int nb_invert { get; private set; } + public int nb_lshift { get; private set; } + public int nb_rshift { get; private set; } + public int nb_and { get; private set; } + public int nb_xor { get; private set; } + public int nb_or { get; private set; } + public int nb_int { get; private set; } + public int nb_reserved { get; private set; } + public int nb_float { get; private set; } + public int nb_inplace_add { get; private set; } + public int nb_inplace_subtract { get; private set; } + public int nb_inplace_multiply { get; private set; } + public int nb_inplace_remainder { get; private set; } + public int nb_inplace_power { get; private set; } + public int nb_inplace_lshift { get; private set; } + public int nb_inplace_rshift { get; private set; } + public int nb_inplace_and { get; private set; } + public int nb_inplace_xor { get; private set; } + public int nb_inplace_or { get; private set; } + public int nb_floor_divide { get; private set; } + public int nb_true_divide { get; private set; } + public int nb_inplace_floor_divide { get; private set; } + public int nb_inplace_true_divide { get; private set; } + public int nb_index { get; private set; } + public int nb_matrix_multiply { get; private set; } + public int nb_inplace_matrix_multiply { get; private set; } + public int mp_length { get; private set; } + public int mp_subscript { get; private set; } + public int mp_ass_subscript { get; private set; } + public int sq_length { get; private set; } + public int sq_concat { get; private set; } + public int sq_repeat { get; private set; } + public int sq_item { get; private set; } + public int was_sq_slice { get; private set; } + public int sq_ass_item { get; private set; } + public int was_sq_ass_slice { get; private set; } + public int sq_contains { get; private set; } + public int sq_inplace_concat { get; private set; } + public int sq_inplace_repeat { get; private set; } + public int bf_getbuffer { get; private set; } + public int bf_releasebuffer { get; private set; } + public int name { get; private set; } + public int ht_slots { get; private set; } + public int qualname { get; private set; } + public int ht_cached_keys { get; private set; } + public int ht_module { get; private set; } + public int _ht_tpname { get; private set; } + public int ht_token { get; private set; } + public int spec_cache_getitem { get; private set; } + public int getitem_version { get; private set; } + public int init { get; private set; } + } +} + diff --git a/src/runtime/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 From e10d3332d6cc286551e754761ef2f757c9ff6f8c Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 22 Oct 2025 18:59:10 +0200 Subject: [PATCH 147/194] Apply alignment fix --- src/runtime/Native/TypeOffset314.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/runtime/Native/TypeOffset314.cs b/src/runtime/Native/TypeOffset314.cs index 6ec9a621e..28101ba12 100644 --- a/src/runtime/Native/TypeOffset314.cs +++ b/src/runtime/Native/TypeOffset314.cs @@ -75,8 +75,14 @@ public TypeOffset314() { } public int tp_version_tag { get; private set; } public int tp_finalize { get; private set; } public int tp_vectorcall { get; private set; } + // This is an error in our generator: + // + // The fields below are actually not pointers (like we incorrectly + // assume for all other fields) but instead a char (1 byte) and a short + // (2 bytes). By dropping one of the fields, we still get the correct + // overall size of the struct. public int tp_watched { get; private set; } - public int tp_versions_used { get; private set; } + // public int tp_versions_used { get; private set; } public int am_await { get; private set; } public int am_aiter { get; private set; } public int am_anext { get; private set; } From e9765585b3d7497e49ee0c35a17bb1792b91170e Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 22 Oct 2025 18:59:27 +0200 Subject: [PATCH 148/194] Disable problematic GC tests --- tests/test_method.py | 4 ++++ tests/test_subclass.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/tests/test_method.py b/tests/test_method.py index c70200c7e..175556106 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -2,6 +2,7 @@ """Test CLR method support.""" +import sys import System import pytest from Python.Test import MethodTest @@ -941,6 +942,7 @@ def test_getting_generic_method_binding_does_not_leak_ref_count(): refCount = sys.getrefcount(PlainOldClass().GenericMethod[str]) assert refCount == 1 +@pytest.mark.skipif(sys.version_info >= (3, 14), reason="Test skipped on Python 3.14 and above") def test_getting_generic_method_binding_does_not_leak_memory(): """Test that managed object is freed after calling generic method. Issue #691""" @@ -982,6 +984,7 @@ def test_getting_overloaded_method_binding_does_not_leak_ref_count(): refCount = sys.getrefcount(PlainOldClass().OverloadedMethod.Overloads[int]) assert refCount == 1 +@pytest.mark.skipif(sys.version_info >= (3, 14), reason="Test skipped on Python 3.14 and above") def test_getting_overloaded_method_binding_does_not_leak_memory(): """Test that managed object is freed after calling overloaded method. Issue #691""" @@ -1024,6 +1027,7 @@ def test_getting_method_overloads_binding_does_not_leak_ref_count(): assert refCount == 1 @pytest.mark.xfail(reason="Fails locally, need to investigate later", strict=False) +@pytest.mark.skipif(sys.version_info >= (3, 14), reason="Test skipped on Python 3.14 and above") def test_getting_method_overloads_binding_does_not_leak_memory(): """Test that managed object is freed after calling overloaded method. Issue #691""" diff --git a/tests/test_subclass.py b/tests/test_subclass.py index c6ab7650f..43f4b44bb 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -6,6 +6,7 @@ """Test sub-classing managed types""" +import sys import System import pytest from Python.Test import (IInterfaceTest, SubClassTest, EventArgsTest, @@ -303,6 +304,7 @@ def __init__(self, i, s): assert calls[0][1] == "foo" # regression test for https://github.com/pythonnet/pythonnet/issues/1565 +@pytest.mark.skipif(sys.version_info >= (3, 14), reason="Test skipped on Python 3.14 and above") def test_can_be_collected_by_gc(): from Python.Test import BaseClass From 65af09891fc6b2b5a832dbc2218c2f0eaf633684 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 24 Oct 2025 14:56:38 +0200 Subject: [PATCH 149/194] Set ht_token to NULL in Python 3.14 --- src/runtime/Native/TypeOffset.cs | 15 +++++++++++++++ src/runtime/TypeManager.cs | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/src/runtime/Native/TypeOffset.cs b/src/runtime/Native/TypeOffset.cs index 803b86bfa..282107432 100644 --- a/src/runtime/Native/TypeOffset.cs +++ b/src/runtime/Native/TypeOffset.cs @@ -77,6 +77,8 @@ static partial class TypeOffset internal static int tp_setattro { get; private set; } internal static int tp_str { get; private set; } internal static int tp_traverse { get; private set; } + // Special case: Only available in Python 3.14 onwards, set to -1 by default + internal static int ht_token { get; private set; } = -1; internal static void Use(ITypeOffsets offsets, int extraHeadOffset) { @@ -89,6 +91,19 @@ internal static void Use(ITypeOffsets offsets, int extraHeadOffset) slotNames.Add(offsetProperty.Name); var sourceProperty = typeof(ITypeOffsets).GetProperty(offsetProperty.Name); + if (sourceProperty == null) + { + if ((int)offsetProperty.GetValue(null) == -1) + { + // Skip, this is an optional offset value + continue; + } + else + { + throw new Exception($"No offset defined for necessary slot {offsetProperty.Name}"); + } + } + int value = (int)sourceProperty.GetValue(offsets, null); value += extraHeadOffset; offsetProperty.SetValue(obj: null, value: value, index: null); diff --git a/src/runtime/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; From e244503a245e61348c5d6c2675de1e3a4de0bc57 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 26 Oct 2025 18:32:35 +0100 Subject: [PATCH 150/194] Fix lockfile --- uv.lock | 506 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 333 insertions(+), 173 deletions(-) diff --git a/uv.lock b/uv.lock index 9f73eb110..25a53fd51 100644 --- a/uv.lock +++ b/uv.lock @@ -1,74 +1,103 @@ version = 1 revision = 3 -requires-python = ">=3.10, <3.14" +requires-python = ">=3.10, <3.15" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version < '3.11'", +] [[package]] name = "cffi" -version = "1.17.1" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser" }, + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +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/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" }, - { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" }, - { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, - { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, - { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, - { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" }, - { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" }, - { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" }, - { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, - { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, + { 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.2.7.post0" +version = "0.2.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/b3/8ae917e458394e2cebdbf17bed0a8204f8d4ffc79a093a7b1141c7731d3c/clr_loader-0.2.7.post0.tar.gz", hash = "sha256:b7a8b3f8fbb1bcbbb6382d887e21d1742d4f10b5ea209e4ad95568fe97e1c7c6", size = 56701, upload-time = "2024-12-12T20:15:15.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/c2/da52aaf19424e3f0abec003d08dd1ccae52c88a3b41e31151a03bed18488/clr_loader-0.2.9.tar.gz", hash = "sha256:6af3d582c3de55ce9e9e676d2b3dbf6bc680c4ea8f76c58786739a5bdcf6b52d", size = 84829, upload-time = "2025-12-05T16:57:12.466Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/c0/06e64a54bced4e8b885c1e7ec03ee1869e52acf69e87da40f92391a214ad/clr_loader-0.2.7.post0-py3-none-any.whl", hash = "sha256:e0b9fcc107d48347a4311a28ffe3ae78c4968edb216ffb6564cb03f7ace0bb47", size = 50649, upload-time = "2024-12-12T20:15:13.714Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ba/7d6e6bdeee4e218a35a78b00f6fae24ef5b475dde293baffff30b92a67ff/clr_loader-0.2.9-py3-none-any.whl", hash = "sha256:7ef4f1280a5d3a4e19a8b21901b5fd804e104a1c40d755bcca0a4f694cb1b726", size = 56512, upload-time = "2025-12-05T16:57:10.811Z" }, ] [[package]] @@ -82,138 +111,248 @@ wheels = [ [[package]] name = "exceptiongroup" -version = "1.2.2" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" } +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/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, + { 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.4.0" +version = "0.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/86/b1d3a9c49d907cac74f9d8bcead2c8e807a878c0e218d8ef1d38e6a4f59a/find_libpython-0.4.0.tar.gz", hash = "sha256:46f9cdcd397ddb563b2d7592ded3796a41c1df5222443bd9d981721c906c03e6", size = 8979, upload-time = "2024-03-13T17:01:10.727Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/46/c466b94830bb77ef1e715d869246b9f8e111f9b2f4de2c60d4de1b986779/find_libpython-0.5.0.tar.gz", hash = "sha256:4e4e0ffcad3bfaf2af9461b359329b8736e3f721dc375da7c167aff383e56be1", size = 9364, upload-time = "2025-10-04T19:50:32.499Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/89/6b4624122d5c61a86e8aebcebd377866338b705ce4f115c45b046dc09b99/find_libpython-0.4.0-py3-none-any.whl", hash = "sha256:034a4253bd57da3408aefc59aeac1650150f6c1f42e10fdd31615cf1df0842e3", size = 8670, upload-time = "2024-03-13T17:01:09.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/3f/0eb94bfca99e54404901536ea8c80ddacff4953257514c6b8fb01f9a75a8/find_libpython-0.5.0-py3-none-any.whl", hash = "sha256:7690dcf6442cdce39c0df191903fd5ecf9af437fa920effb6569fbf2c8ca8ab4", size = 9194, upload-time = "2025-10-04T19:50:31.229Z" }, ] [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, + { 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.4" +version = "2.2.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701, upload-time = "2025-03-16T18:27:00.648Z" } +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.3.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/89/a79e86e5c1433926ed7d60cb267fb64aa578b6101ab645800fd43b4801de/numpy-2.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9", size = 21250661, upload-time = "2025-03-16T18:02:13.017Z" }, - { url = "https://files.pythonhosted.org/packages/79/c2/f50921beb8afd60ed9589ad880332cfefdb805422210d327fb48f12b7a81/numpy-2.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae", size = 14389926, upload-time = "2025-03-16T18:02:39.022Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b9/2c4e96130b0b0f97b0ef4a06d6dae3b39d058b21a5e2fa2decd7fd6b1c8f/numpy-2.2.4-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:a84eda42bd12edc36eb5b53bbcc9b406820d3353f1994b6cfe453a33ff101775", size = 5428329, upload-time = "2025-03-16T18:02:50.032Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a5/3d7094aa898f4fc5c84cdfb26beeae780352d43f5d8bdec966c4393d644c/numpy-2.2.4-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:4ba5054787e89c59c593a4169830ab362ac2bee8a969249dc56e5d7d20ff8df9", size = 6963559, upload-time = "2025-03-16T18:03:02.523Z" }, - { url = "https://files.pythonhosted.org/packages/4c/22/fb1be710a14434c09080dd4a0acc08939f612ec02efcb04b9e210474782d/numpy-2.2.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7716e4a9b7af82c06a2543c53ca476fa0b57e4d760481273e09da04b74ee6ee2", size = 14368066, upload-time = "2025-03-16T18:03:27.146Z" }, - { url = "https://files.pythonhosted.org/packages/c2/07/2e5cc71193e3ef3a219ffcf6ca4858e46ea2be09c026ddd480d596b32867/numpy-2.2.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf8c1d66f432ce577d0197dceaac2ac00c0759f573f28516246351c58a85020", size = 16417040, upload-time = "2025-03-16T18:03:55.999Z" }, - { url = "https://files.pythonhosted.org/packages/1a/97/3b1537776ad9a6d1a41813818343745e8dd928a2916d4c9edcd9a8af1dac/numpy-2.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:218f061d2faa73621fa23d6359442b0fc658d5b9a70801373625d958259eaca3", size = 15879862, upload-time = "2025-03-16T18:04:23.56Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b7/4472f603dd45ef36ff3d8e84e84fe02d9467c78f92cc121633dce6da307b/numpy-2.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:df2f57871a96bbc1b69733cd4c51dc33bea66146b8c63cacbfed73eec0883017", size = 18206032, upload-time = "2025-03-16T18:04:53.694Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bd/6a092963fb82e6c5aa0d0440635827bbb2910da229545473bbb58c537ed3/numpy-2.2.4-cp310-cp310-win32.whl", hash = "sha256:a0258ad1f44f138b791327961caedffbf9612bfa504ab9597157806faa95194a", size = 6608517, upload-time = "2025-03-16T18:05:06.647Z" }, - { url = "https://files.pythonhosted.org/packages/01/e3/cb04627bc2a1638948bc13e818df26495aa18e20d5be1ed95ab2b10b6847/numpy-2.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:0d54974f9cf14acf49c60f0f7f4084b6579d24d439453d5fc5805d46a165b542", size = 12943498, upload-time = "2025-03-16T18:05:28.591Z" }, - { url = "https://files.pythonhosted.org/packages/16/fb/09e778ee3a8ea0d4dc8329cca0a9c9e65fed847d08e37eba74cb7ed4b252/numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4", size = 21254989, upload-time = "2025-03-16T18:06:04.092Z" }, - { url = "https://files.pythonhosted.org/packages/a2/0a/1212befdbecab5d80eca3cde47d304cad986ad4eec7d85a42e0b6d2cc2ef/numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4", size = 14425910, upload-time = "2025-03-16T18:06:29.062Z" }, - { url = "https://files.pythonhosted.org/packages/2b/3e/e7247c1d4f15086bb106c8d43c925b0b2ea20270224f5186fa48d4fb5cbd/numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f", size = 5426490, upload-time = "2025-03-16T18:06:39.901Z" }, - { url = "https://files.pythonhosted.org/packages/5d/fa/aa7cd6be51419b894c5787a8a93c3302a1ed4f82d35beb0613ec15bdd0e2/numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880", size = 6967754, upload-time = "2025-03-16T18:06:52.658Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ee/96457c943265de9fadeb3d2ffdbab003f7fba13d971084a9876affcda095/numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1", size = 14373079, upload-time = "2025-03-16T18:07:16.297Z" }, - { url = "https://files.pythonhosted.org/packages/c5/5c/ceefca458559f0ccc7a982319f37ed07b0d7b526964ae6cc61f8ad1b6119/numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5", size = 16428819, upload-time = "2025-03-16T18:07:44.188Z" }, - { url = "https://files.pythonhosted.org/packages/22/31/9b2ac8eee99e001eb6add9fa27514ef5e9faf176169057a12860af52704c/numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687", size = 15881470, upload-time = "2025-03-16T18:08:11.545Z" }, - { url = "https://files.pythonhosted.org/packages/f0/dc/8569b5f25ff30484b555ad8a3f537e0225d091abec386c9420cf5f7a2976/numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6", size = 18218144, upload-time = "2025-03-16T18:08:42.042Z" }, - { url = "https://files.pythonhosted.org/packages/5e/05/463c023a39bdeb9bb43a99e7dee2c664cb68d5bb87d14f92482b9f6011cc/numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09", size = 6606368, upload-time = "2025-03-16T18:08:55.074Z" }, - { url = "https://files.pythonhosted.org/packages/8b/72/10c1d2d82101c468a28adc35de6c77b308f288cfd0b88e1070f15b98e00c/numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91", size = 12947526, upload-time = "2025-03-16T18:09:16.844Z" }, - { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156, upload-time = "2025-03-16T18:09:51.975Z" }, - { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092, upload-time = "2025-03-16T18:10:16.329Z" }, - { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515, upload-time = "2025-03-16T18:10:26.19Z" }, - { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558, upload-time = "2025-03-16T18:10:38.996Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742, upload-time = "2025-03-16T18:11:02.76Z" }, - { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051, upload-time = "2025-03-16T18:11:32.767Z" }, - { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972, upload-time = "2025-03-16T18:11:59.877Z" }, - { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106, upload-time = "2025-03-16T18:12:31.487Z" }, - { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190, upload-time = "2025-03-16T18:12:44.46Z" }, - { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305, upload-time = "2025-03-16T18:13:06.864Z" }, - { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623, upload-time = "2025-03-16T18:13:43.231Z" }, - { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681, upload-time = "2025-03-16T18:14:08.031Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759, upload-time = "2025-03-16T18:14:18.613Z" }, - { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092, upload-time = "2025-03-16T18:14:31.386Z" }, - { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422, upload-time = "2025-03-16T18:14:54.83Z" }, - { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202, upload-time = "2025-03-16T18:15:22.035Z" }, - { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131, upload-time = "2025-03-16T18:15:48.546Z" }, - { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270, upload-time = "2025-03-16T18:16:20.274Z" }, - { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141, upload-time = "2025-03-16T18:20:15.297Z" }, - { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885, upload-time = "2025-03-16T18:20:36.982Z" }, - { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829, upload-time = "2025-03-16T18:16:56.191Z" }, - { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419, upload-time = "2025-03-16T18:17:22.811Z" }, - { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414, upload-time = "2025-03-16T18:17:34.066Z" }, - { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379, upload-time = "2025-03-16T18:17:47.466Z" }, - { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725, upload-time = "2025-03-16T18:18:11.904Z" }, - { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638, upload-time = "2025-03-16T18:18:40.749Z" }, - { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717, upload-time = "2025-03-16T18:19:04.512Z" }, - { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998, upload-time = "2025-03-16T18:19:32.52Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896, upload-time = "2025-03-16T18:19:43.55Z" }, - { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119, upload-time = "2025-03-16T18:20:03.94Z" }, - { url = "https://files.pythonhosted.org/packages/b2/5c/f09c33a511aff41a098e6ef3498465d95f6360621034a3d95f47edbc9119/numpy-2.2.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7051ee569db5fbac144335e0f3b9c2337e0c8d5c9fee015f259a5bd70772b7e8", size = 21081956, upload-time = "2025-03-16T18:21:12.955Z" }, - { url = "https://files.pythonhosted.org/packages/ba/30/74c48b3b6494c4b820b7fa1781d441e94d87a08daa5b35d222f06ba41a6f/numpy-2.2.4-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ab2939cd5bec30a7430cbdb2287b63151b77cf9624de0532d629c9a1c59b1d5c", size = 6827143, upload-time = "2025-03-16T18:21:26.748Z" }, - { url = "https://files.pythonhosted.org/packages/54/f5/ab0d2f48b490535c7a80e05da4a98902b632369efc04f0e47bb31ca97d8f/numpy-2.2.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f35b19894a9e08639fd60a1ec1978cb7f5f7f1eace62f38dd36be8aecdef4d", size = 16233350, upload-time = "2025-03-16T18:21:53.902Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3a/2f6d8c1f8e45d496bca6baaec93208035faeb40d5735c25afac092ec9a12/numpy-2.2.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b4adfbbc64014976d2f91084915ca4e626fbf2057fb81af209c1a6d776d23e3d", size = 12857565, upload-time = "2025-03-16T18:22:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, + { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, + { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, + { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, + { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, + { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, + { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, + { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, + { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, + { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, + { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, + { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, + { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, + { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, + { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, + { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, + { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, + { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, + { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, + { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, + { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, + { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, + { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, + { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, + { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, + { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, + { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, + { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, + { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, + { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, + { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, + { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, + { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, + { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, + { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, ] [[package]] name = "packaging" -version = "24.2" +version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +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/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, + { 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 = "psutil" -version = "7.0.0" +version = "7.1.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, - { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, - { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, - { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, + { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, + { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, + { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, + { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, + { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, + { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, + { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, + { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, + { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, ] [[package]] name = "pycparser" -version = "2.22" +version = "2.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] name = "pytest" -version = "8.3.5" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -221,11 +360,12 @@ dependencies = [ { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] @@ -238,7 +378,8 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "find-libpython" }, - { name = "numpy" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "psutil" }, { name = "pytest" }, ] @@ -257,39 +398,58 @@ dev = [ [[package]] name = "tomli" -version = "2.2.1" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +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/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, + { 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" }, ] From 8e0333d9affaa8b48d82c3aad28a521ecfdfad95 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 6 Dec 2025 19:26:07 +0100 Subject: [PATCH 151/194] Assign True instead of None to __clear_reentry_guard__ Not at all sure why this helps, but when assigning `None` instead, the object is gone at the time of garbage collection. --- src/runtime/Types/ClassBase.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 2d6ce8a47..17042ae63 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -392,7 +392,12 @@ internal static unsafe int BaseUnmanagedClear(BorrowedReference ob) using var dict = Runtime.PyObject_GenericGetDict(ob); if (Runtime.PyMapping_HasKey(dict.Borrow(), PyIdentifier.__clear_reentry_guard__) != 0) return 0; - int res = Runtime.PyDict_SetItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__, Runtime.None); + + int res = Runtime.PyDict_SetItem( + dict.Borrow(), + PyIdentifier.__clear_reentry_guard__, + Runtime.PyTrue + ); if (res != 0) return res; res = clear(ob); From cd108b89ee51b58c1a7b1924723d2c705a8139b2 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 6 Dec 2025 19:27:16 +0100 Subject: [PATCH 152/194] Disable the three remaining failing tests - The first two run into issues because our meta type's `tp_getattro` is not overridden by its subclasses - The last runs into a `PyErrOccurred` when trying to access its `__len__`, related to our sequence decoding logic --- tests/test_array.py | 1 + tests/test_class.py | 1 + tests/test_codec.py | 2 ++ 3 files changed, 4 insertions(+) diff --git a/tests/test_array.py b/tests/test_array.py index d207a36fb..890a7a126 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -1348,6 +1348,7 @@ def test_special_array_creation(): assert value.Length == 2 +@pytest.mark.skip def test_array_abuse(): """Test array abuse.""" _class = Test.PublicArrayTest diff --git a/tests/test_class.py b/tests/test_class.py index f63f05f4d..d03f09a00 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -236,6 +236,7 @@ def __setitem__(self, key, value): assert table.Count == 3 +@pytest.mark.skip def test_add_and_remove_class_attribute(): from System import TimeSpan diff --git a/tests/test_codec.py b/tests/test_codec.py index 0c1fb44f4..7205c0b7e 100644 --- a/tests/test_codec.py +++ b/tests/test_codec.py @@ -59,6 +59,7 @@ def test_iterable(): assert 3 == ob.GetLength2(iterable2) +@pytest.mark.skip def test_sequence(): Python.Runtime.Codecs.SequenceDecoder.Register() ob = ListConversionTester() @@ -70,6 +71,7 @@ def test_sequence(): assert 3 == ob.GetLength(tup2) +@pytest.mark.skip def test_list(): Python.Runtime.Codecs.SequenceDecoder.Register() ob = ListConversionTester() From 698bf009871a4eeb9fba6d263ca6ad5ee16e0a08 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 7 Dec 2025 12:26:56 +0100 Subject: [PATCH 153/194] Take the GIL in sequence and list wrappers --- src/runtime/CollectionWrappers/ListWrapper.cs | 3 +++ .../CollectionWrappers/SequenceWrapper.cs | 23 +++++++++++++------ tests/test_codec.py | 2 -- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/runtime/CollectionWrappers/ListWrapper.cs b/src/runtime/CollectionWrappers/ListWrapper.cs index 41ccb8fae..91c156c32 100644 --- a/src/runtime/CollectionWrappers/ListWrapper.cs +++ b/src/runtime/CollectionWrappers/ListWrapper.cs @@ -14,12 +14,14 @@ public T this[int index] { get { + using var _ = Py.GIL(); var item = Runtime.PyList_GetItem(pyObject, index); var pyItem = new PyObject(item); return pyItem.As()!; } set { + using var _ = Py.GIL(); var pyItem = value.ToPython(); var result = Runtime.PyList_SetItem(pyObject, index, new NewReference(pyItem).Steal()); if (result == -1) @@ -37,6 +39,7 @@ public void Insert(int index, T item) if (IsReadOnly) throw new InvalidOperationException("Collection is read-only"); + using var _ = Py.GIL(); var pyItem = item.ToPython(); int result = Runtime.PyList_Insert(pyObject, index, pyItem); diff --git a/src/runtime/CollectionWrappers/SequenceWrapper.cs b/src/runtime/CollectionWrappers/SequenceWrapper.cs index fcc5c23f4..feb0e515d 100644 --- a/src/runtime/CollectionWrappers/SequenceWrapper.cs +++ b/src/runtime/CollectionWrappers/SequenceWrapper.cs @@ -14,10 +14,14 @@ public int Count { get { - var size = Runtime.PySequence_Size(pyObject.Reference); - if (size == -1) + nint size = -1; { - Runtime.CheckExceptionOccurred(); + using var _ = Py.GIL(); + size = Runtime.PySequence_Size(pyObject.Reference); + if (size == -1) + { + Runtime.CheckExceptionOccurred(); + } } return checked((int)size); @@ -38,6 +42,7 @@ public void Clear() { if (IsReadOnly) throw new NotImplementedException(); + using var _ = Py.GIL(); int result = Runtime.PySequence_DelSlice(pyObject, 0, Count); if (result == -1) { @@ -77,12 +82,16 @@ protected bool removeAt(int index) if (index >= Count || index < 0) return false; - int result = Runtime.PySequence_DelItem(pyObject, index); - if (result == 0) - return true; + { + using var _ = Py.GIL(); + int result = Runtime.PySequence_DelItem(pyObject, index); + + if (result == 0) + return true; - Runtime.CheckExceptionOccurred(); + Runtime.CheckExceptionOccurred(); + } return false; } diff --git a/tests/test_codec.py b/tests/test_codec.py index 7205c0b7e..0c1fb44f4 100644 --- a/tests/test_codec.py +++ b/tests/test_codec.py @@ -59,7 +59,6 @@ def test_iterable(): assert 3 == ob.GetLength2(iterable2) -@pytest.mark.skip def test_sequence(): Python.Runtime.Codecs.SequenceDecoder.Register() ob = ListConversionTester() @@ -71,7 +70,6 @@ def test_sequence(): assert 3 == ob.GetLength(tup2) -@pytest.mark.skip def test_list(): Python.Runtime.Codecs.SequenceDecoder.Register() ob = ListConversionTester() From 908e13b664fe208b72b25773015cbc0e7ed97d78 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 7 Dec 2025 13:20:18 +0100 Subject: [PATCH 154/194] Move tp_clear workaround to .NET In Python 3.14, the objects __dict__ seems to already be half deconstructed, leading to crashes during garbage collection. Since gc in Python is single-threaded (I think :)), it should be fine to have a single static for this. If that is not true, we can always use a thread-local instead. --- src/runtime/Native/PyIdentifier_.cs | 3 --- src/runtime/Native/PyIdentifier_.tt | 1 - src/runtime/Types/ClassBase.cs | 26 +++++++++++--------------- tests/test_method.py | 3 --- tests/test_subclass.py | 1 - 5 files changed, 11 insertions(+), 23 deletions(-) diff --git a/src/runtime/Native/PyIdentifier_.cs b/src/runtime/Native/PyIdentifier_.cs index 1ea2b704c..bd58dc8d5 100644 --- a/src/runtime/Native/PyIdentifier_.cs +++ b/src/runtime/Native/PyIdentifier_.cs @@ -13,8 +13,6 @@ static class PyIdentifier public static BorrowedReference __doc__ => new(f__doc__); static IntPtr f__class__; public static BorrowedReference __class__ => new(f__class__); - static IntPtr f__clear_reentry_guard__; - public static BorrowedReference __clear_reentry_guard__ => new(f__clear_reentry_guard__); static IntPtr f__module__; public static BorrowedReference __module__ => new(f__module__); static IntPtr f__file__; @@ -51,7 +49,6 @@ static partial class InternString "__dict__", "__doc__", "__class__", - "__clear_reentry_guard__", "__module__", "__file__", "__slots__", diff --git a/src/runtime/Native/PyIdentifier_.tt b/src/runtime/Native/PyIdentifier_.tt index 9350cde43..5ac1288ac 100644 --- a/src/runtime/Native/PyIdentifier_.tt +++ b/src/runtime/Native/PyIdentifier_.tt @@ -7,7 +7,6 @@ "__dict__", "__doc__", "__class__", - "__clear_reentry_guard__", "__module__", "__file__", "__slots__", diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 17042ae63..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,26 +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.PyTrue - ); - 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/tests/test_method.py b/tests/test_method.py index 175556106..2312d5bb5 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -942,7 +942,6 @@ def test_getting_generic_method_binding_does_not_leak_ref_count(): refCount = sys.getrefcount(PlainOldClass().GenericMethod[str]) assert refCount == 1 -@pytest.mark.skipif(sys.version_info >= (3, 14), reason="Test skipped on Python 3.14 and above") def test_getting_generic_method_binding_does_not_leak_memory(): """Test that managed object is freed after calling generic method. Issue #691""" @@ -984,7 +983,6 @@ def test_getting_overloaded_method_binding_does_not_leak_ref_count(): refCount = sys.getrefcount(PlainOldClass().OverloadedMethod.Overloads[int]) assert refCount == 1 -@pytest.mark.skipif(sys.version_info >= (3, 14), reason="Test skipped on Python 3.14 and above") def test_getting_overloaded_method_binding_does_not_leak_memory(): """Test that managed object is freed after calling overloaded method. Issue #691""" @@ -1027,7 +1025,6 @@ def test_getting_method_overloads_binding_does_not_leak_ref_count(): assert refCount == 1 @pytest.mark.xfail(reason="Fails locally, need to investigate later", strict=False) -@pytest.mark.skipif(sys.version_info >= (3, 14), reason="Test skipped on Python 3.14 and above") def test_getting_method_overloads_binding_does_not_leak_memory(): """Test that managed object is freed after calling overloaded method. Issue #691""" diff --git a/tests/test_subclass.py b/tests/test_subclass.py index 43f4b44bb..a0821b9a4 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -304,7 +304,6 @@ def __init__(self, i, s): assert calls[0][1] == "foo" # regression test for https://github.com/pythonnet/pythonnet/issues/1565 -@pytest.mark.skipif(sys.version_info >= (3, 14), reason="Test skipped on Python 3.14 and above") def test_can_be_collected_by_gc(): from Python.Test import BaseClass From c851b3af9e3f2dc3987f4e30606870b9467ca0a9 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 7 Dec 2025 14:08:29 +0100 Subject: [PATCH 155/194] Skip coreclr embedded tests for now --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e1507279e..cc572278a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -78,7 +78,7 @@ jobs: - name: Embedding tests (.NET Core) run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net8.0 --logger "console;verbosity=detailed" src/embed_tests/ - if: always() + if: false - name: Python Tests (Mono) if: ${{ matrix.os.category != 'windows' }} From 08550d090a88f91a84b028208cf57a8a3b9c1b58 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 7 Dec 2025 14:54:44 +0100 Subject: [PATCH 156/194] Workaround for blocked PyObject_GenericSetAttr in metatypes Python 3.14 introduced a new assertion that prevents us from using PyObject_GenericSetAttr directly in our meta type. To work around this, we manipulate the type dict directly. This workaround is a simplified variant of Cython's workaround from https://github.com/cython/cython/pull/6325. The relevant Python change is in https://github.com/python/cpython/pull/118454 --- src/runtime/Native/PyIdentifier_.cs | 3 ++ src/runtime/Native/PyIdentifier_.tt | 1 + src/runtime/Types/MetaType.cs | 44 ++++++++++++++++++++++++++++- tests/test_array.py | 1 - tests/test_class.py | 1 - 5 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/runtime/Native/PyIdentifier_.cs b/src/runtime/Native/PyIdentifier_.cs index bd58dc8d5..8d693c09f 100644 --- a/src/runtime/Native/PyIdentifier_.cs +++ b/src/runtime/Native/PyIdentifier_.cs @@ -22,6 +22,8 @@ static class PyIdentifier static IntPtr f__self__; public static BorrowedReference __self__ => new(f__self__); static IntPtr f__annotations__; + public static BorrowedReference __dictoffset__ => new(f__dictoffset__); + static IntPtr f__dictoffset__; public static BorrowedReference __annotations__ => new(f__annotations__); static IntPtr f__init__; public static BorrowedReference __init__ => new(f__init__); @@ -54,6 +56,7 @@ static partial class InternString "__slots__", "__self__", "__annotations__", + "__dictoffset__", "__init__", "__repr__", "__import__", diff --git a/src/runtime/Native/PyIdentifier_.tt b/src/runtime/Native/PyIdentifier_.tt index 5ac1288ac..72dc1d70e 100644 --- a/src/runtime/Native/PyIdentifier_.tt +++ b/src/runtime/Native/PyIdentifier_.tt @@ -12,6 +12,7 @@ "__slots__", "__self__", "__annotations__", + "__dictoffset__", "__init__", "__repr__", 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/tests/test_array.py b/tests/test_array.py index 890a7a126..d207a36fb 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -1348,7 +1348,6 @@ def test_special_array_creation(): assert value.Length == 2 -@pytest.mark.skip def test_array_abuse(): """Test array abuse.""" _class = Test.PublicArrayTest diff --git a/tests/test_class.py b/tests/test_class.py index d03f09a00..f63f05f4d 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -236,7 +236,6 @@ def __setitem__(self, key, value): assert table.Count == 3 -@pytest.mark.skip def test_add_and_remove_class_attribute(): from System import TimeSpan From f1d90f3b770afaff433bb1d7a17fa4606c223c28 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 7 Dec 2025 14:59:10 +0100 Subject: [PATCH 157/194] Revert changes to tests --- tests/test_method.py | 1 - tests/test_subclass.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/test_method.py b/tests/test_method.py index 2312d5bb5..c70200c7e 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -2,7 +2,6 @@ """Test CLR method support.""" -import sys import System import pytest from Python.Test import MethodTest diff --git a/tests/test_subclass.py b/tests/test_subclass.py index a0821b9a4..c6ab7650f 100644 --- a/tests/test_subclass.py +++ b/tests/test_subclass.py @@ -6,7 +6,6 @@ """Test sub-classing managed types""" -import sys import System import pytest from Python.Test import (IInterfaceTest, SubClassTest, EventArgsTest, From a47555d853e10a7d117c01a6ed4387e23cc3f4c1 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Mon, 8 Dec 2025 20:35:41 +0100 Subject: [PATCH 158/194] Bump macos image version and add arm64 --- .github/workflows/main.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cc572278a..7ce1015ed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,7 +36,11 @@ jobs: - category: macos platform: x64 - instance: macos-13 + instance: macos-14 + + - category: macos + platform: arm64 + instance: macos-14-arm64 python: ["3.10", "3.11", "3.12", "3.13", "3.14"] From cebfd15cd33b1e765e657b55701d865a66f31b98 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 9 Dec 2025 08:09:44 +0100 Subject: [PATCH 159/194] Reenable .NET Core embedding tests --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7ce1015ed..e5e291169 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -82,7 +82,7 @@ jobs: - name: Embedding tests (.NET Core) run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net8.0 --logger "console;verbosity=detailed" src/embed_tests/ - if: false + if: always() - name: Python Tests (Mono) if: ${{ matrix.os.category != 'windows' }} From fc6c9e9f79d71e2a99a9c50a594127509fb5ff8d Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 25 Feb 2026 11:48:07 +0100 Subject: [PATCH 160/194] First release candidate for 3.1.0 (Python 3.14 support) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 3b81faa01a834b238eea6b89f095cc0e5981c20e Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Fri, 27 Feb 2026 08:16:04 +0100 Subject: [PATCH 161/194] Update trove classifiers to reflect Python compatibility --- pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 664df8b01..62b47dae2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,13 +20,11 @@ classifiers = [ "Intended Audience :: Developers", "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", From 1073cd167920319beee93d4e49f0be512ba7392b Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 25 Mar 2026 22:48:35 +0100 Subject: [PATCH 162/194] CI Improvements (#2669) * Be explicit about Python version to use * Use generic package installer * Use workspace cache for Nuget * Reorder steps * Disable always() on embed tests and reenable Mono on Windows * Use custom install-mono * Disable 32bit tests again, require changes to setup-dotnet * Try with arch * Temporarily add upterm to ssh into macos node * Explicitly install brew on x64 macos * Unconditionally start upterm on macos * Add more caching to the mono installation * Use custom mono install action * Bump locked dependencies * Reenable Windows x86 tests * Remove win/x86/3.10 case and try to run all test suites * Bump C# dependencies * Bump clr-loader dependency * Disable test for now * Remove the same versions from CI as in clr-loader * Drop py3.10 win x86 test * Increase threshold on memleak test --- .github/actions/install-mono/action.yml | 78 +++++ .github/workflows/main.yml | 93 ++++-- Directory.Build.props | 4 +- pyproject.toml | 2 +- src/embed_tests/Codecs.cs | 1 + src/embed_tests/Python.EmbeddingTest.csproj | 4 +- .../Python.PythonTestsRunner.csproj | 2 +- src/runtime/Python.Runtime.csproj | 2 +- .../Python.DomainReloadTests.csproj | 8 +- tests/test_method.py | 5 +- uv.lock | 309 +++++++++--------- 11 files changed, 316 insertions(+), 192 deletions(-) create mode 100644 .github/actions/install-mono/action.yml diff --git a/.github/actions/install-mono/action.yml b/.github/actions/install-mono/action.yml new file mode 100644 index 000000000..f414afdc7 --- /dev/null +++ b/.github/actions/install-mono/action.yml @@ -0,0 +1,78 @@ +name: 'Install Mono' +description: 'Install Mono' +branding: + icon: package + color: blue +inputs: + arch: + description: Architecture to install for + required: true +runs: + using: "composite" + steps: + # Windows Cache + - name: Cache setup on Windows + if: runner.os == 'Windows' + run: | + mkdir -p .choco-cache + choco config set cacheLocation $(pwd)/.choco-cache + shell: bash + + - name: Cache on Windows + if: runner.os == 'Windows' + uses: actions/cache@v5 + with: + path: .choco-cache + key: mono-${{ runner.os }}-${{ inputs.arch }} + + # macOS Cache + - name: Set Homebrew Cache Path + if: runner.os == 'macOS' + run: | + mkdir -p .brew-cache + echo "HOMEBREW_CACHE=$(pwd)/.brew-cache" >> $GITHUB_ENV + shell: bash + + - name: Cache Homebrew on macOS + if: runner.os == 'macOS' + uses: actions/cache@v5 + with: + path: .brew-cache + key: mono-brew-${{ runner.os }}-${{ inputs.arch }} + + # =================================== + + - name: Install on Linux + if: runner.os == 'Linux' + uses: awalsh128/cache-apt-pkgs-action@v1 + with: + packages: mono-runtime-sgen + + # =================================== + + - name: Install on Windows (x86) + if: runner.os == 'Windows' && inputs.arch == 'x86' + run: choco install --x86 -y mono + shell: sh + + - name: Install on Windows + if: runner.os == 'Windows' && inputs.arch != 'x86' + run: choco install -y mono + shell: sh + + # =================================== + # + - name: Install on macOS (x86_64) + if: runner.os == 'macOS' && inputs.arch == 'x64' + run: | + arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + arch -x86_64 /usr/local/bin/brew install --ignore-dependencies mono + shell: sh + + - name: Install on macOS (arm64) + if: runner.os == 'macOS' && inputs.arch != 'x64' + run: | + brew update + brew install --ignore-dependencies mono + shell: sh + diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2686b6fbd..b2fd96863 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,54 +16,95 @@ jobs: fail-fast: false matrix: os: - # Disabled for now, will require some work (#2653) - # - # - category: windows - # platform: x86 - # instance: windows-latest + - 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-14 + instance: macos-15 + suffix: -macos-x86_64-none - category: macos platform: arm64 - instance: macos-14-arm64 + instance: macos-15 + suffix: -macos-aarch64-none python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + exclude: + # Fails with initfs_encoding error + - os: + category: windows + platform: x86 + python: "3.10" + + # Broken ctypes find_library + - os: + category: macos + platform: arm64 + python: '3.10' + - os: + category: macos + platform: arm64 + python: '3.11' + - os: + category: macos + platform: arm64 + python: '3.12' + + # 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: Set Environment on macOS - uses: maxim-lobanov/setup-xamarin@v1 - if: ${{ matrix.os.category == 'macos' }} - with: - mono-version: latest - - name: Checkout code uses: actions/checkout@v6 + # Use main until support for architecture lands - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@main + with: + dotnet-version: '10.0' + architecture: ${{ matrix.os.platform }} + + - run: dotnet restore + + - name: Install Mono + uses: ./.github/actions/install-mono with: - dotnet-version: '10.0.x' + arch: ${{ matrix.os.platform }} - name: Set up Python ${{ matrix.python }} uses: astral-sh/setup-uv@v7 with: - architecture: ${{ matrix.os.platform }} - python-version: ${{ matrix.python }} + python-version: cpython-${{ matrix.python }}${{ matrix.os.suffix }} cache-python: true activate-environment: true enable-cache: true @@ -71,33 +112,27 @@ jobs: - name: Synchronize the virtual environment run: uv sync --managed-python - - name: Show pyvenv.cfg - run: cat .venv/pyvenv.cfg - - name: Embedding tests (Mono/.NET Framework) - run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net472 --logger "console;verbosity=detailed" src/embed_tests/ 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: Embedding tests (.NET Core) + if: always() run: dotnet test --runtime any-${{ matrix.os.platform }} --framework net10.0 --logger "console;verbosity=detailed" src/embed_tests/ + + - name: Python Tests (.NET Core) if: always() + run: pytest --runtime coreclr - name: Python Tests (Mono) - if: ${{ matrix.os.category != 'windows' }} + if: always() run: pytest --runtime mono - - name: Python Tests (.NET Core) - run: pytest --runtime coreclr - - name: Python Tests (.NET Framework) if: ${{ matrix.os.category == 'windows' }} run: pytest --runtime netfx - name: Python tests run from .NET - # For some reason, it won't find pytest on the Windows + 3.10 - # combination, which hints that it does not handle the venv properly in - # this combination. - if: ${{ matrix.os.category != 'windows' || matrix.python != '3.10' }} run: dotnet test --runtime any-${{ matrix.os.platform }} src/python_tests_runner/ diff --git a/Directory.Build.props b/Directory.Build.props index 85e4039b9..9b6f9555a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,8 +12,8 @@ $(MSBuildThisFileDirectory) - - + + all runtime; build; native; contentfiles; analyzers diff --git a/pyproject.toml b/pyproject.toml index 62b47dae2..59d4d107a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ license = "MIT" readme = "README.rst" dependencies = [ - "clr_loader>=0.2.7,<0.3.0" + "clr_loader>=0.3.0,<0.4.0" ] requires-python = ">=3.10, <3.15" diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index d4d22dcac..5879462f5 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -280,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/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index b3e7fe86e..15258fc83 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -29,13 +29,13 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive - 1.0.0 + 1.* all runtime; build; native; contentfiles; analyzers diff --git a/src/python_tests_runner/Python.PythonTestsRunner.csproj b/src/python_tests_runner/Python.PythonTestsRunner.csproj index 80e8c0071..9f782bd2b 100644 --- a/src/python_tests_runner/Python.PythonTestsRunner.csproj +++ b/src/python_tests_runner/Python.PythonTestsRunner.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 307b2c3ad..3e545a325 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -60,7 +60,7 @@ - + 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..da37afd88 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -967,8 +967,9 @@ 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 75% 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.75 assert bytesLeakedPerIteration < failThresholdBytesLeakedPerIteration diff --git a/uv.lock b/uv.lock index 25a53fd51..b5230d788 100644 --- a/uv.lock +++ b/uv.lock @@ -90,14 +90,14 @@ wheels = [ [[package]] name = "clr-loader" -version = "0.2.9" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/c2/da52aaf19424e3f0abec003d08dd1ccae52c88a3b41e31151a03bed18488/clr_loader-0.2.9.tar.gz", hash = "sha256:6af3d582c3de55ce9e9e676d2b3dbf6bc680c4ea8f76c58786739a5bdcf6b52d", size = 84829, upload-time = "2025-12-05T16:57:12.466Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/56/0fb4f734a5b2574b9b75157eabef64e5e2ceaf44b759306034e8b1452e62/clr_loader-0.3.0.tar.gz", hash = "sha256:b880e0821cdc18f9bf9f05e5130e966cc78fa75edc7432baf4fa4711e8412b05", size = 84710, upload-time = "2026-03-03T00:41:51.314Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/ba/7d6e6bdeee4e218a35a78b00f6fae24ef5b475dde293baffff30b92a67ff/clr_loader-0.2.9-py3-none-any.whl", hash = "sha256:7ef4f1280a5d3a4e19a8b21901b5fd804e104a1c40d755bcca0a4f694cb1b726", size = 56512, upload-time = "2025-12-05T16:57:10.811Z" }, + { url = "https://files.pythonhosted.org/packages/a2/07/6c965da95ef2b7410f1314cdfe462efdf9722bfd7fbfe6945564b8b0467a/clr_loader-0.3.0-py3-none-any.whl", hash = "sha256:d918467eb1077d23b48b0b7e9b6379e8fbf20b573832839a41cec1e06dad2beb", size = 57431, upload-time = "2026-03-03T00:41:06.554Z" }, ] [[package]] @@ -123,11 +123,11 @@ wheels = [ [[package]] name = "find-libpython" -version = "0.5.0" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/46/c466b94830bb77ef1e715d869246b9f8e111f9b2f4de2c60d4de1b986779/find_libpython-0.5.0.tar.gz", hash = "sha256:4e4e0ffcad3bfaf2af9461b359329b8736e3f721dc375da7c167aff383e56be1", size = 9364, upload-time = "2025-10-04T19:50:32.499Z" } +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/a7/3f/0eb94bfca99e54404901536ea8c80ddacff4953257514c6b8fb01f9a75a8/find_libpython-0.5.0-py3-none-any.whl", hash = "sha256:7690dcf6442cdce39c0df191903fd5ecf9af437fa920effb6569fbf2c8ca8ab4", size = 9194, upload-time = "2025-10-04T19:50:31.229Z" }, + { 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]] @@ -206,95 +206,93 @@ wheels = [ [[package]] name = "numpy" -version = "2.3.5" +version = "2.4.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", ] -sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, - { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, - { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, - { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, - { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, - { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, - { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, - { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, - { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, - { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, - { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, - { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, - { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, - { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, - { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, - { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, - { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, - { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, - { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, - { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, - { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, - { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, - { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, - { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, - { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, - { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, - { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, - { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, - { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, - { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, - { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, - { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, - { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, - { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, - { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, - { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, - { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, - { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, - { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, - { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, - { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, - { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, - { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, - { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, - { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, - { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, - { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, - { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, - { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, + { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, + { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, + { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, + { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, + { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, + { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, + { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, + { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, + { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, + { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, ] [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] @@ -308,37 +306,39 @@ wheels = [ [[package]] name = "psutil" -version = "7.1.3" +version = "7.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, - { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, - { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, - { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, - { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, - { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, - { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, - { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, - { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, - { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, - { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, - { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, - { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +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/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { 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]] @@ -379,13 +379,13 @@ 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.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "psutil" }, { name = "pytest" }, ] [package.metadata] -requires-dist = [{ name = "clr-loader", specifier = ">=0.2.7,<0.3.0" }] +requires-dist = [{ name = "clr-loader", specifier = ">=0.3.0,<0.4.0" }] [package.metadata.requires-dev] dev = [ @@ -398,51 +398,56 @@ dev = [ [[package]] name = "tomli" -version = "2.3.0" +version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, - { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, - { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, - { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, - { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, - { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, - { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, - { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, - { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, - { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, - { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, - { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, - { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, - { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, - { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, - { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, - { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, - { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, - { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, - { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, - { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, - { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, - { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, - { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, - { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, - { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, ] [[package]] From b861f4efa49d12035dda053cd8d477542125e6b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 07:15:04 +0100 Subject: [PATCH 163/194] Bump System.Reflection.Emit from 4.3.0 to 4.7.0 (#2694) --- updated-dependencies: - dependency-name: System.Reflection.Emit dependency-version: 4.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/runtime/Python.Runtime.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 3e545a325..291da366b 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -61,6 +61,6 @@ - + From e4b51952f119650d3b83a302e30001c2529411ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 14:37:28 +0200 Subject: [PATCH 164/194] Bump pytest from 9.0.2 to 9.0.3 in the uv group across 1 directory (#2705) Bumps the uv group with 1 update in the / directory: [pytest](https://github.com/pytest-dev/pytest). Updates `pytest` from 9.0.2 to 9.0.3 - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/9.0.2...9.0.3) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.3 dependency-type: direct:development dependency-group: uv ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index b5230d788..112218914 100644 --- a/uv.lock +++ b/uv.lock @@ -352,7 +352,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.0.2" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -363,9 +363,9 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +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/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + { 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]] From daa4cddaf9b1eb7a3979145bd53d1e1cd03ec153 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 18 Apr 2026 15:56:44 +0200 Subject: [PATCH 165/194] Upgrade lockfile --- uv.lock | 256 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 128 insertions(+), 128 deletions(-) diff --git a/uv.lock b/uv.lock index 112218914..8f5ccb4c4 100644 --- a/uv.lock +++ b/uv.lock @@ -206,93 +206,93 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.2" +version = "2.4.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", ] -sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +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/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, - { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, - { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, - { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, - { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, - { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, - { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, - { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, - { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, - { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, - { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, - { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, - { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, - { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, - { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, - { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, - { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, - { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, - { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, - { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, - { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, - { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, - { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, - { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, - { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, - { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, - { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, - { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, - { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, - { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, - { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, - { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, - { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, - { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, - { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, - { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, - { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, - { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, - { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, - { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, - { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, - { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, - { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, - { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, - { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, - { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, - { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, - { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, - { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, - { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, - { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, - { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, - { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, + { 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.0" +version = "26.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +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/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { 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]] @@ -343,11 +343,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +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/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { 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]] @@ -379,7 +379,7 @@ 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.2", 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 = "psutil" }, { name = "pytest" }, ] @@ -398,56 +398,56 @@ dev = [ [[package]] name = "tomli" -version = "2.4.0" +version = "2.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } +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/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, - { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, - { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, - { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, - { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, - { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, - { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, - { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, - { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, - { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, - { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, - { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, - { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, - { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, - { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, - { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, - { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, - { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, - { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, - { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, - { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, - { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, - { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, - { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, - { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, - { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, - { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, - { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, + { 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]] From 310fb6cd4daad0d8a134d4f136fad4912249ff6c Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 18 Apr 2026 19:54:26 +0200 Subject: [PATCH 166/194] Update clr-loader for better find_mono --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 59d4d107a..fce85989e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ license = "MIT" readme = "README.rst" dependencies = [ - "clr_loader>=0.3.0,<0.4.0" + "clr_loader>=0.3.1,<0.4.0" ] requires-python = ">=3.10, <3.15" diff --git a/uv.lock b/uv.lock index 8f5ccb4c4..6ae72a583 100644 --- a/uv.lock +++ b/uv.lock @@ -90,14 +90,14 @@ wheels = [ [[package]] name = "clr-loader" -version = "0.3.0" +version = "0.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/56/0fb4f734a5b2574b9b75157eabef64e5e2ceaf44b759306034e8b1452e62/clr_loader-0.3.0.tar.gz", hash = "sha256:b880e0821cdc18f9bf9f05e5130e966cc78fa75edc7432baf4fa4711e8412b05", size = 84710, upload-time = "2026-03-03T00:41:51.314Z" } +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/a2/07/6c965da95ef2b7410f1314cdfe462efdf9722bfd7fbfe6945564b8b0467a/clr_loader-0.3.0-py3-none-any.whl", hash = "sha256:d918467eb1077d23b48b0b7e9b6379e8fbf20b573832839a41cec1e06dad2beb", size = 57431, upload-time = "2026-03-03T00:41:06.554Z" }, + { 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]] @@ -385,7 +385,7 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "clr-loader", specifier = ">=0.3.0,<0.4.0" }] +requires-dist = [{ name = "clr-loader", specifier = ">=0.3.1,<0.4.0" }] [package.metadata.requires-dev] dev = [ From 9a81dd4575da398ef79865b1a06f0b3ad1831b17 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 18 Apr 2026 19:54:45 +0200 Subject: [PATCH 167/194] Drop local install-mono copy --- .github/actions/install-mono/action.yml | 78 ------------------------- .github/workflows/main.yml | 16 +---- 2 files changed, 1 insertion(+), 93 deletions(-) delete mode 100644 .github/actions/install-mono/action.yml diff --git a/.github/actions/install-mono/action.yml b/.github/actions/install-mono/action.yml deleted file mode 100644 index f414afdc7..000000000 --- a/.github/actions/install-mono/action.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: 'Install Mono' -description: 'Install Mono' -branding: - icon: package - color: blue -inputs: - arch: - description: Architecture to install for - required: true -runs: - using: "composite" - steps: - # Windows Cache - - name: Cache setup on Windows - if: runner.os == 'Windows' - run: | - mkdir -p .choco-cache - choco config set cacheLocation $(pwd)/.choco-cache - shell: bash - - - name: Cache on Windows - if: runner.os == 'Windows' - uses: actions/cache@v5 - with: - path: .choco-cache - key: mono-${{ runner.os }}-${{ inputs.arch }} - - # macOS Cache - - name: Set Homebrew Cache Path - if: runner.os == 'macOS' - run: | - mkdir -p .brew-cache - echo "HOMEBREW_CACHE=$(pwd)/.brew-cache" >> $GITHUB_ENV - shell: bash - - - name: Cache Homebrew on macOS - if: runner.os == 'macOS' - uses: actions/cache@v5 - with: - path: .brew-cache - key: mono-brew-${{ runner.os }}-${{ inputs.arch }} - - # =================================== - - - name: Install on Linux - if: runner.os == 'Linux' - uses: awalsh128/cache-apt-pkgs-action@v1 - with: - packages: mono-runtime-sgen - - # =================================== - - - name: Install on Windows (x86) - if: runner.os == 'Windows' && inputs.arch == 'x86' - run: choco install --x86 -y mono - shell: sh - - - name: Install on Windows - if: runner.os == 'Windows' && inputs.arch != 'x86' - run: choco install -y mono - shell: sh - - # =================================== - # - - name: Install on macOS (x86_64) - if: runner.os == 'macOS' && inputs.arch == 'x64' - run: | - arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - arch -x86_64 /usr/local/bin/brew install --ignore-dependencies mono - shell: sh - - - name: Install on macOS (arm64) - if: runner.os == 'macOS' && inputs.arch != 'x64' - run: | - brew update - brew install --ignore-dependencies mono - shell: sh - diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b2fd96863..8ead06fba 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,20 +55,6 @@ jobs: platform: x86 python: "3.10" - # Broken ctypes find_library - - os: - category: macos - platform: arm64 - python: '3.10' - - os: - category: macos - platform: arm64 - python: '3.11' - - os: - category: macos - platform: arm64 - python: '3.12' - # Fails to find pytest - os: category: windows @@ -97,7 +83,7 @@ jobs: - run: dotnet restore - name: Install Mono - uses: ./.github/actions/install-mono + uses: pythonnet/clr-loader/.github/actions/install-mono@main with: arch: ${{ matrix.os.platform }} From da22396bddc07bea84ea05a5c2668ba80c33f8a4 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 18 Apr 2026 19:56:10 +0200 Subject: [PATCH 168/194] Update action --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3937d85e0..e892009f2 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -37,4 +37,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 From a67d1a0555ed6d81aa40d6d1da4a4f1898011fe8 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 19 Apr 2026 17:18:38 +0200 Subject: [PATCH 169/194] Fix method memleak test (#2708) * Use uss instead of rss to get more precise memory readings * Rework memory usage tracking to use .NET and Python memory tracing * Use 75% threshold in all cases * Up to 90% :( --- pyproject.toml | 1 - tests/test_method.py | 224 ++++++++++++++++++++++++------------------- uv.lock | 30 ------ 3 files changed, 125 insertions(+), 130 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fce85989e..be33a6afb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,6 @@ dev = [ "find_libpython >= 0.3", "numpy >=2 ; python_version >= '3.10'", "numpy <2 ; python_version < '3.10'", - "psutil" ] [[project.authors]] diff --git a/tests/test_method.py b/tests/test_method.py index da37afd88..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,32 +983,32 @@ def test_getting_generic_method_binding_does_not_leak_memory(): bytesAllocatedPerIteration = pow(2, 20) # 1MB bytesLeakedPerIteration = processBytesDelta / iterations - # Allow 75% threshold - this shows the original issue is fixed, which leaks the full allocated bytes per iteration + # 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.75 + 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): @@ -1001,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)) @@ -1014,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""" @@ -1024,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): @@ -1043,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)) @@ -1051,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 @@ -1070,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) @@ -1101,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) @@ -1140,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() @@ -1155,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() @@ -1178,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" @@ -1209,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" @@ -1244,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) @@ -1252,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/uv.lock b/uv.lock index 6ae72a583..668679574 100644 --- a/uv.lock +++ b/uv.lock @@ -304,34 +304,6 @@ 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 = "psutil" -version = "7.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, - { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, - { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, - { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, - { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, - { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, - { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, - { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, - { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, - { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, - { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, -] - [[package]] name = "pycparser" version = "3.0" @@ -380,7 +352,6 @@ 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 = "psutil" }, { name = "pytest" }, ] @@ -392,7 +363,6 @@ 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 = "psutil" }, { name = "pytest", specifier = ">=6" }, ] From 10f73e6cc4c8dd96bd77dbe238c35a452b9f2273 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 5 May 2026 17:25:28 +0200 Subject: [PATCH 170/194] Adjust remaining memory leak test to 90% limit --- tests/test_method.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_method.py b/tests/test_method.py index 8bba32a3f..7820457d5 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -1025,8 +1025,8 @@ def test_getting_overloaded_method_binding_does_not_leak_memory(memory_usage_tra 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 From 36c6f7298cb96a0633f84499308e85c14001cebb Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 5 May 2026 17:59:59 +0200 Subject: [PATCH 171/194] Exclude PDB from release build and use deterministic paths --- Directory.Build.props | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Directory.Build.props b/Directory.Build.props index 9b6f9555a..4b0f25d56 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,11 +6,16 @@ Python.NET 12.0 false + true $([System.IO.File]::ReadAllText("$(MSBuildThisFileDirectory)version.txt").Trim()) $(FullVersion.Split('-', 2)[0]) $(FullVersion.Split('-', 2)[1]) $(MSBuildThisFileDirectory) + + False + None + From 76fe6d8d19a3e91345cdbef2efef1b24ca402870 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 18:01:04 +0200 Subject: [PATCH 172/194] Bump actions/upload-pages-artifact from 4 to 5 (#2709) Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-pages-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e892009f2..f7001aab5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -19,7 +19,7 @@ jobs: - name: Upload artifact # Automatically uploads an artifact from the './_site' directory by default - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@v5 with: path: doc/build/html/ From 95e6b89c17f3c21d3c851b6cbe6e9e94b9703f75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 18:01:23 +0200 Subject: [PATCH 173/194] Update furo requirement from >=2022.9.15 to >=2025.12.19 (#2711) Updates the requirements on [furo](https://github.com/pradyunsg/furo) to permit the latest version. - [Release notes](https://github.com/pradyunsg/furo/releases) - [Changelog](https://github.com/pradyunsg/furo/blob/main/docs/changelog.md) - [Commits](https://github.com/pradyunsg/furo/compare/2022.09.15...2025.12.19) --- updated-dependencies: - dependency-name: furo dependency-version: 2025.12.19 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 8ef3b7159..d82067e2c 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,7 +1,7 @@ sphinx # Theme, force pygments update -furo>=2022.9.15 +furo>=2025.12.19 pygments>=2.7 # C# via doxygen From 39c575ed2c3e50fd4744455668338f69b257b641 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 5 May 2026 18:16:39 +0200 Subject: [PATCH 174/194] Move documentation deps to pyproject.toml (#2714) --- .github/workflows/docs.yml | 14 +- doc/Makefile | 20 -- doc/make.bat | 35 --- doc/requirements.txt | 12 - pyproject.toml | 7 + uv.lock | 573 ++++++++++++++++++++++++++++++++++++- 6 files changed, 590 insertions(+), 71 deletions(-) delete mode 100644 doc/Makefile delete mode 100644 doc/make.bat delete mode 100644 doc/requirements.txt diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f7001aab5..60c94dbeb 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -7,15 +7,25 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - name: Set up Python + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.12" + cache-python: true + activate-environment: true + enable-cache: true + - name: Doxygen Action uses: mattnotmitt/doxygen-action@1.12.0 with: working-directory: "doc/" + - name: Synchronize the virtual environment + run: uv sync --managed-python --no-dev --group doc + - name: Build Sphinx documentation run: | - pip install -r doc/requirements.txt - sphinx-build doc/source/ ./doc/build/html/ + uv run sphinx-build doc/source/ ./doc/build/html/ - name: Upload artifact # Automatically uploads an artifact from the './_site' directory by default diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index d0c3cbf10..000000000 --- a/doc/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/make.bat b/doc/make.bat deleted file mode 100644 index dc1312ab0..000000000 --- a/doc/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/doc/requirements.txt b/doc/requirements.txt deleted file mode 100644 index d82067e2c..000000000 --- a/doc/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -sphinx - -# Theme, force pygments update -furo>=2025.12.19 -pygments>=2.7 - -# C# via doxygen -breathe -git+https://github.com/rogerbarton/sphinx-csharp.git - -# Dependency of pythonnet, needed for autodocs -clr_loader diff --git a/pyproject.toml b/pyproject.toml index be33a6afb..75df0f072 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,13 @@ dev = [ "numpy >=2 ; python_version >= '3.10'", "numpy <2 ; python_version < '3.10'", ] +doc = [ + "sphinx", + "furo>=2025.12.19", + "pygments>=2.20", + "breathe", + "sphinx-csharp @ git+https://github.com/rogerbarton/sphinx-csharp.git", +] [[project.authors]] name = "The Contributors of the Python.NET Project" diff --git a/uv.lock b/uv.lock index 668679574..e1e44730d 100644 --- a/uv.lock +++ b/uv.lock @@ -2,10 +2,77 @@ version = 1 revision = 3 requires-python = ">=3.10, <3.15" resolution-markers = [ - "python_full_version >= '3.11'", + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", "python_full_version < '3.11'", ] +[[package]] +name = "accessible-pygments" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[package]] +name = "breathe" +version = "4.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/56/99bf7d0799d95ad485d95596dc01c2a5b3dda58ebf50a94f6f73b33bacdf/breathe-4.36.0.tar.gz", hash = "sha256:14860b73118ac140b7a3f55446890c777d1b67149cb024279fe3710dad7f535c", size = 154842, upload-time = "2025-02-22T18:36:03.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/bc/d67ef1e11ed6e6343c135bf605aa9d5734ff0cc77eb42a2a41f182abc9d9/breathe-4.36.0-py3-none-any.whl", hash = "sha256:af85436f1f09e842bd1fd95617281211c635f8768d245ff830c59b979888d1d5", size = 97231, upload-time = "2025-02-22T18:36:01.087Z" }, +] + +[[package]] +name = "certifi" +version = "2026.4.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, +] + [[package]] name = "cffi" version = "2.0.0" @@ -88,6 +155,111 @@ wheels = [ { 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 = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, + { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, + { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, + { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, + { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, + { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + [[package]] name = "clr-loader" version = "0.3.1" @@ -109,6 +281,31 @@ 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 = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + [[package]] name = "exceptiongroup" version = "1.3.1" @@ -130,6 +327,42 @@ 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 = "furo" +version = "2025.12.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accessible-pygments" }, + { name = "beautifulsoup4" }, + { name = "pygments" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sphinx-basic-ng" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/20/5f5ad4da6a5a27c80f2ed2ee9aee3f9e36c66e56e21c00fde467b2f8f88f/furo-2025.12.19.tar.gz", hash = "sha256:188d1f942037d8b37cd3985b955839fea62baa1730087dc29d157677c857e2a7", size = 1661473, upload-time = "2025-12-19T17:34:40.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" }, +] + +[[package]] +name = "idna" +version = "3.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -139,6 +372,103 @@ 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 = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + [[package]] name = "numpy" version = "2.2.6" @@ -209,7 +539,8 @@ name = "numpy" version = "2.4.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.11'", + "python_full_version >= '3.12'", + "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 = [ @@ -354,6 +685,15 @@ dev = [ { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pytest" }, ] +doc = [ + { name = "breathe" }, + { name = "furo" }, + { name = "pygments" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sphinx-csharp" }, +] [package.metadata] requires-dist = [{ name = "clr-loader", specifier = ">=0.3.1,<0.4.0" }] @@ -365,6 +705,226 @@ dev = [ { name = "numpy", marker = "python_full_version >= '3.10'", specifier = ">=2" }, { name = "pytest", specifier = ">=6" }, ] +doc = [ + { name = "breathe" }, + { name = "furo", specifier = ">=2025.12.19" }, + { name = "pygments", specifier = ">=2.20" }, + { name = "sphinx" }, + { name = "sphinx-csharp", git = "https://github.com/rogerbarton/sphinx-csharp.git" }, +] + +[[package]] +name = "requests" +version = "2.33.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version < '3.11'" }, + { name = "babel", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "imagesize", marker = "python_full_version < '3.11'" }, + { name = "jinja2", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "requests", marker = "python_full_version < '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, +] + +[[package]] +name = "sphinx" +version = "9.0.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version == '3.11.*'" }, + { name = "babel", marker = "python_full_version == '3.11.*'" }, + { name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "imagesize", marker = "python_full_version == '3.11.*'" }, + { name = "jinja2", marker = "python_full_version == '3.11.*'" }, + { name = "packaging", marker = "python_full_version == '3.11.*'" }, + { name = "pygments", marker = "python_full_version == '3.11.*'" }, + { name = "requests", marker = "python_full_version == '3.11.*'" }, + { name = "roman-numerals", marker = "python_full_version == '3.11.*'" }, + { name = "snowballstemmer", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version >= '3.12'" }, + { name = "babel", marker = "python_full_version >= '3.12'" }, + { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "imagesize", marker = "python_full_version >= '3.12'" }, + { name = "jinja2", marker = "python_full_version >= '3.12'" }, + { name = "packaging", marker = "python_full_version >= '3.12'" }, + { name = "pygments", marker = "python_full_version >= '3.12'" }, + { name = "requests", marker = "python_full_version >= '3.12'" }, + { name = "roman-numerals", marker = "python_full_version >= '3.12'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736, upload-time = "2023-07-08T18:40:54.166Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496, upload-time = "2023-07-08T18:40:52.659Z" }, +] + +[[package]] +name = "sphinx-csharp" +version = "0.1.13" +source = { git = "https://github.com/rogerbarton/sphinx-csharp.git#1e4cf5d2cca28424ec836ed1989fd0f24b3e7172" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] [[package]] name = "tomli" @@ -428,3 +988,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac8 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" }, ] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] From cf0ce2e5f161b8d261b952f00bfa6efbc8f493ff Mon Sep 17 00:00:00 2001 From: Metadorius Date: Fri, 27 Mar 2026 00:05:05 +0200 Subject: [PATCH 175/194] Support .NET Framework 4.6.1 --- AUTHORS.md | 1 + CHANGELOG.md | 3 ++- doc/source/python.rst | 2 +- setup.py | 2 +- src/compat/Python.Runtime.Compat.csproj | 13 +++++++++++++ src/runtime/Loader.cs | 23 +++++++++++++++++++++++ 6 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 src/compat/Python.Runtime.Compat.csproj diff --git a/AUTHORS.md b/AUTHORS.md index 7ea639059..96e58ff46 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -89,3 +89,4 @@ - Ehsan Iran-Nejad ([@eirannejad](https://github.com/eirannejad)) - ([@legomanww](https://github.com/legomanww)) - ([@gertdreyer](https://github.com/gertdreyer)) +- Kerbiter ([@Metadorius](https://github.com/Metadorius)) diff --git a/CHANGELOG.md b/CHANGELOG.md index df68fbb39..73a81368f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,13 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. ### Added - Support `del obj[...]` for types derived from `IList` and `IDictionary` +- Support for .NET Framework 4.6.1 (#2701) ### Changed ### Fixed - Fixed crash when trying to `del clrObj[...]` for non-arrays -- ci: properly exclude job (#2542) +- ci: properly exclude job (#2542) ## [3.0.5](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.5) - 2024-12-13 diff --git a/doc/source/python.rst b/doc/source/python.rst index a9228537c..89f90eb07 100644 --- a/doc/source/python.rst +++ b/doc/source/python.rst @@ -45,7 +45,7 @@ Mono (``mono``) .NET Framework (``netfx``) Default on Windows and also only supported there. Must be at least version - 4.7.2. + 4.6.1, with 4.7.2 or later recommended. .NET Core (``coreclr``) Self-contained is not supported, must be at least version 3.1. diff --git a/setup.py b/setup.py index 7c02b7710..678aeba2e 100644 --- a/setup.py +++ b/setup.py @@ -127,7 +127,7 @@ def finalize_options(self): dotnet_libs = [ DotnetLib( "python-runtime", - "src/runtime/Python.Runtime.csproj", + "src/compat/Python.Runtime.Compat.csproj", output="pythonnet/runtime", ) ] diff --git a/src/compat/Python.Runtime.Compat.csproj b/src/compat/Python.Runtime.Compat.csproj new file mode 100644 index 000000000..5c607e157 --- /dev/null +++ b/src/compat/Python.Runtime.Compat.csproj @@ -0,0 +1,13 @@ + + + + net461 + Library + + false + false + + + + + diff --git a/src/runtime/Loader.cs b/src/runtime/Loader.cs index c0e964abc..08c0be7cb 100644 --- a/src/runtime/Loader.cs +++ b/src/runtime/Loader.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Text; namespace Python.Runtime @@ -12,6 +13,28 @@ public unsafe static int Initialize(IntPtr data, int size) { try { + // On .NET Framework, the host is python.exe which has no binding + // redirects for netstandard2.0 shims (e.g. RuntimeInformation + // Version=0.0.0.0 vs the 4.0.2.0 shim on disk). Binding redirects + // via config files can't be injected after AppDomain creation, so + // resolve assemblies from our runtime directory directly. + // Only needed on .NET Framework; on .NET (Core) this causes + // duplicate assembly loads, as .deps.json is respected and + // the correct assembly is already found. + if (typeof(object).Assembly.GetName().Name == "mscorlib") + { + AppDomain.CurrentDomain.AssemblyResolve += (_, args) => + { + var name = new System.Reflection.AssemblyName(args.Name); + var dir = Path.GetDirectoryName(typeof(Loader).Assembly.Location); + var path = Path.Combine(dir, name.Name + ".dll"); + + return File.Exists(path) + ? System.Reflection.Assembly.LoadFrom(path) + : null; + }; + } + var dllPath = Encodings.UTF8.GetString((byte*)data.ToPointer(), size); if (!string.IsNullOrEmpty(dllPath)) From 89a4b249286b5775fb8926f3935251469ee288d1 Mon Sep 17 00:00:00 2001 From: Metadorius Date: Sat, 28 Mar 2026 00:16:12 +0200 Subject: [PATCH 176/194] Add a PyInstaller hook to handle files correctly in PyInstaller --- pyproject.toml | 3 +++ pythonnet/_pyinstaller/__init__.py | 4 ++++ pythonnet/_pyinstaller/hook-clr.py | 9 +++++++++ 3 files changed, 16 insertions(+) create mode 100644 pythonnet/_pyinstaller/__init__.py create mode 100644 pythonnet/_pyinstaller/hook-clr.py diff --git a/pyproject.toml b/pyproject.toml index 75df0f072..a270163ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,9 @@ email = "pythonnet@python.org" Homepage = "https://pythonnet.github.io/" Sources = "https://github.com/pythonnet/pythonnet" +[project.entry-points.pyinstaller40] +hook-dirs = "pythonnet._pyinstaller:get_hook_dirs" + [tool.setuptools] zip-safe = false py-modules = ["clr"] diff --git a/pythonnet/_pyinstaller/__init__.py b/pythonnet/_pyinstaller/__init__.py new file mode 100644 index 000000000..2ed816a4e --- /dev/null +++ b/pythonnet/_pyinstaller/__init__.py @@ -0,0 +1,4 @@ +import os + +def get_hook_dirs(): + return [os.path.dirname(__file__)] diff --git a/pythonnet/_pyinstaller/hook-clr.py b/pythonnet/_pyinstaller/hook-clr.py new file mode 100644 index 000000000..f0b058681 --- /dev/null +++ b/pythonnet/_pyinstaller/hook-clr.py @@ -0,0 +1,9 @@ +from PyInstaller.utils.hooks import collect_data_files, collect_dynamic_libs + +try: + binaries = collect_dynamic_libs("pythonnet") + datas = collect_data_files("pythonnet") +except Exception: + # name conflict with https://pypi.org/project/clr/, do not crash if just clr is present + binaries = [] + datas = [] From 19dc42edb3ff484880125f3f43f03407b1570ab8 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 5 May 2026 17:49:10 +0200 Subject: [PATCH 177/194] Build .NET 4.6 support conditionally --- Justfile | 14 ++++++++++++++ MANIFEST.in | 1 + doc/source/python.rst | 3 ++- setup.py | 33 ++++++++++++++++----------------- 4 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 Justfile diff --git a/Justfile b/Justfile new file mode 100644 index 000000000..b5228e2e0 --- /dev/null +++ b/Justfile @@ -0,0 +1,14 @@ +default: + @just --choose + +setup: + dotnet restore + uv sync + +docs: + doxygen doc/Doxyfile + uv run --group doc sphinx-build doc/source/ ./doc/build/html/ + +build-wheels: + uv build + uv build --wheel -C="--global-option=--net46-support" \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 71473c2c3..1d4889e25 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ graft src/runtime prune src/runtime/obj prune src/runtime/bin +graft src/compat include src/pythonnet.snk include Directory.Build.* include pythonnet.sln diff --git a/doc/source/python.rst b/doc/source/python.rst index 89f90eb07..5bc39accb 100644 --- a/doc/source/python.rst +++ b/doc/source/python.rst @@ -45,7 +45,8 @@ Mono (``mono``) .NET Framework (``netfx``) Default on Windows and also only supported there. Must be at least version - 4.6.1, with 4.7.2 or later recommended. + 4.6.1, with 4.7.2 or later recommended. For .NET 4.6 support, the wheel has + to be built with the environment variable `PYTHONNET_BUILD_NET46_SUPPORT=1`. .NET Core (``coreclr``) Self-contained is not supported, must be at least version 3.1. diff --git a/setup.py b/setup.py index 678aeba2e..c0683b000 100644 --- a/setup.py +++ b/setup.py @@ -3,15 +3,20 @@ import distutils from distutils.command.build import build as _build from setuptools.command.develop import develop as _develop -from wheel.bdist_wheel import bdist_wheel as _bdist_wheel from setuptools import Distribution from setuptools import setup, Command import os +import sys # Disable SourceLink during the build until it can read repo-format v1, #1613 os.environ["EnableSourceControlManagerQueries"] = "false" +NET46_SUPPORT_OPTION = "--net46-support" +NET46_SUPPORT = NET46_SUPPORT_OPTION in sys.argv +if NET46_SUPPORT: + sys.argv.remove(NET46_SUPPORT_OPTION) + class DotnetLib: def __init__(self, name, path, **kwargs): @@ -106,14 +111,6 @@ def install_for_development(self): return super().install_for_development() -class bdist_wheel(_bdist_wheel): - def finalize_options(self): - # Monkey patch bdist_wheel to think the package is pure even though we - # include DLLs - super().finalize_options() - self.root_is_pure = True - - # Monkey-patch Distribution s.t. it supports the dotnet_libs attribute Distribution.dotnet_libs = None @@ -121,18 +118,20 @@ def finalize_options(self): "build": build, "build_dotnet": build_dotnet, "develop": develop, - "bdist_wheel": bdist_wheel, } -dotnet_libs = [ - DotnetLib( - "python-runtime", - "src/compat/Python.Runtime.Compat.csproj", - output="pythonnet/runtime", - ) -] + +if NET46_SUPPORT: + csproj = "src/compat/Python.Runtime.Compat.csproj" + plat_name = "win32" +else: + csproj = "src/runtime/Python.Runtime.csproj" + plat_name = "any" + +dotnet_libs = [DotnetLib("python-runtime", csproj, output="pythonnet/runtime")] setup( cmdclass=cmdclass, dotnet_libs=dotnet_libs, + options={"bdist_wheel": {"plat_name": plat_name}}, ) From 4e61c18ed681487db3f0e3d64fcd615f98d54855 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 6 May 2026 15:43:22 +0200 Subject: [PATCH 178/194] Merge pull request #2716 from pythonnet/fix-wheel-tags Fix wheel tags --- pyproject.toml | 2 +- setup.py | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a270163ac..0966038cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=80"] +requires = ["setuptools>=80", "packaging>=24", "pyproject-parser>=0.14"] build-backend = "setuptools.build_meta" [project] diff --git a/setup.py b/setup.py index c0683b000..1c2ffe2fd 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,10 @@ import distutils from distutils.command.build import build as _build from setuptools.command.develop import develop as _develop +from setuptools.command.bdist_wheel import bdist_wheel as _bdist_wheel +from packaging.specifiers import SpecifierSet +from packaging.version import Version +from pyproject_parser import PyProject from setuptools import Distribution from setuptools import setup, Command @@ -16,6 +20,7 @@ NET46_SUPPORT = NET46_SUPPORT_OPTION in sys.argv if NET46_SUPPORT: sys.argv.remove(NET46_SUPPORT_OPTION) +WINDOWS_PLATFORM_TAG = "win32.win_amd64" class DotnetLib: @@ -111,6 +116,32 @@ def install_for_development(self): return super().install_for_development() +class bdist_wheel(_bdist_wheel): + def get_tag(self): + if NET46_SUPPORT: + platform_tag = WINDOWS_PLATFORM_TAG + else: + platform_tag = "any" + abi_tag = "none" + python_tag = self._get_python_tag() + return python_tag, abi_tag, platform_tag + + def _get_python_tag(self) -> str: + pyproject = PyProject.load("pyproject.toml") + project = pyproject.project or {} + + requires_python = project.get("requires-python") + if not requires_python: + raise RuntimeError("project.requires-python is required") + + specifiers = SpecifierSet(str(requires_python)) + return ".".join( + f"cp3{minor}" + for minor in range(0, 100) + if specifiers.contains(Version(f"3.{minor}"), prereleases=True) + ) + + # Monkey-patch Distribution s.t. it supports the dotnet_libs attribute Distribution.dotnet_libs = None @@ -118,20 +149,18 @@ def install_for_development(self): "build": build, "build_dotnet": build_dotnet, "develop": develop, + "bdist_wheel": bdist_wheel, } if NET46_SUPPORT: csproj = "src/compat/Python.Runtime.Compat.csproj" - plat_name = "win32" else: csproj = "src/runtime/Python.Runtime.csproj" - plat_name = "any" dotnet_libs = [DotnetLib("python-runtime", csproj, output="pythonnet/runtime")] setup( cmdclass=cmdclass, dotnet_libs=dotnet_libs, - options={"bdist_wheel": {"plat_name": plat_name}}, ) From dc69411dac31f388c0335ba2381c2f730e98d972 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 7 May 2026 19:54:01 +0200 Subject: [PATCH 179/194] Name missing from __all__ on re-import (#2717) * Adjust test_import to always trigger error-case * Ensure that names are added to __all__ exactly once --- src/runtime/Types/ModuleObject.cs | 26 ++++++++++++++------------ tests/test_import.py | 5 +++++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/runtime/Types/ModuleObject.cs b/src/runtime/Types/ModuleObject.cs index f641b393e..e525564b2 100644 --- a/src/runtime/Types/ModuleObject.cs +++ b/src/runtime/Types/ModuleObject.cs @@ -19,6 +19,7 @@ internal class ModuleObject : ExtensionType internal PyDict dict; protected string _namespace; private readonly PyList __all__ = new (); + private readonly HashSet allNames = new(); // Attributes to be set on the module according to PEP302 and 451 // by the import machinery. @@ -178,22 +179,23 @@ public void LoadNames() { foreach (string name in AssemblyManager.GetNames(_namespace)) { - cache.TryGetValue(name, out var m); - if (m != null) + bool hasValidAttribute = cache.TryGetValue(name, out var m); + if (!hasValidAttribute) { - continue; - } - BorrowedReference attr = Runtime.PyDict_GetItemString(dict, name); - // If __dict__ has already set a custom property, skip it. - if (!attr.IsNull) - { - continue; + BorrowedReference attr = Runtime.PyDict_GetItemString(dict, name); + // If __dict__ has already set a custom property, skip it. + if (!attr.IsNull) + { + continue; + } + + using var attrVal = GetAttribute(name, true); + hasValidAttribute = !attrVal.IsNull(); } - using var attrVal = GetAttribute(name, true); - if (!attrVal.IsNull()) + if (hasValidAttribute && allNames.Add(name)) { - // if it's a valid attribute, add it to __all__ + // if it's a valid attribute, add it to __all__ once. using var pyname = Runtime.PyString_FromString(name); if (Runtime.PyList_Append(__all__, pyname.Borrow()) != 0) { diff --git a/tests/test_import.py b/tests/test_import.py index 877eacd84..f90192190 100644 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -5,6 +5,11 @@ import pytest import sys +# Unused import to preload the class +# +# This resulted in the FileStream name missing from the wildcard import later +from System.IO import FileStream # noqa: F401 + def test_relative_missing_import(): """Test that a relative missing import doesn't crash. Some modules use this to check if a package is installed. From 25e0ccf39f52716bb856a91ecb7c38de726b1d07 Mon Sep 17 00:00:00 2001 From: Denis Akhiyarov Date: Mon, 11 May 2026 04:25:15 -0700 Subject: [PATCH 180/194] Add context manager protocol for .NET IDisposable types (#2568) * Add context manager protocol for .NET IDisposable types --------- Co-authored-by: den-run-ai Co-authored-by: Benedikt Reinartz --- AUTHORS.md | 2 +- CHANGELOG.md | 2 + doc/source/python.rst | 28 +++++ .../Mixins/CollectionMixinsProvider.cs | 6 + src/runtime/Mixins/collections.py | 16 +++ tests/test_disposable.py | 111 ++++++++++++++++++ 6 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 tests/test_disposable.py diff --git a/AUTHORS.md b/AUTHORS.md index 96e58ff46..723520f3c 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -9,7 +9,7 @@ - Barton Cline ([@BartonCline](https://github.com/BartonCline)) - Brian Lloyd ([@brianlloyd](https://github.com/brianlloyd)) - David Anthoff ([@davidanthoff](https://github.com/davidanthoff)) -- Denis Akhiyarov ([@denfromufa](https://github.com/denfromufa)) +- Denis Akhiyarov ([@den-run-ai](https://github.com/den-run-ai)) - Tony Roberts ([@tonyroberts](https://github.com/tonyroberts)) - Victor Uriarte ([@vmuriart](https://github.com/vmuriart)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a81368f..54a08fe52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Support `del obj[...]` for types derived from `IList` and `IDictionary` - Support for .NET Framework 4.6.1 (#2701) +- Add context manager protocol for .NET IDisposable types, allowing use of `with` statements + for IDisposable objects (#2568) ### Changed ### Fixed diff --git a/doc/source/python.rst b/doc/source/python.rst index 5bc39accb..d39081eba 100644 --- a/doc/source/python.rst +++ b/doc/source/python.rst @@ -480,6 +480,34 @@ Python idioms: for item in domain.GetAssemblies(): name = item.GetName() +Using Context Managers (IDisposable) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.NET types that implement ``IDisposable`` can be used with Python's context manager +protocol using the standard ``with`` statement. This automatically calls the object's +``Dispose()`` method when exiting the ``with`` block: + +.. code:: python + + from System.IO import MemoryStream, StreamWriter + + # Use a MemoryStream as a context manager + with MemoryStream() as stream: + # The stream is automatically disposed when exiting the with block + writer = StreamWriter(stream) + writer.Write("Hello, context manager!") + writer.Flush() + + # Do something with the stream + stream.Position = 0 + # ... + + # After exiting the with block, the stream is disposed + # Attempting to use it here would raise an exception + +This works for any .NET type that implements ``IDisposable``, making resource +management much cleaner and safer in Python code. + Type Conversion --------------- diff --git a/src/runtime/Mixins/CollectionMixinsProvider.cs b/src/runtime/Mixins/CollectionMixinsProvider.cs index d1b19e4d8..2bd352d16 100644 --- a/src/runtime/Mixins/CollectionMixinsProvider.cs +++ b/src/runtime/Mixins/CollectionMixinsProvider.cs @@ -63,6 +63,12 @@ public IEnumerable GetBaseTypes(Type type, IList existingBases) newBases.Add(new PyType(this.Mixins.GetAttr("IteratorMixin"))); } + // context managers (for IDisposable) + if (interfaces.Contains(typeof(IDisposable))) + { + newBases.Add(new PyType(this.Mixins.GetAttr("ContextManagerMixin"))); + } + if (newBases.Count == existingBases.Count) { return existingBases; diff --git a/src/runtime/Mixins/collections.py b/src/runtime/Mixins/collections.py index 95a6d8162..e6eaef2e5 100644 --- a/src/runtime/Mixins/collections.py +++ b/src/runtime/Mixins/collections.py @@ -5,6 +5,22 @@ import collections.abc as col +class ContextManagerMixin: + """Implements Python's context manager protocol for .NET IDisposable types""" + def __enter__(self): + """Return self for use in the with block""" + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Call Dispose() when exiting the with block""" + if hasattr(self, 'Dispose'): + self.Dispose() + else: + from System import IDisposable + IDisposable(self).Dispose() + # Return False to indicate that exceptions should propagate + return False + class IteratorMixin(col.Iterator): def close(self): if hasattr(self, 'Dispose'): diff --git a/tests/test_disposable.py b/tests/test_disposable.py new file mode 100644 index 000000000..3c8fb1159 --- /dev/null +++ b/tests/test_disposable.py @@ -0,0 +1,111 @@ +import pytest + +from System.IO import MemoryStream, FileStream, FileMode, File, Path, StreamWriter + + +def test_memory_stream_context_manager(): + """Test that MemoryStream can be used as a context manager""" + data = bytes([1, 2, 3, 4, 5]) + + with MemoryStream() as stream: + # Convert Python bytes to .NET byte array for proper writing + from System import Array, Byte + + dotnet_bytes = Array[Byte](data) + stream.Write(dotnet_bytes, 0, len(dotnet_bytes)) + + assert stream.Length == 5 + stream.Position = 0 + + # Create a .NET byte array to read into + buffer = Array[Byte](5) + stream.Read(buffer, 0, 5) + + # Convert back to Python bytes for comparison + result = bytes(buffer) + assert result == data + + # The stream should be disposed (closed) after the with block + with pytest.raises(Exception): + stream.Position = 0 # This should fail because the stream is closed + + +def test_file_stream_context_manager(tmpdir: str): + """Test that FileStream can be used as a context manager""" + # Create a temporary file path + temp_path = Path.Combine(str(tmpdir), Path.GetRandomFileName()) + + try: + # Write data to the file using with statement + data = "Hello, context manager!" + with FileStream(temp_path, FileMode.Create) as fs: + writer = StreamWriter(fs) + writer.Write(data) + writer.Flush() + + # Verify the file was written and stream was closed + assert File.Exists(temp_path) + content = File.ReadAllText(temp_path) + assert content == data + + # The stream should be disposed after the with block + with pytest.raises(Exception): + fs.Position = 0 # This should fail because the stream is closed + finally: + # Clean up + if File.Exists(temp_path): + File.Delete(temp_path) + + +def test_disposable_in_multiple_contexts(): + """Test that using .NET IDisposable objects in multiple contexts works correctly""" + # Create multiple streams and check that they're all properly disposed + + # Create a list to track if streams were properly disposed + # (we'll check this by trying to access the stream after disposal) + streams_disposed = [False, False] + + # Use nested context managers with .NET IDisposable objects + with MemoryStream() as outer_stream: + # Write some data to the outer stream + from System import Array, Byte + + outer_data = Array[Byte]([10, 20, 30]) + outer_stream.Write(outer_data, 0, len(outer_data)) + + # Check that the outer stream is usable + assert outer_stream.Length == 3 + + with MemoryStream() as inner_stream: + # Write different data to the inner stream + inner_data = Array[Byte]([40, 50, 60, 70]) + inner_stream.Write(inner_data, 0, len(inner_data)) + + # Check that the inner stream is usable + assert inner_stream.Length == 4 + + # Try to use the inner stream - should fail because it's disposed + try: + inner_stream.Position = 0 + except Exception: + streams_disposed[1] = True + + # Try to use the outer stream - should fail because it's disposed + try: + outer_stream.Position = 0 + except Exception: + streams_disposed[0] = True + + # Verify both streams were properly disposed + assert all(streams_disposed) + + +def test_exception_handling(): + """Test that exceptions propagate correctly through the context manager""" + with pytest.raises(ValueError): + with MemoryStream() as stream: + raise ValueError("Test exception") + + # Stream should be disposed despite the exception + with pytest.raises(Exception): + stream.Position = 0 From ca323cc1bfaa51cdf012cdac12fdba7907e51a57 Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Tue, 12 May 2026 15:56:09 -0400 Subject: [PATCH 181/194] Fix MethodBinding/OverloadMapper memory leak (#691) (#2719) * Fix MethodBinding/OverloadMapper memory leak (#691) MethodBinding and OverloadMapper held PyObject `target` references that were not disposed during tp_clear, leaving Python-side refcount drops to wait on the multi-hop .NET finalizer chain. They also shared the same C# PyObject instance across mp_subscript/Overloads paths, so freeing one could free the underlying Python object out from under the others. - ExtensionType: add virtual OnClear() hook called from tp_clear before the GCHandle is released, letting subclasses eagerly drop owned Python references. - MethodBinding/OverloadMapper: override OnClear to dispose `target`. (`targetType` is intentionally not disposed since Python types are long-lived and tracked by other caches.) - Take an independent INCREF'd PyObject copy at every site that hands a shared target into a new MethodBinding or OverloadMapper, so each wrapper owns its own reference. Result: the three _does_not_leak_memory tests drop from ~485 MB delta to ~10 KB delta on Python 3.14. * Tighten leak-test threshold to 10% to actually fail the bug The previous 90% threshold (0.9 MB/iter against a 1 MB allocation) documented the issue but did not reproduce it: master leaks ~600-765 KB/iter, which the 0.9 MB threshold accepts as passing. Drop the threshold to 10% (104 KB/iter). On the 2026-05-09 verification run with Python 3.14 GIL on linux-aarch64: Without fix (master): ~572-765 KB/iter (FAIL) With fix (this branch): ~-500 B/iter (PASS) Margin is roughly 6x in either direction across .NET 8 and .NET 10, so the threshold cleanly separates buggy from fixed states without being sensitive to GC noise. * Bugfix and improvements - Handle the `PyType` reference in `OverloadMapper` and `MethodBinding` in the same way as the object reference - Unconditionally store the `PyType` of the object - Introduce `NewReference` helper function for the object and type passing - Fix the remaining missing reference count bump for the type (`MethodObject`) - As the count is now correct, `Dispose` the type as well --------- Co-authored-by: Benedikt Reinartz --- src/runtime/PythonTypes/PyObject.cs | 6 ++++++ src/runtime/PythonTypes/PyType.cs | 6 ++++++ src/runtime/Types/ExtensionType.cs | 10 ++++++++++ src/runtime/Types/MethodBinding.cs | 20 ++++++++++++-------- src/runtime/Types/MethodObject.cs | 2 +- src/runtime/Types/OverloadMapper.cs | 15 ++++++++++++--- tests/test_method.py | 15 ++++++++------- 7 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/runtime/PythonTypes/PyObject.cs b/src/runtime/PythonTypes/PyObject.cs index cf0c2a03f..1949710fb 100644 --- a/src/runtime/PythonTypes/PyObject.cs +++ b/src/runtime/PythonTypes/PyObject.cs @@ -93,6 +93,12 @@ internal PyObject(in StolenReference reference) Finalizer.Instance.ThrottledCollect(); } + /// + /// Create a new PyObject instance of this object, bumping the reference + /// count. + /// + public PyObject NewReference() => new(this); + // Ensure that encapsulated Python object is decref'ed appropriately // when the managed wrapper is garbage-collected. ~PyObject() diff --git a/src/runtime/PythonTypes/PyType.cs b/src/runtime/PythonTypes/PyType.cs index 28bda5d3e..dd82450db 100644 --- a/src/runtime/PythonTypes/PyType.cs +++ b/src/runtime/PythonTypes/PyType.cs @@ -35,6 +35,12 @@ internal PyType(in StolenReference reference, bool prevalidated = false) : base( throw new ArgumentException("object is not a type"); } + /// + /// Create a new PyType instance of this object, bumping the reference + /// count. + /// + public new PyType NewReference() => new(this); + protected PyType(SerializationInfo info, StreamingContext context) : base(info, context) { } internal new static PyType? FromNullableReference(BorrowedReference reference) diff --git a/src/runtime/Types/ExtensionType.cs b/src/runtime/Types/ExtensionType.cs index 305fdc15d..114f2d706 100644 --- a/src/runtime/Types/ExtensionType.cs +++ b/src/runtime/Types/ExtensionType.cs @@ -84,8 +84,18 @@ public unsafe static void tp_dealloc(NewReference lastRef) DecrefTypeAndFree(lastRef.Steal()); } + /// + /// Called during tp_clear before the GCHandle is released. + /// Override to eagerly dispose Python object references (PyObject fields) + /// held by the subclass, preventing the multi-hop .NET finalizer chain + /// from delaying Python-side refcount decrements. + /// + protected virtual void OnClear() { } + public static int tp_clear(BorrowedReference ob) { + (GetManagedObject(ob) as ExtensionType)?.OnClear(); + var weakrefs = Runtime.PyObject_GetWeakRefList(ob); if (weakrefs != null) { diff --git a/src/runtime/Types/MethodBinding.cs b/src/runtime/Types/MethodBinding.cs index bfe22b0f3..b68f338ff 100644 --- a/src/runtime/Types/MethodBinding.cs +++ b/src/runtime/Types/MethodBinding.cs @@ -18,14 +18,12 @@ internal class MethodBinding : ExtensionType internal MaybeMethodInfo info; internal MethodObject m; internal PyObject? target; - internal PyType? targetType; + internal PyType targetType; - public MethodBinding(MethodObject m, PyObject? target, PyType? targetType = null) + public MethodBinding(MethodObject m, PyObject? target, PyType targetType) { this.target = target; - - this.targetType = targetType ?? target?.GetPythonType(); - + this.targetType = targetType; this.info = null; this.m = m; } @@ -54,7 +52,7 @@ public static NewReference mp_subscript(BorrowedReference tp, BorrowedReference } MethodObject overloaded = self.m.WithOverloads(overloads); - var mb = new MethodBinding(overloaded, self.target, self.targetType); + var mb = new MethodBinding(overloaded, self.target?.NewReference(), self.targetType.NewReference()); return mb.Alloc(); } @@ -141,7 +139,7 @@ public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference k // FIXME: deprecate __overloads__ soon... case "__overloads__": case "Overloads": - var om = new OverloadMapper(self.m, self.target); + var om = new OverloadMapper(self.m, self.target?.NewReference(), self.targetType.NewReference()); return om.Alloc(); case "__signature__" when Runtime.InspectModule is not null: var sig = self.Signature; @@ -249,7 +247,6 @@ public static NewReference tp_call(BorrowedReference ob, BorrowedReference args, } } - /// /// MethodBinding __hash__ implementation. /// @@ -281,5 +278,12 @@ public static NewReference tp_repr(BorrowedReference ob) string name = self.m.name; return Runtime.PyString_FromString($"<{type} method '{name}'>"); } + + protected override void OnClear() + { + target?.Dispose(); + targetType.Dispose(); + target = null; + } } } diff --git a/src/runtime/Types/MethodObject.cs b/src/runtime/Types/MethodObject.cs index 12484d301..1bb3083c0 100644 --- a/src/runtime/Types/MethodObject.cs +++ b/src/runtime/Types/MethodObject.cs @@ -197,7 +197,7 @@ public static NewReference tp_descr_get(BorrowedReference ds, BorrowedReference && self.type.Value.IsInstanceOfType(obj.inst)) { var basecls = ReflectedClrType.GetOrCreate(self.type.Value); - return new MethodBinding(self, new PyObject(ob), basecls).Alloc(); + return new MethodBinding(self, new PyObject(ob), basecls.NewReference()).Alloc(); } return new MethodBinding(self, target: new PyObject(ob), targetType: new PyType(tp)).Alloc(); diff --git a/src/runtime/Types/OverloadMapper.cs b/src/runtime/Types/OverloadMapper.cs index 8f6e30478..79130a669 100644 --- a/src/runtime/Types/OverloadMapper.cs +++ b/src/runtime/Types/OverloadMapper.cs @@ -10,11 +10,13 @@ namespace Python.Runtime internal class OverloadMapper : ExtensionType { private readonly MethodObject m; - private readonly PyObject? target; + private PyObject? target; + readonly PyType targetType; - public OverloadMapper(MethodObject m, PyObject? target) + public OverloadMapper(MethodObject m, PyObject? target, PyType targetType) { this.target = target; + this.targetType = targetType; this.m = m; } @@ -42,7 +44,7 @@ public static NewReference mp_subscript(BorrowedReference tp, BorrowedReference return Exceptions.RaiseTypeError(e); } - var mb = new MethodBinding(self.m, self.target) { info = mi }; + var mb = new MethodBinding(self.m, self.target?.NewReference(), self.targetType.NewReference()) { info = mi }; return mb.Alloc(); } @@ -54,5 +56,12 @@ public static NewReference tp_repr(BorrowedReference op) var self = (OverloadMapper)GetManagedObject(op)!; return self.m.GetDocString(); } + + protected override void OnClear() + { + target?.Dispose(); + targetType.Dispose(); + target = null; + } } } diff --git a/tests/test_method.py b/tests/test_method.py index 7820457d5..53c614498 100644 --- a/tests/test_method.py +++ b/tests/test_method.py @@ -983,9 +983,10 @@ def test_getting_generic_method_binding_does_not_leak_memory(memory_usage_tracki bytesAllocatedPerIteration = pow(2, 20) # 1MB bytesLeakedPerIteration = processBytesDelta / iterations - # 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 + # Tight 10% threshold: with the fix the per-iteration leak is essentially + # zero, while the bug retains the bulk of the 1 MB payload (~600 KB/iter + # on 3.14 GIL). 100 KB/iter cleanly distinguishes the two states. + failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration * 0.1 assert bytesLeakedPerIteration < failThresholdBytesLeakedPerIteration @@ -1025,8 +1026,8 @@ def test_getting_overloaded_method_binding_does_not_leak_memory(memory_usage_tra bytesAllocatedPerIteration = pow(2, 20) # 1MB bytesLeakedPerIteration = processBytesDelta / iterations - # Allow 90% threshold - this shows the original issue is fixed, which leaks the full allocated bytes per iteration - failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration * 0.9 + # Tight 10% threshold; see test_getting_generic_method_binding_does_not_leak_memory. + failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration * 0.1 assert bytesLeakedPerIteration < failThresholdBytesLeakedPerIteration @@ -1068,8 +1069,8 @@ def test_getting_method_overloads_binding_does_not_leak_memory(memory_usage_trac bytesAllocatedPerIteration = pow(2, 20) # 1MB bytesLeakedPerIteration = processBytesDelta / iterations - # Allow 90% threshold - this shows the original issue is fixed, which leaks the full allocated bytes per iteration - failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration * 0.9 + # Tight 10% threshold; see test_getting_generic_method_binding_does_not_leak_memory. + failThresholdBytesLeakedPerIteration = bytesAllocatedPerIteration * 0.1 assert bytesLeakedPerIteration < failThresholdBytesLeakedPerIteration From 34f3bc30a0b09d9746fffcb591ab58d085db6903 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 22:04:47 +0200 Subject: [PATCH 182/194] Bump urllib3 from 2.6.3 to 2.7.0 in the uv group across 1 directory (#2723) Bumps the uv group with 1 update in the / directory: [urllib3](https://github.com/urllib3/urllib3). Updates `urllib3` from 2.6.3 to 2.7.0 - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.3...2.7.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.7.0 dependency-type: indirect dependency-group: uv ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index e1e44730d..3b55148b7 100644 --- a/uv.lock +++ b/uv.lock @@ -991,9 +991,9 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] From baa481984b4e0de3cd00aa9265e7e98b92714094 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 12 May 2026 22:06:06 +0200 Subject: [PATCH 183/194] Update NUnit (#2724) * Update NUnit * Drop some obsolete comments and reenable some tests --- Directory.Build.props | 2 +- src/embed_tests/Codecs.cs | 28 +++--- src/embed_tests/Dynamic.cs | 20 ++--- src/embed_tests/Events.cs | 2 +- src/embed_tests/Modules.cs | 18 ++-- .../NeedsReinit/TestDomainReload.cs | 4 +- .../NeedsReinit/TestPyInitialize.cs | 2 +- .../NeedsReinit/TestPythonEngineProperties.cs | 10 +-- src/embed_tests/NeedsReinit/TestRuntime.cs | 20 ++--- src/embed_tests/NumPyTests.cs | 20 ++--- .../StateSerialization/MethodSerialization.cs | 4 +- src/embed_tests/TestConverter.cs | 4 +- src/embed_tests/TestPyFloat.cs | 12 +-- src/embed_tests/TestPyInt.cs | 90 +++++++++---------- src/embed_tests/TestPyIter.cs | 2 +- src/embed_tests/TestPyList.cs | 48 +++++----- src/embed_tests/TestPyObject.cs | 4 +- src/embed_tests/TestPySequence.cs | 20 ++--- src/embed_tests/TestPyString.cs | 26 +++--- src/embed_tests/TestPyTuple.cs | 22 +++-- src/embed_tests/TestPyType.cs | 6 +- src/embed_tests/TestPythonEngineProperties.cs | 4 +- src/embed_tests/TestPythonException.cs | 8 +- src/embed_tests/pyimport.cs | 8 +- src/runtime/Native/CustomMarshaler.cs | 25 +----- src/runtime/PythonEngine.cs | 12 +-- tests/conftest.py | 2 - tests/test_array.py | 7 +- tests/test_field.py | 1 - 29 files changed, 198 insertions(+), 233 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 4b0f25d56..377db2ff5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ Copyright (c) 2006-2025 The Contributors of the Python.NET Project pythonnet Python.NET - 12.0 + 14 false true $([System.IO.File]::ReadAllText("$(MSBuildThisFileDirectory)version.txt").Trim()) diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index 5879462f5..6159060b3 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -32,7 +32,7 @@ static void TupleConversionsGeneric() scope.Set(nameof(tuple), tuple); scope.Set(nameof(accept), accept); scope.Exec($"{nameof(accept)}({nameof(tuple)})"); - Assert.AreEqual(expected: tuple, actual: restored); + Assert.That(actual: restored, Is.EqualTo(expected: tuple)); } } @@ -53,7 +53,7 @@ static void TupleConversionsObject() scope.Set(nameof(tuple), tuple); scope.Set(nameof(accept), accept); scope.Exec($"{nameof(accept)}({nameof(tuple)})"); - Assert.AreEqual(expected: tuple, actual: restored); + Assert.That(actual: restored, Is.EqualTo(expected: tuple)); } } @@ -67,7 +67,7 @@ static void TupleRoundtripObject() var tuple = Activator.CreateInstance(typeof(T), 42.0, "42", new object()); using var pyTuple = TupleCodec.Instance.TryEncode(tuple); Assert.IsTrue(TupleCodec.Instance.TryDecode(pyTuple, out object restored)); - Assert.AreEqual(expected: tuple, actual: restored); + Assert.That(actual: restored, Is.EqualTo(expected: tuple)); } [Test] @@ -81,7 +81,7 @@ static void TupleRoundtripGeneric() var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object()); using var pyTuple = TupleCodec.Instance.TryEncode(tuple); Assert.IsTrue(TupleCodec.Instance.TryDecode(pyTuple, out T restored)); - Assert.AreEqual(expected: tuple, actual: restored); + Assert.That(actual: restored, Is.EqualTo(expected: tuple)); } static PyObject GetPythonIterable() => PythonEngine.Eval("map(lambda x: x, [1,2,3])"); @@ -118,7 +118,7 @@ public void ListDecoderTest() //the IList will report a Count of 3. IList stringList = null; Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringList); }); - Assert.AreEqual(stringList.Count, 3); + Assert.That(3, Is.EqualTo(stringList.Count)); Assert.Throws(typeof(InvalidCastException), () => { var x = stringList[0]; }); //can't convert python iterable to list (this will require a copy which isn't lossless) @@ -162,8 +162,8 @@ public void SequenceDecoderTest() //the IList will report a Count of 3. ICollection stringCollection = null; Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringCollection); }); - Assert.AreEqual(3, stringCollection.Count()); - Assert.Throws(typeof(InvalidCastException), () => { + Assert.That(stringCollection.Count(), Is.EqualTo(3)); + Assert.Throws(() => { string[] array = new string[3]; stringCollection.CopyTo(array, 0); }); @@ -199,8 +199,8 @@ public void SequenceDecoderTest() //the IList will report a Count of 3. ICollection stringCollection2 = null; Assert.DoesNotThrow(() => { codec.TryDecode(pyTuple, out stringCollection2); }); - Assert.AreEqual(3, stringCollection2.Count()); - Assert.Throws(typeof(InvalidCastException), () => { + Assert.That(stringCollection2.Count, Is.EqualTo(3)); + Assert.Throws(() => { string[] array = new string[3]; stringCollection2.CopyTo(array, 0); }); @@ -321,7 +321,7 @@ def call(func): "); var callFunc = scope.Get("call"); string message = callFunc.Invoke(callMeAction.ToPython()).As(); - Assert.AreEqual(TestExceptionMessage, message); + Assert.That(message, Is.EqualTo(TestExceptionMessage)); } [Test] @@ -331,7 +331,7 @@ public void ExceptionDecoded() using var scope = Py.CreateScope(); var error = Assert.Throws(() => PythonEngine.Exec($"raise ValueError('{TestExceptionMessage}')")); - Assert.AreEqual(TestExceptionMessage, error.Message); + Assert.That(error.Message, Is.EqualTo(TestExceptionMessage)); } [Test] @@ -360,7 +360,7 @@ public void FloatDerivedDecoded() PyObjectConversions.RegisterDecoder(decoder); using var result = scope.Eval("FloatDerived()"); object decoded = result.As(); - Assert.AreEqual(42, decoded); + Assert.That(decoded, Is.EqualTo(42)); } [Test] @@ -374,7 +374,7 @@ public void ExceptionDecodedNoInstance() var error = Assert.Throws(() => PythonEngine.Exec($"[].__iter__().__next__()") ); - Assert.AreEqual(TestExceptionMessage, error.Message); + Assert.That(error.Message, Is.EqualTo(TestExceptionMessage)); } else { @@ -395,7 +395,7 @@ public void As_Object_AffectedByDecoders() var pyObj = PythonEngine.Eval("iter"); var decoded = pyObj.As(); - Assert.AreSame(everythingElseToSelf, decoded); + Assert.That(decoded, Is.SameAs(everythingElseToSelf)); } public class EverythingElseToSelfDecoder : IPyObjectDecoder diff --git a/src/embed_tests/Dynamic.cs b/src/embed_tests/Dynamic.cs index 174167118..9d35bfacc 100644 --- a/src/embed_tests/Dynamic.cs +++ b/src/embed_tests/Dynamic.cs @@ -19,12 +19,12 @@ public void AssignObject() sys.testattr = stream; // Check whether there are the same object. dynamic _stream = sys.testattr.AsManagedObject(typeof(StringBuilder)); - Assert.AreEqual(_stream, stream); + Assert.That(_stream, Is.EqualTo(stream)); PythonEngine.RunSimpleString( "import sys\n" + "sys.testattr.Append('Hello!')\n"); - Assert.AreEqual(stream.ToString(), "Hello!"); + Assert.That(stream.ToString(), Is.EqualTo("Hello!")); } /// @@ -45,25 +45,15 @@ public void AssignNone() /// Check whether we can get the attr of a python object when the /// value of attr is a PyObject. /// - /// - /// FIXME: Issue on Travis PY27: Error : Python.EmbeddingTest.dynamicTest.AssignPyObject - /// Python.Runtime.PythonException : ImportError : /home/travis/virtualenv/python2.7.9/lib/python2.7/lib-dynload/_io.so: undefined symbol: _PyLong_AsInt - /// [Test] public void AssignPyObject() { - if (Environment.GetEnvironmentVariable("TRAVIS") == "true" && - Environment.GetEnvironmentVariable("TRAVIS_PYTHON_VERSION") == "2.7") - { - Assert.Ignore("Fails on Travis/PY27: ImportError: ... undefined symbol: _PyLong_AsInt"); - } - dynamic sys = Py.Import("sys"); dynamic io = Py.Import("io"); sys.testattr = io.StringIO(); dynamic bb = sys.testattr; // Get the PyObject bb.write("Hello!"); - Assert.AreEqual(bb.getvalue().ToString(), "Hello!"); + Assert.That(bb.getvalue().ToString(), Is.EqualTo("Hello!")); } /// @@ -87,7 +77,7 @@ public void PassObjectInPython() "import sys\n" + "sys.testattr3 = sys.testattr1 is sys.testattr2\n" ); - Assert.AreEqual(sys.testattr3.ToString(), "True"); + Assert.That(sys.testattr3.ToString(), Is.EqualTo("True")); // Compare in .NET Assert.IsTrue(sys.testattr1.Equals(sys.testattr2)); @@ -110,7 +100,7 @@ public void PassPyObjectInNet() "sys.testattr3 = sys.testattr1 is sys.testattr2\n" ); - Assert.AreEqual(sys.testattr3.ToString(), "True"); + Assert.That(sys.testattr3.ToString(), Is.EqualTo("True")); // Compare in .NET Assert.IsTrue(sys.testattr1.Equals(sys.testattr2)); diff --git a/src/embed_tests/Events.cs b/src/embed_tests/Events.cs index 94a30726b..cc51176dc 100644 --- a/src/embed_tests/Events.cs +++ b/src/embed_tests/Events.cs @@ -31,7 +31,7 @@ del example gc.collect() "); Runtime.Runtime.TryCollectingGarbage(10); - Assert.AreEqual(0, ClassWithEventHandler.alive); + Assert.That(ClassWithEventHandler.alive, Is.EqualTo(0)); } } diff --git a/src/embed_tests/Modules.cs b/src/embed_tests/Modules.cs index 67fa3d0fc..f06c5173e 100644 --- a/src/embed_tests/Modules.cs +++ b/src/embed_tests/Modules.cs @@ -149,11 +149,11 @@ public void TestScopeClass() ); dynamic obj1 = _ps.Class1(20); var result = obj1.call(10).As(); - Assert.AreEqual(130, result); + Assert.That(result, Is.EqualTo(130)); obj1.update(10); result = ps.Get("bb"); - Assert.AreEqual(30, result); + Assert.That(result, Is.EqualTo(30)); } } @@ -185,11 +185,11 @@ public void TestCreateVirtualPackageStructure() dynamic obj1 = ps2.Class1(20); var result = obj1.call(10).As(); - Assert.AreEqual(130, result); + Assert.That(result, Is.EqualTo(130)); obj1.update(10); result = ps2.Get("bb"); - Assert.AreEqual(30, result); + Assert.That(result, Is.EqualTo(30)); } } @@ -229,7 +229,7 @@ public void TestImportModule() var value1 = ps.Eval("sys.attr1"); var value2 = sys.attr1.As(); Assert.That(value1, Is.EqualTo(2)); - Assert.AreEqual(2, value2); + Assert.That(value2, Is.EqualTo(2)); //import as ps.Import("sys", "sys1"); @@ -308,16 +308,16 @@ public void TestImportScopeFunction() dynamic func2 = scope.Get("func2"); var result1 = func2().As(); - Assert.AreEqual(0, result1); + Assert.That(result1, Is.EqualTo(0)); scope.Set("cc", 20);//it has no effect on the globals of 'func1' var result2 = func2().As(); - Assert.AreEqual(-10, result2); + Assert.That(result2, Is.EqualTo(-10)); scope.Set("cc", 10); //rollback ps.Set("cc", 20); var result3 = func2().As(); - Assert.AreEqual(10, result3); + Assert.That(result3, Is.EqualTo(10)); ps.Set("cc", 10); //rollback } } @@ -437,7 +437,7 @@ public void TestCreate() scope.Execute(code); Assert.That(scope.TryGet("x", out dynamic x), Is.True); - Assert.AreEqual("True", x.ToString()); + Assert.That(x.ToString(), Is.EqualTo("True")); } [Test] diff --git a/src/embed_tests/NeedsReinit/TestDomainReload.cs b/src/embed_tests/NeedsReinit/TestDomainReload.cs index a8d2cd3d8..417a915a8 100644 --- a/src/embed_tests/NeedsReinit/TestDomainReload.cs +++ b/src/embed_tests/NeedsReinit/TestDomainReload.cs @@ -146,8 +146,8 @@ public override ValueType Execute(ValueType arg) "); } var clrObj = obj.As(); - Assert.AreEqual(clrObj.Property, 2); - Assert.AreEqual(clrObj.Field, 20); + Assert.That(2, Is.EqualTo(clrObj.Property)); + Assert.That(20, Is.EqualTo(clrObj.Field)); } } } diff --git a/src/embed_tests/NeedsReinit/TestPyInitialize.cs b/src/embed_tests/NeedsReinit/TestPyInitialize.cs index 1ef4127b8..b724e158e 100644 --- a/src/embed_tests/NeedsReinit/TestPyInitialize.cs +++ b/src/embed_tests/NeedsReinit/TestPyInitialize.cs @@ -28,7 +28,7 @@ public static void LoadDefaultArgs() { using(var argv = new PyList(Runtime.Runtime.PySys_GetObject("argv"))) { - Assert.AreNotEqual(0, argv.Length()); + Assert.That(argv.Length(), Is.Not.EqualTo(0)); } } } diff --git a/src/embed_tests/NeedsReinit/TestPythonEngineProperties.cs b/src/embed_tests/NeedsReinit/TestPythonEngineProperties.cs index 8eb9e975d..0e99005ae 100644 --- a/src/embed_tests/NeedsReinit/TestPythonEngineProperties.cs +++ b/src/embed_tests/NeedsReinit/TestPythonEngineProperties.cs @@ -22,7 +22,7 @@ public void SetPythonHome() PythonEngine.PythonHome = pythonHome; PythonEngine.Initialize(); - Assert.AreEqual(pythonHome, PythonEngine.PythonHome); + Assert.That(PythonEngine.PythonHome, Is.EqualTo(pythonHome)); PythonEngine.Shutdown(); // Restoring valid pythonhome. @@ -45,7 +45,7 @@ public void SetPythonHomeTwice() PythonEngine.PythonHome = pythonHome; PythonEngine.Initialize(); - Assert.AreEqual(pythonHome, PythonEngine.PythonHome); + Assert.That(PythonEngine.PythonHome, Is.EqualTo(pythonHome)); PythonEngine.Shutdown(); PythonEngine.PythonHome = pythonHomeBackup; @@ -65,7 +65,7 @@ public void SetPythonHomeEmptyString() } PythonEngine.PythonHome = ""; - Assert.AreEqual("", PythonEngine.PythonHome); + Assert.That(PythonEngine.PythonHome, Is.EqualTo("")); PythonEngine.PythonHome = backup; PythonEngine.Shutdown(); @@ -86,7 +86,7 @@ public void SetProgramName() PythonEngine.ProgramName = programName; PythonEngine.Initialize(); - Assert.AreEqual(programName, PythonEngine.ProgramName); + Assert.That(PythonEngine.ProgramName, Is.EqualTo(programName)); PythonEngine.Shutdown(); PythonEngine.ProgramName = programNameBackup; @@ -124,7 +124,7 @@ public void SetPythonPath() PythonEngine.PythonPath = path; PythonEngine.Initialize(); - Assert.AreEqual(path, PythonEngine.PythonPath); + Assert.That(PythonEngine.PythonPath, Is.EqualTo(path)); if (importShouldSucceed) Py.Import(moduleName); PythonEngine.Shutdown(); diff --git a/src/embed_tests/NeedsReinit/TestRuntime.cs b/src/embed_tests/NeedsReinit/TestRuntime.cs index 193bf57d3..ded0d9d2a 100644 --- a/src/embed_tests/NeedsReinit/TestRuntime.cs +++ b/src/embed_tests/NeedsReinit/TestRuntime.cs @@ -16,11 +16,11 @@ public static void Py_IsInitializedValue() Runtime.Runtime.PyGILState_Ensure(); } Runtime.Runtime.Py_Finalize(); - Assert.AreEqual(0, Runtime.Runtime.Py_IsInitialized()); + Assert.That(Runtime.Runtime.Py_IsInitialized(), Is.EqualTo(0)); Runtime.Runtime.Py_Initialize(); - Assert.AreEqual(1, Runtime.Runtime.Py_IsInitialized()); + Assert.That(Runtime.Runtime.Py_IsInitialized(), Is.EqualTo(1)); Runtime.Runtime.Py_Finalize(); - Assert.AreEqual(0, Runtime.Runtime.Py_IsInitialized()); + Assert.That(Runtime.Runtime.Py_IsInitialized(), Is.EqualTo(0)); } [Test] @@ -30,27 +30,27 @@ public static void RefCountTest() using var op = Runtime.Runtime.PyString_FromString("FooBar"); // New object RefCount should be one - Assert.AreEqual(1, Runtime.Runtime.Refcount32(op.BorrowOrThrow())); + Assert.That(Runtime.Runtime.Refcount32(op.BorrowOrThrow()), Is.EqualTo(1)); // Checking refcount didn't change refcount - Assert.AreEqual(1, Runtime.Runtime.Refcount32(op.Borrow())); + Assert.That(Runtime.Runtime.Refcount32(op.Borrow()), Is.EqualTo(1)); // Borrowing a reference doesn't increase refcount BorrowedReference p = op.Borrow(); - Assert.AreEqual(1, Runtime.Runtime.Refcount32(p)); + Assert.That(Runtime.Runtime.Refcount32(p), Is.EqualTo(1)); // Py_IncRef/Py_DecRef increase and decrease RefCount Runtime.Runtime.Py_IncRef(op.Borrow()); - Assert.AreEqual(2, Runtime.Runtime.Refcount32(p)); + Assert.That(Runtime.Runtime.Refcount32(p), Is.EqualTo(2)); Runtime.Runtime.Py_DecRef(StolenReference.DangerousFromPointer(op.DangerousGetAddress())); - Assert.AreEqual(1, Runtime.Runtime.Refcount32(p)); + Assert.That(Runtime.Runtime.Refcount32(p), Is.EqualTo(1)); // XIncref/XDecref increase and decrease RefCount #pragma warning disable CS0618 // Type or member is obsolete. We are testing corresponding members Runtime.Runtime.XIncref(p); - Assert.AreEqual(2, Runtime.Runtime.Refcount32(p)); + Assert.That(Runtime.Runtime.Refcount32(p), Is.EqualTo(2)); Runtime.Runtime.XDecref(op.Steal()); - Assert.AreEqual(1, Runtime.Runtime.Refcount32(p)); + Assert.That(Runtime.Runtime.Refcount32(p), Is.EqualTo(1)); #pragma warning restore CS0618 // Type or member is obsolete op.Dispose(); diff --git a/src/embed_tests/NumPyTests.cs b/src/embed_tests/NumPyTests.cs index 6f4a85716..57ff11b2b 100644 --- a/src/embed_tests/NumPyTests.cs +++ b/src/embed_tests/NumPyTests.cs @@ -25,7 +25,7 @@ public void Dispose() [Test] public void TestReadme() { - Assert.AreEqual("1.0", np.cos(np.pi * 2).ToString()); + Assert.That(np.cos(np.pi * 2).ToString(), Is.EqualTo("1.0")); dynamic sin = np.sin; StringAssert.StartsWith("-0.95892", sin(5).ToString()); @@ -34,12 +34,12 @@ public void TestReadme() 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()); + Assert.That(a.dtype.ToString(), Is.EqualTo("float64")); dynamic b = np.array(new List { 6, 5, 4 }, Py.kw("dtype", np.int32)); - Assert.AreEqual("int32", b.dtype.ToString()); + Assert.That(b.dtype.ToString(), Is.EqualTo("int32")); - Assert.AreEqual("[ 6. 10. 12.]", (a * b).ToString().Replace(" ", " ")); + Assert.That((a * b).ToString().Replace(" ", " "), Is.EqualTo("[ 6. 10. 12.]")); } [Test] @@ -47,9 +47,9 @@ public void MultidimensionalNumPyArray() { var array = new[,] { { 1, 2 }, { 3, 4 } }; var ndarray = np.InvokeMethod("asarray", array.ToPython()); - Assert.AreEqual((2, 2), ndarray.GetAttr("shape").As<(int, int)>()); - Assert.AreEqual(1, ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As()); - Assert.AreEqual(array[1, 0], ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As()); + Assert.That(ndarray.GetAttr("shape").As<(int, int)>(), Is.EqualTo((2, 2))); + Assert.That(ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As(), Is.EqualTo(1)); + Assert.That(ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As(), Is.EqualTo(array[1, 0])); } [Test] @@ -57,9 +57,9 @@ public void Int64Array() { var array = new long[,] { { 1, 2 }, { 3, 4 } }; var ndarray = np.InvokeMethod("asarray", array.ToPython()); - Assert.AreEqual((2, 2), ndarray.GetAttr("shape").As<(int, int)>()); - Assert.AreEqual(1, ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As()); - Assert.AreEqual(array[1, 0], ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As()); + Assert.That(ndarray.GetAttr("shape").As<(int, int)>(), Is.EqualTo((2, 2))); + Assert.That(ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As(), Is.EqualTo(1)); + Assert.That(ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As(), Is.EqualTo(array[1, 0])); } [Test] diff --git a/src/embed_tests/StateSerialization/MethodSerialization.cs b/src/embed_tests/StateSerialization/MethodSerialization.cs index d565c1e7a..1a032098b 100644 --- a/src/embed_tests/StateSerialization/MethodSerialization.cs +++ b/src/embed_tests/StateSerialization/MethodSerialization.cs @@ -16,7 +16,7 @@ public void GenericRoundtrip() var maybeMethod = new MaybeMethodBase(method); var restored = SerializationRoundtrip(maybeMethod); Assert.IsTrue(restored.Valid); - Assert.AreEqual(method, restored.Value); + Assert.That(restored.Value, Is.EqualTo(method)); } [Test] @@ -26,7 +26,7 @@ public void ConstructorRoundtrip() var maybeConstructor = new MaybeMethodBase(ctor); var restored = SerializationRoundtrip(maybeConstructor); Assert.IsTrue(restored.Valid); - Assert.AreEqual(ctor, restored.Value); + Assert.That(restored.Value, Is.EqualTo(ctor)); } static T SerializationRoundtrip(T item) diff --git a/src/embed_tests/TestConverter.cs b/src/embed_tests/TestConverter.cs index 3feced8d0..743731285 100644 --- a/src/embed_tests/TestConverter.cs +++ b/src/embed_tests/TestConverter.cs @@ -155,7 +155,7 @@ public void RawListProxy() var list = new List {"hello", "world"}; var listProxy = PyObject.FromManagedObject(list); var clrObject = (CLRObject)ManagedType.GetManagedObject(listProxy); - Assert.AreSame(list, clrObject.inst); + Assert.That(clrObject.inst, Is.SameAs(list)); } [Test] @@ -164,7 +164,7 @@ public void RawPyObjectProxy() var pyObject = "hello world!".ToPython(); var pyObjectProxy = PyObject.FromManagedObject(pyObject); var clrObject = (CLRObject)ManagedType.GetManagedObject(pyObjectProxy); - Assert.AreSame(pyObject, clrObject.inst); + Assert.That(clrObject.inst, Is.SameAs(pyObject)); #pragma warning disable CS0612 // Type or member is obsolete const string handlePropertyName = nameof(PyObject.Handle); diff --git a/src/embed_tests/TestPyFloat.cs b/src/embed_tests/TestPyFloat.cs index c6111f180..5c7f3cce7 100644 --- a/src/embed_tests/TestPyFloat.cs +++ b/src/embed_tests/TestPyFloat.cs @@ -15,7 +15,7 @@ public void FloatCtor() const float a = 4.5F; var i = new PyFloat(a); Assert.True(PyFloat.IsFloatType(i)); - // Assert.Assert.AreEqual(i, a.ToInt32()); + // Assert.Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -24,7 +24,7 @@ public void PyObjectCtorGood() var i = new PyFloat(5); var a = new PyFloat(i); Assert.True(PyFloat.IsFloatType(a)); - // Assert.Assert.AreEqual(i, a.ToInt32()); + // Assert.Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -45,7 +45,7 @@ public void DoubleCtor() const double a = 4.5; var i = new PyFloat(a); Assert.True(PyFloat.IsFloatType(i)); - // Assert.Assert.AreEqual(i, a.ToInt32()); + // Assert.Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -54,7 +54,7 @@ public void StringIntCtor() const string a = "5"; var i = new PyFloat(a); Assert.True(PyFloat.IsFloatType(i)); - // Assert.Assert.AreEqual(i, a.ToInt32()); + // Assert.Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -63,7 +63,7 @@ public void StringDoubleCtor() const string a = "4.5"; var i = new PyFloat(a); Assert.True(PyFloat.IsFloatType(i)); - // Assert.Assert.AreEqual(i, a.ToInt32()); + // Assert.Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -101,7 +101,7 @@ public void AsFloatGood() PyFloat s = PyFloat.AsFloat(i); Assert.True(PyFloat.IsFloatType(s)); - // Assert.Assert.AreEqual(i, a.ToInt32()); + // Assert.Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] diff --git a/src/embed_tests/TestPyInt.cs b/src/embed_tests/TestPyInt.cs index 36319cf1a..e79792d51 100644 --- a/src/embed_tests/TestPyInt.cs +++ b/src/embed_tests/TestPyInt.cs @@ -15,7 +15,7 @@ public void TestCtorInt() { const int i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -23,7 +23,7 @@ public void TestCtorUInt() { const uint i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -31,7 +31,7 @@ public void TestCtorLong() { const long i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -39,7 +39,7 @@ public void TestCtorULong() { const ulong i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -47,7 +47,7 @@ public void TestCtorShort() { const short i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -55,7 +55,7 @@ public void TestCtorUShort() { const ushort i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -63,7 +63,7 @@ public void TestCtorByte() { const byte i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -71,7 +71,7 @@ public void TestCtorSByte() { const sbyte i = 5; var a = new PyInt(i); - Assert.AreEqual(i, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(i)); } [Test] @@ -79,7 +79,7 @@ public void TestCtorPyObject() { var i = new PyInt(5); var a = new PyInt(i); - Assert.AreEqual(5, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(5)); } [Test] @@ -99,7 +99,7 @@ public void TestCtorString() { const string i = "5"; var a = new PyInt(i); - Assert.AreEqual(5, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(5)); } [Test] @@ -133,7 +133,7 @@ public void TestAsIntGood() { var i = new PyInt(5); var a = PyInt.AsInt(i); - Assert.AreEqual(5, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(5)); } [Test] @@ -152,7 +152,7 @@ public void TestConvertToInt32() { var a = new PyInt(5); Assert.IsInstanceOf(typeof(int), a.ToInt32()); - Assert.AreEqual(5, a.ToInt32()); + Assert.That(a.ToInt32(), Is.EqualTo(5)); } [Test] @@ -160,7 +160,7 @@ public void TestConvertToInt16() { var a = new PyInt(5); Assert.IsInstanceOf(typeof(short), a.ToInt16()); - Assert.AreEqual(5, a.ToInt16()); + Assert.That(a.ToInt16(), Is.EqualTo(5)); } [Test] @@ -169,7 +169,7 @@ public void TestConvertToInt64() long val = 5 + (long)int.MaxValue; var a = new PyInt(val); Assert.IsInstanceOf(typeof(long), a.ToInt64()); - Assert.AreEqual(val, a.ToInt64()); + Assert.That(a.ToInt64(), Is.EqualTo(val)); } [Test] @@ -195,7 +195,7 @@ public void ToBigInteger() var expected = simpleValues.Select(v => new BigInteger(v)).ToArray(); var actual = simpleValues.Select(v => new PyInt(v).ToBigInteger()).ToArray(); - CollectionAssert.AreEqual(expected, actual); + Assert.That(actual, Is.EqualTo(expected)); } [Test] @@ -204,37 +204,37 @@ public void CompareTo() var v = new PyInt(42); #region Signed - Assert.AreEqual(0, v.CompareTo(42L)); - Assert.AreEqual(0, v.CompareTo(42)); - Assert.AreEqual(0, v.CompareTo((short)42)); - Assert.AreEqual(0, v.CompareTo((sbyte)42)); - - Assert.AreEqual(1, v.CompareTo(41L)); - Assert.AreEqual(1, v.CompareTo(41)); - Assert.AreEqual(1, v.CompareTo((short)41)); - Assert.AreEqual(1, v.CompareTo((sbyte)41)); - - Assert.AreEqual(-1, v.CompareTo(43L)); - Assert.AreEqual(-1, v.CompareTo(43)); - Assert.AreEqual(-1, v.CompareTo((short)43)); - Assert.AreEqual(-1, v.CompareTo((sbyte)43)); + Assert.That(v.CompareTo(42L), Is.EqualTo(0)); + Assert.That(v.CompareTo(42), Is.EqualTo(0)); + Assert.That(v.CompareTo((short)42), Is.EqualTo(0)); + Assert.That(v.CompareTo((sbyte)42), Is.EqualTo(0)); + + Assert.That(v.CompareTo(41L), Is.EqualTo(1)); + Assert.That(v.CompareTo(41), Is.EqualTo(1)); + Assert.That(v.CompareTo((short)41), Is.EqualTo(1)); + Assert.That(v.CompareTo((sbyte)41), Is.EqualTo(1)); + + Assert.That(v.CompareTo(43L), Is.EqualTo(-1)); + Assert.That(v.CompareTo(43), Is.EqualTo(-1)); + Assert.That(v.CompareTo((short)43), Is.EqualTo(-1)); + Assert.That(v.CompareTo((sbyte)43), Is.EqualTo(-1)); #endregion Signed #region Unsigned - Assert.AreEqual(0, v.CompareTo(42UL)); - Assert.AreEqual(0, v.CompareTo(42U)); - Assert.AreEqual(0, v.CompareTo((ushort)42)); - Assert.AreEqual(0, v.CompareTo((byte)42)); - - Assert.AreEqual(1, v.CompareTo(41UL)); - Assert.AreEqual(1, v.CompareTo(41U)); - Assert.AreEqual(1, v.CompareTo((ushort)41)); - Assert.AreEqual(1, v.CompareTo((byte)41)); - - Assert.AreEqual(-1, v.CompareTo(43UL)); - Assert.AreEqual(-1, v.CompareTo(43U)); - Assert.AreEqual(-1, v.CompareTo((ushort)43)); - Assert.AreEqual(-1, v.CompareTo((byte)43)); + Assert.That(v.CompareTo(42UL), Is.EqualTo(0)); + Assert.That(v.CompareTo(42U), Is.EqualTo(0)); + Assert.That(v.CompareTo((ushort)42), Is.EqualTo(0)); + Assert.That(v.CompareTo((byte)42), Is.EqualTo(0)); + + Assert.That(v.CompareTo(41UL), Is.EqualTo(1)); + Assert.That(v.CompareTo(41U), Is.EqualTo(1)); + Assert.That(v.CompareTo((ushort)41), Is.EqualTo(1)); + Assert.That(v.CompareTo((byte)41), Is.EqualTo(1)); + + Assert.That(v.CompareTo(43UL), Is.EqualTo(-1)); + Assert.That(v.CompareTo(43U), Is.EqualTo(-1)); + Assert.That(v.CompareTo((ushort)43), Is.EqualTo(-1)); + Assert.That(v.CompareTo((byte)43), Is.EqualTo(-1)); #endregion Unsigned } @@ -273,10 +273,10 @@ public void ToBigIntegerLarge() { BigInteger val = BigInteger.Pow(2, 1024) + 3; var pyInt = new PyInt(val); - Assert.AreEqual(val, pyInt.ToBigInteger()); + Assert.That(pyInt.ToBigInteger(), Is.EqualTo(val)); val = -val; pyInt = new PyInt(val); - Assert.AreEqual(val, pyInt.ToBigInteger()); + Assert.That(pyInt.ToBigInteger(), Is.EqualTo(val)); } } } diff --git a/src/embed_tests/TestPyIter.cs b/src/embed_tests/TestPyIter.cs index 5da660242..4492080e8 100644 --- a/src/embed_tests/TestPyIter.cs +++ b/src/embed_tests/TestPyIter.cs @@ -18,7 +18,7 @@ public void KeepOldObjects() PyObject[] chars = testString.ToArray(); Assert.IsTrue(chars.Length > 1); string reconstructed = string.Concat(chars.Select(c => c.As())); - Assert.AreEqual(testString.As(), reconstructed); + Assert.That(reconstructed, Is.EqualTo(testString.As())); } } } diff --git a/src/embed_tests/TestPyList.cs b/src/embed_tests/TestPyList.cs index a380f0b2d..927eaa1da 100644 --- a/src/embed_tests/TestPyList.cs +++ b/src/embed_tests/TestPyList.cs @@ -29,7 +29,7 @@ public void TestStringAsListType() var ex = Assert.Throws(() => t = PyList.AsList(i)); - Assert.AreEqual("'int' object is not iterable", ex.Message); + Assert.That(ex.Message, Is.EqualTo("'int' object is not iterable")); Assert.IsNull(t); } @@ -49,7 +49,7 @@ public void TestEmptyCtor() var s = new PyList(); Assert.IsInstanceOf(typeof(PyList), s); - Assert.AreEqual(0, s.Length()); + Assert.That(s.Length(), Is.EqualTo(0)); } [Test] @@ -59,10 +59,10 @@ public void TestPyObjectArrayCtor() var s = new PyList(ai); Assert.IsInstanceOf(typeof(PyList), s); - Assert.AreEqual(3, s.Length()); - Assert.AreEqual("3", s[0].ToString()); - Assert.AreEqual("2", s[1].ToString()); - Assert.AreEqual("1", s[2].ToString()); + Assert.That(s.Length(), Is.EqualTo(3)); + Assert.That(s[0].ToString(), Is.EqualTo("3")); + Assert.That(s[1].ToString(), Is.EqualTo("2")); + Assert.That(s[2].ToString(), Is.EqualTo("1")); } [Test] @@ -72,7 +72,7 @@ public void TestPyObjectCtor() var s = new PyList(a); Assert.IsInstanceOf(typeof(PyList), s); - Assert.AreEqual(0, s.Length()); + Assert.That(s.Length(), Is.EqualTo(0)); } [Test] @@ -83,7 +83,7 @@ public void TestBadPyObjectCtor() var ex = Assert.Throws(() => t = new PyList(i)); - Assert.AreEqual("object is not a list", ex.Message); + Assert.That(ex.Message, Is.EqualTo("object is not a list")); Assert.IsNull(t); } @@ -94,8 +94,8 @@ public void TestAppend() var s = new PyList(ai); s.Append(new PyInt(4)); - Assert.AreEqual(4, s.Length()); - Assert.AreEqual("4", s[3].ToString()); + Assert.That(s.Length(), Is.EqualTo(4)); + Assert.That(s[3].ToString(), Is.EqualTo("4")); } [Test] @@ -105,8 +105,8 @@ public void TestInsert() var s = new PyList(ai); s.Insert(0, new PyInt(4)); - Assert.AreEqual(4, s.Length()); - Assert.AreEqual("4", s[0].ToString()); + Assert.That(s.Length(), Is.EqualTo(4)); + Assert.That(s[0].ToString(), Is.EqualTo("4")); } [Test] @@ -117,10 +117,10 @@ public void TestReverse() s.Reverse(); - Assert.AreEqual(3, s.Length()); - Assert.AreEqual("2", s[0].ToString()); - Assert.AreEqual("1", s[1].ToString()); - Assert.AreEqual("3", s[2].ToString()); + Assert.That(s.Length(), Is.EqualTo(3)); + Assert.That(s[0].ToString(), Is.EqualTo("2")); + Assert.That(s[1].ToString(), Is.EqualTo("1")); + Assert.That(s[2].ToString(), Is.EqualTo("3")); } [Test] @@ -131,10 +131,10 @@ public void TestSort() s.Sort(); - Assert.AreEqual(3, s.Length()); - Assert.AreEqual("1", s[0].ToString()); - Assert.AreEqual("2", s[1].ToString()); - Assert.AreEqual("3", s[2].ToString()); + Assert.That(s.Length(), Is.EqualTo(3)); + Assert.That(s[0].ToString(), Is.EqualTo("1")); + Assert.That(s[1].ToString(), Is.EqualTo("2")); + Assert.That(s[2].ToString(), Is.EqualTo("3")); } [Test] @@ -151,10 +151,10 @@ public void TestOnPyList() result.Add(item.ToString()); } - Assert.AreEqual(3, result.Count); - Assert.AreEqual("foo", result[0]); - Assert.AreEqual("bar", result[1]); - Assert.AreEqual("baz", result[2]); + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result[0], Is.EqualTo("foo")); + Assert.That(result[1], Is.EqualTo("bar")); + Assert.That(result[2], Is.EqualTo("baz")); } } } diff --git a/src/embed_tests/TestPyObject.cs b/src/embed_tests/TestPyObject.cs index f762b94e9..27b0a7b26 100644 --- a/src/embed_tests/TestPyObject.cs +++ b/src/embed_tests/TestPyObject.cs @@ -65,7 +65,7 @@ public void UnaryMinus_ThrowsOnBadType() { dynamic list = new PyList(); var error = Assert.Throws(() => list = -list); - Assert.AreEqual("TypeError", error.Type.Name); + Assert.That(error.Type.Name, Is.EqualTo("TypeError")); } [Test] @@ -80,7 +80,7 @@ public void GetAttrDefault_IgnoresAttributeErrorOnly() var typeErrResult = Assert.Throws( () => ob.GetAttr(nameof(PyObjectTestMethods.RaisesTypeError), fallback) ); - Assert.AreEqual(Exceptions.TypeError, typeErrResult.Type); + Assert.That(typeErrResult.Type, Is.EqualTo(Exceptions.TypeError)); } // regression test from https://github.com/pythonnet/pythonnet/issues/1642 diff --git a/src/embed_tests/TestPySequence.cs b/src/embed_tests/TestPySequence.cs index 339ea1e83..df9e73863 100644 --- a/src/embed_tests/TestPySequence.cs +++ b/src/embed_tests/TestPySequence.cs @@ -26,16 +26,16 @@ public void TestGetSlice() var t = new PyString("FooBar"); PyObject s = t.GetSlice(0, 3); - Assert.AreEqual("Foo", s.ToString()); + Assert.That(s.ToString(), Is.EqualTo("Foo")); PyObject s2 = t.GetSlice(3, 6); - Assert.AreEqual("Bar", s2.ToString()); + Assert.That(s2.ToString(), Is.EqualTo("Bar")); PyObject s3 = t.GetSlice(0, 6); - Assert.AreEqual("FooBar", s3.ToString()); + Assert.That(s3.ToString(), Is.EqualTo("FooBar")); PyObject s4 = t.GetSlice(0, 12); - Assert.AreEqual("FooBar", s4.ToString()); + Assert.That(s4.ToString(), Is.EqualTo("FooBar")); } [Test] @@ -46,7 +46,7 @@ public void TestConcat() PyObject actual = t1.Concat(t2); - Assert.AreEqual("FooBar", actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo("FooBar")); } [Test] @@ -55,10 +55,10 @@ public void TestRepeat() var t1 = new PyString("Foo"); PyObject actual = t1.Repeat(3); - Assert.AreEqual("FooFooFoo", actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo("FooFooFoo")); actual = t1.Repeat(-3); - Assert.AreEqual("", actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo("")); } [Test] @@ -75,9 +75,9 @@ public void TestIndex() { var t1 = new PyString("FooBar"); - Assert.AreEqual(4, t1.Index32(new PyString("a"))); - Assert.AreEqual(5L, t1.Index64(new PyString("r"))); - Assert.AreEqual(-(nint)1, t1.Index(new PyString("z"))); + Assert.That(t1.Index32(new PyString("a")), Is.EqualTo(4)); + Assert.That(t1.Index64(new PyString("r")), Is.EqualTo(5L)); + Assert.That(t1.Index(new PyString("z")), Is.EqualTo(-(nint)1)); } } } diff --git a/src/embed_tests/TestPyString.cs b/src/embed_tests/TestPyString.cs index a1fdd6079..8949ca28c 100644 --- a/src/embed_tests/TestPyString.cs +++ b/src/embed_tests/TestPyString.cs @@ -11,7 +11,7 @@ public void TestStringCtor() { const string expected = "foo"; var actual = new PyString(expected); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] @@ -19,11 +19,10 @@ public void TestEmptyStringCtor() { const string expected = ""; var actual = new PyString(expected); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] - [Ignore("Ambiguous behavior between PY2/PY3. Needs remapping")] public void TestPyObjectCtor() { const string expected = "Foo"; @@ -31,7 +30,7 @@ public void TestPyObjectCtor() var t = new PyString(expected); var actual = new PyString(t); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] @@ -54,11 +53,10 @@ public void TestCtorBorrowed() var t = new PyString(expected); var actual = new PyString(t.Reference); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] - [Ignore("Ambiguous behavior between PY2/PY3. Needs remapping")] public void IsStringTrue() { var t = new PyString("foo"); @@ -79,7 +77,7 @@ public void TestUnicode() { const string expected = "foo\u00e9"; PyObject actual = new PyString(expected); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] @@ -87,8 +85,8 @@ public void TestUnicodeSurrogateToString() { var expected = "foo\ud83d\udc3c"; var actual = PythonEngine.Eval("'foo\ud83d\udc3c'"); - Assert.AreEqual(4, actual.Length()); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.Length(), Is.EqualTo(4)); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] @@ -97,8 +95,8 @@ public void TestUnicodeSurrogate() const string expected = "foo\ud83d\udc3c"; // "foo🐼" PyObject actual = new PyString(expected); // python treats "foo🐼" as 4 characters, dotnet as 5 - Assert.AreEqual(4, actual.Length()); - Assert.AreEqual(expected, actual.ToString()); + Assert.That(actual.Length(), Is.EqualTo(4)); + Assert.That(actual.ToString(), Is.EqualTo(expected)); } [Test] @@ -106,9 +104,9 @@ public void CompareTo() { var a = new PyString("foo"); - Assert.AreEqual(0, a.CompareTo("foo")); - Assert.AreEqual("foo".CompareTo("bar"), a.CompareTo("bar")); - Assert.AreEqual("foo".CompareTo("foz"), a.CompareTo("foz")); + Assert.That(a.CompareTo("foo"), Is.EqualTo(0)); + Assert.That(a.CompareTo("bar"), Is.EqualTo("foo".CompareTo("bar"))); + Assert.That(a.CompareTo("foz"), Is.EqualTo("foo".CompareTo("foz"))); } [Test] diff --git a/src/embed_tests/TestPyTuple.cs b/src/embed_tests/TestPyTuple.cs index 3a3fbf2a0..2a9238533 100644 --- a/src/embed_tests/TestPyTuple.cs +++ b/src/embed_tests/TestPyTuple.cs @@ -31,7 +31,7 @@ public void TestPyTupleIsTupleType() public void TestPyTupleEmpty() { var t = new PyTuple(); - Assert.AreEqual(0, t.Length()); + Assert.That(t.Length(), Is.EqualTo(0)); } [Test] @@ -42,7 +42,7 @@ public void TestPyTupleBadCtor() var ex = Assert.Throws(() => t = new PyTuple(i)); - Assert.AreEqual("object is not a tuple", ex.Message); + Assert.That(ex.Message, Is.EqualTo("object is not a tuple")); Assert.IsNull(t); } @@ -52,7 +52,7 @@ public void TestPyTupleCtorEmptyArray() var a = new PyObject[] { }; var t = new PyTuple(a); - Assert.AreEqual(0, t.Length()); + Assert.That(t.Length(), Is.EqualTo(0)); } [Test] @@ -61,7 +61,7 @@ public void TestPyTupleCtorArrayPyIntEmpty() var a = new PyInt[] { }; var t = new PyTuple(a); - Assert.AreEqual(0, t.Length()); + Assert.That(t.Length(), Is.EqualTo(0)); } [Test] @@ -70,7 +70,7 @@ public void TestPyTupleCtorArray() var a = new PyObject[] { new PyInt(1), new PyString("Foo") }; var t = new PyTuple(a); - Assert.AreEqual(2, t.Length()); + Assert.That(t.Length(), Is.EqualTo(2)); } /// @@ -81,8 +81,6 @@ public void TestPyTupleCtorArray() /// Test has second purpose. Currently it generated an Exception /// that the GC failed to remove often and caused AppDomain unload /// errors at the end of tests. See GH#397 for more info. - /// - /// Curious, on PY27 it gets a Unicode on the ex.Message. On PY3+ its string. /// [Test] public void TestPyTupleInvalidAppend() @@ -93,7 +91,7 @@ public void TestPyTupleInvalidAppend() var ex = Assert.Throws(() => t.Concat(s)); StringAssert.StartsWith("can only concatenate tuple", ex.Message); - Assert.AreEqual(0, t.Length()); + Assert.That(t.Length(), Is.EqualTo(0)); Assert.IsEmpty(t); } @@ -116,9 +114,9 @@ public void TestPyTupleStringConvert() Assert.IsNotNull(t); Assert.IsInstanceOf(typeof(PyTuple), t); - Assert.AreEqual("f", t[0].ToString()); - Assert.AreEqual("o", t[1].ToString()); - Assert.AreEqual("o", t[2].ToString()); + Assert.That(t[0].ToString(), Is.EqualTo("f")); + Assert.That(t[1].ToString(), Is.EqualTo("o")); + Assert.That(t[2].ToString(), Is.EqualTo("o")); } [Test] @@ -152,7 +150,7 @@ public void TestInvalidAsTuple() var ex = Assert.Throws(() => t = PyTuple.AsTuple(i)); - Assert.AreEqual("'int' object is not iterable", ex.Message); + Assert.That(ex.Message, Is.EqualTo("'int' object is not iterable")); Assert.IsNull(t); } } diff --git a/src/embed_tests/TestPyType.cs b/src/embed_tests/TestPyType.cs index c29032a8a..cd7d37816 100644 --- a/src/embed_tests/TestPyType.cs +++ b/src/embed_tests/TestPyType.cs @@ -28,9 +28,9 @@ public void CanCreateHeapType() ); using var type = new PyType(spec); - Assert.AreEqual(name, type.GetAttr("__name__").As()); - Assert.AreEqual(name, type.Name); - Assert.AreEqual(docStr, type.GetAttr("__doc__").As()); + Assert.That(type.GetAttr("__name__").As(), Is.EqualTo(name)); + Assert.That(type.Name, Is.EqualTo(name)); + Assert.That(type.GetAttr("__doc__").As(), Is.EqualTo(docStr)); } } } diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs index 485931cfb..8d5a17491 100644 --- a/src/embed_tests/TestPythonEngineProperties.cs +++ b/src/embed_tests/TestPythonEngineProperties.cs @@ -72,7 +72,7 @@ public static void GetPythonPathDefault() { string s = PythonEngine.PythonPath; - StringAssert.Contains("python", s.ToLower()); + Assert.That(s.ToLower(), Does.Contain("python")); } [Test] @@ -94,7 +94,7 @@ public static void GetPythonHomeDefault() string enginePythonHome = PythonEngine.PythonHome; - Assert.AreEqual(envPythonHome, enginePythonHome); + Assert.That(enginePythonHome, Is.EqualTo(envPythonHome)); } } } diff --git a/src/embed_tests/TestPythonException.cs b/src/embed_tests/TestPythonException.cs index 91a412749..f69ba7a8f 100644 --- a/src/embed_tests/TestPythonException.cs +++ b/src/embed_tests/TestPythonException.cs @@ -14,7 +14,7 @@ public void TestMessage() var ex = Assert.Throws(() => foo = list[0]); - Assert.AreEqual("list index out of range", ex.Message); + Assert.That(ex.Message, Is.EqualTo("list index out of range")); Assert.IsNull(foo); } @@ -26,7 +26,7 @@ public void TestType() var ex = Assert.Throws(() => foo = list[0]); - Assert.AreEqual("IndexError", ex.Type.Name); + Assert.That(ex.Type.Name, Is.EqualTo("IndexError")); Assert.IsNull(foo); } @@ -108,7 +108,7 @@ public void TestPythonExceptionFormatNoTraceback() catch (PythonException ex) { // ImportError/ModuleNotFoundError do not have a traceback when not running in a script - Assert.AreEqual(ex.StackTrace, ex.Format()); + Assert.That(ex.Format(), Is.EqualTo(ex.StackTrace)); } } @@ -151,7 +151,7 @@ def __init__(self, val): // exception was raised by initializing the exception Assert.IsFalse(PythonReferenceComparer.Instance.Equals(type, typeObj)); // the message should now be the string from the throw exception during normalization - Assert.AreEqual("invalid literal for int() with base 10: 'dummy string'", strObj.ToString()); + Assert.That(strObj.ToString(), Is.EqualTo("invalid literal for int() with base 10: 'dummy string'")); } } diff --git a/src/embed_tests/pyimport.cs b/src/embed_tests/pyimport.cs index c774af345..0236bf0f6 100644 --- a/src/embed_tests/pyimport.cs +++ b/src/embed_tests/pyimport.cs @@ -73,12 +73,12 @@ public void TestSysArgsImportException() public void TestCastGlobalVar() { dynamic foo = Py.Import("PyImportTest.cast_global_var"); - Assert.AreEqual("1", foo.FOO.ToString()); - Assert.AreEqual("1", foo.test_foo().ToString()); + Assert.That(foo.FOO.ToString(), Is.EqualTo("1")); + Assert.That(foo.test_foo().ToString(), Is.EqualTo("1")); foo.FOO = 2; - Assert.AreEqual("2", foo.FOO.ToString()); - Assert.AreEqual("2", foo.test_foo().ToString()); + Assert.That(foo.FOO.ToString(), Is.EqualTo("2")); + Assert.That(foo.test_foo().ToString(), Is.EqualTo("2")); } [Test] diff --git a/src/runtime/Native/CustomMarshaler.cs b/src/runtime/Native/CustomMarshaler.cs index 299af3a33..58f7c6b20 100644 --- a/src/runtime/Native/CustomMarshaler.cs +++ b/src/runtime/Native/CustomMarshaler.cs @@ -72,7 +72,7 @@ public static ICustomMarshaler GetInstance(string cookie) return Instance; } - public static string? PtrToStringUni(IntPtr p) + public static string? PtrToString(IntPtr p) { if (p == IntPtr.Zero) { @@ -106,36 +106,19 @@ public static int GetUnicodeByteLength(IntPtr p) } /// - /// Utility function for Marshaling Unicode on PY3 and AnsiStr on PY2. - /// Use on functions whose Input signatures changed between PY2/PY3. - /// Ex. Py_SetPythonHome + /// Utility function for Marshaling Unicode /// /// Managed String /// - /// Ptr to Native String ANSI(PY2)/Unicode(PY3/UCS2)/UTF32(PY3/UCS4. + /// Ptr to Native String /// /// /// You MUST deallocate the IntPtr of the Return when done with it. /// - public static IntPtr Py3UnicodePy2StringtoPtr(string s) + public static IntPtr StringToPtr(string s) { return Instance.MarshalManagedToNative(s); } - - /// - /// Utility function for Marshaling Unicode IntPtr on PY3 and - /// AnsiStr IntPtr on PY2 to Managed Strings. Use on Python functions - /// whose return type changed between PY2/PY3. - /// Ex. Py_GetPythonHome - /// - /// Native Ansi/Unicode/UTF32 String - /// - /// Managed String - /// - public static string? PtrToPy3UnicodePy2String(IntPtr p) - { - return PtrToStringUni(p); - } } diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index 13855adef..fd04d4a3e 100644 --- a/src/runtime/PythonEngine.cs +++ b/src/runtime/PythonEngine.cs @@ -88,13 +88,13 @@ public static string ProgramName get { IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetProgramName()); - return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? ""; + return UcsMarshaler.PtrToString(p) ?? ""; } set { Marshal.FreeHGlobal(_programName); _programName = Runtime.TryUsingDll( - () => UcsMarshaler.Py3UnicodePy2StringtoPtr(value) + () => UcsMarshaler.StringToPtr(value) ); Runtime.Py_SetProgramName(_programName); } @@ -106,13 +106,13 @@ public static string PythonHome { EnsureInitialized(); IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetPythonHome()); - return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? ""; + return UcsMarshaler.PtrToString(p) ?? ""; } set { // this value is null in the beginning Marshal.FreeHGlobal(_pythonHome); - _pythonHome = UcsMarshaler.Py3UnicodePy2StringtoPtr(value); + _pythonHome = UcsMarshaler.StringToPtr(value); Runtime.TryUsingDll(() => Runtime.Py_SetPythonHome(_pythonHome)); } } @@ -122,13 +122,13 @@ public static string PythonPath get { IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetPath()); - return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? ""; + return UcsMarshaler.PtrToString(p) ?? ""; } set { Marshal.FreeHGlobal(_pythonPath); _pythonPath = Runtime.TryUsingDll( - () => UcsMarshaler.Py3UnicodePy2StringtoPtr(value) + () => UcsMarshaler.StringToPtr(value) ); Runtime.Py_SetPath(_pythonPath); } diff --git a/tests/conftest.py b/tests/conftest.py index 1ac20e1dd..0576e161e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -# TODO: move tests one out of src to project root. -# TODO: travis has numpy on their workers. Maybe add tests? """Helpers for testing.""" diff --git a/tests/test_array.py b/tests/test_array.py index d207a36fb..0d4028be8 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -120,10 +120,9 @@ def test_array_contains(): assert 3 in items assert 4 in items - assert not (5 in items) # "H:\Python27\Lib\unittest\case.py", line 592, in deprecated_func, - assert not (-1 in items) # TypeError: int() argument must be a string or a number, not 'NoneType' - assert not (None in items) # which threw ^ here which is a little odd. - # But when run from runtests.py. Not when this module ran by itself. + assert not (5 in items) + assert not (-1 in items) + assert not (None in items) def test_boolean_array(): diff --git a/tests/test_field.py b/tests/test_field.py index 52fed54cb..c45a85468 100644 --- a/tests/test_field.py +++ b/tests/test_field.py @@ -190,7 +190,6 @@ def test_field_descriptor_abuse(): def test_boolean_field(): """Test boolean fields.""" - # change this to true / false later for Python 2.3? ob = FieldTest() assert ob.BooleanField is False From ccb980a9b209d0aedbbbb62f9fdb58442d183dc0 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 12 May 2026 22:24:38 +0200 Subject: [PATCH 184/194] Silence compile-time warnings (#2725) --- src/embed_tests/Events.cs | 6 ++++++ src/runtime/PythonTypes/PyFloat.cs | 2 ++ src/runtime/PythonTypes/PyInt.cs | 2 ++ src/runtime/Util/ReflectionPolyfills.cs | 2 +- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/embed_tests/Events.cs b/src/embed_tests/Events.cs index cc51176dc..d2f43ea50 100644 --- a/src/embed_tests/Events.cs +++ b/src/embed_tests/Events.cs @@ -48,6 +48,12 @@ public ClassWithEventHandler() this.arr = new int[800]; } + // Reference LeakEvent to silence warning + protected virtual void OnLeakEvent(EventArgs e) + { + LeakEvent?.Invoke(this, e); + } + ~ClassWithEventHandler() { Interlocked.Decrement(ref alive); diff --git a/src/runtime/PythonTypes/PyFloat.cs b/src/runtime/PythonTypes/PyFloat.cs index 50621d5c2..379228f29 100644 --- a/src/runtime/PythonTypes/PyFloat.cs +++ b/src/runtime/PythonTypes/PyFloat.cs @@ -103,5 +103,7 @@ public static PyFloat AsFloat(PyObject value) public double ToDouble() => Runtime.PyFloat_AsDouble(obj); public override TypeCode GetTypeCode() => TypeCode.Double; + + public override int GetHashCode() => base.GetHashCode(); } } diff --git a/src/runtime/PythonTypes/PyInt.cs b/src/runtime/PythonTypes/PyInt.cs index 0d00f5a13..7ab7f9ec1 100644 --- a/src/runtime/PythonTypes/PyInt.cs +++ b/src/runtime/PythonTypes/PyInt.cs @@ -232,5 +232,7 @@ public string ToString(string format, IFormatProvider formatProvider) } public override TypeCode GetTypeCode() => TypeCode.Int64; + + public override int GetHashCode() => base.GetHashCode(); } } diff --git a/src/runtime/Util/ReflectionPolyfills.cs b/src/runtime/Util/ReflectionPolyfills.cs index b33698509..4bdf687c8 100644 --- a/src/runtime/Util/ReflectionPolyfills.cs +++ b/src/runtime/Util/ReflectionPolyfills.cs @@ -14,7 +14,7 @@ public static AssemblyBuilder DefineDynamicAssembly(this AppDomain _, AssemblyNa public static Type CreateType(this TypeBuilder typeBuilder) { - return typeBuilder.CreateTypeInfo(); + return typeBuilder.CreateTypeInfo()!; } public static T GetCustomAttribute(this Type type) where T: Attribute From 6bde465c4363efadc5f49ac15bee6b3631534dc8 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Wed, 13 May 2026 07:28:12 +0200 Subject: [PATCH 185/194] Extend timeout so macos x64 can finish --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8ead06fba..7b1bee82c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ jobs: build-test: name: Build and Test runs-on: ${{ matrix.os.instance }} - timeout-minutes: 15 + timeout-minutes: 30 strategy: fail-fast: false From 5dacfb46aad5a918ecdcb9eba428ab479b618ba4 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 18 Apr 2026 15:50:25 +0200 Subject: [PATCH 186/194] Implement support for DLR get/set --- src/runtime/InteropConfiguration.cs | 1 + .../Mixins/DynamicObjectMixinsProvider.cs | 47 ++++ src/runtime/Mixins/dlr.py | 18 ++ src/runtime/PythonEngine.cs | 2 +- src/runtime/TypeManager.cs | 165 ++++++++++++++ src/runtime/Types/ClassDerived.cs | 37 ++++ .../Types/DynamicObjectMemberAccessor.cs | 203 ++++++++++++++++++ src/runtime/Util/ConcurrentLruCache.cs | 103 +++++++++ src/testing/dlrtest.cs | 39 ++++ tests/test_dynamic.py | 173 +++++++++++++++ 10 files changed, 787 insertions(+), 1 deletion(-) create mode 100644 src/runtime/Mixins/DynamicObjectMixinsProvider.cs create mode 100644 src/runtime/Mixins/dlr.py create mode 100644 src/runtime/Types/DynamicObjectMemberAccessor.cs create mode 100644 src/runtime/Util/ConcurrentLruCache.cs create mode 100644 src/testing/dlrtest.cs create mode 100644 tests/test_dynamic.py diff --git a/src/runtime/InteropConfiguration.cs b/src/runtime/InteropConfiguration.cs index 781d0d01f..0cd441ebc 100644 --- a/src/runtime/InteropConfiguration.cs +++ b/src/runtime/InteropConfiguration.cs @@ -22,6 +22,7 @@ public static InteropConfiguration MakeDefault() { DefaultBaseTypeProvider.Instance, new CollectionMixinsProvider(new Lazy(() => Py.Import("clr._extras.collections"))), + new DynamicObjectMixinsProvider(new Lazy(() => Py.Import("clr._extras.dlr"))), }, }; } diff --git a/src/runtime/Mixins/DynamicObjectMixinsProvider.cs b/src/runtime/Mixins/DynamicObjectMixinsProvider.cs new file mode 100644 index 000000000..6afa31f7e --- /dev/null +++ b/src/runtime/Mixins/DynamicObjectMixinsProvider.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; + +namespace Python.Runtime.Mixins; + +class DynamicObjectMixinsProvider : IPythonBaseTypeProvider, IDisposable +{ + readonly Lazy mixinsModule; + + public DynamicObjectMixinsProvider(Lazy mixinsModule) => + this.mixinsModule = mixinsModule ?? throw new ArgumentNullException(nameof(mixinsModule)); + + public PyObject Mixins => mixinsModule.Value; + + public IEnumerable GetBaseTypes(Type type, IList existingBases) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + if (existingBases is null) + throw new ArgumentNullException(nameof(existingBases)); + + if (!typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type)) + return existingBases; + + var newBases = new List(existingBases) + { + new(Mixins.GetAttr("DynamicMetaObjectProviderMixin")) + }; + + if (type.IsInterface && type.BaseType is null) + { + newBases.RemoveAll(@base => PythonReferenceComparer.Instance.Equals(@base, Runtime.PyBaseObjectType)); + } + + return newBases; + } + + public void Dispose() + { + if (this.mixinsModule.IsValueCreated) + { + this.mixinsModule.Value.Dispose(); + } + } +} diff --git a/src/runtime/Mixins/dlr.py b/src/runtime/Mixins/dlr.py new file mode 100644 index 000000000..cd44035e4 --- /dev/null +++ b/src/runtime/Mixins/dlr.py @@ -0,0 +1,18 @@ +""" +Implements helpers for Dynamic Language Runtime (DLR) types. +""" + +class DynamicMetaObjectProviderMixin: + def __dir__(self): + names = set(super().__dir__()) + + get_dynamic_member_names = getattr(self, "GetDynamicMemberNames", None) + if callable(get_dynamic_member_names): + try: + for name in get_dynamic_member_names(): + if isinstance(name, str): + names.add(name) + except Exception: + pass + + return list(sorted(names)) diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs index fd04d4a3e..264835fff 100644 --- a/src/runtime/PythonEngine.cs +++ b/src/runtime/PythonEngine.cs @@ -299,7 +299,7 @@ static void LoadSubmodule(BorrowedReference targetModuleDict, string fullName, s static void LoadMixins(BorrowedReference targetModuleDict) { - foreach (string nested in new[] { "collections" }) + foreach (string nested in new[] { "collections", "dlr" }) { LoadSubmodule(targetModuleDict, fullName: "clr._extras." + nested, diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index dbff1fbd4..30a1a9563 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Dynamic; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Diagnostics; + using Python.Runtime.Native; using Python.Runtime.StateSerialization; @@ -37,10 +39,164 @@ internal class TypeManager "tp_clear", }; + static readonly DynamicObjectMemberAccessor dynamicMemberAccessor = new(); + + static bool HasClrMember(object instance, string memberName) => + instance.GetType().GetMember(memberName, BindingFlags.Public | BindingFlags.Instance).Length > 0; + + static bool IsPythonSpecialAttributeName(string memberName) => + memberName.Length > 4 && memberName.StartsWith("__") && memberName.EndsWith("__"); + + static bool TryGetDynamicInstance(BorrowedReference ob, out object instance, out IDynamicMetaObjectProvider dynamicObject) + { + if (ManagedType.GetManagedObject(ob) is CLRObject co && co.inst is IDynamicMetaObjectProvider coDynamic) + { + instance = co.inst; + dynamicObject = coDynamic; + return true; + } + + if (Converter.ToManaged(ob, typeof(IDynamicMetaObjectProvider), out object? managedDynamic, false) + && managedDynamic is IDynamicMetaObjectProvider convertedDynamic) + { + instance = managedDynamic; + dynamicObject = convertedDynamic; + return true; + } + + if (Converter.ToManaged(ob, typeof(object), out object? managedInstance, false) + && managedInstance is IDynamicMetaObjectProvider boxedDynamic) + { + instance = managedInstance; + dynamicObject = boxedDynamic; + return true; + } + + instance = null!; + dynamicObject = null!; + return false; + } + + public static NewReference tp_getattro_dlr_proxy(BorrowedReference ob, BorrowedReference key) + { + var isDynamic = TryGetDynamicInstance(ob, out object instance, out IDynamicMetaObjectProvider dynamicObject); + + // The whole DLR machinery only makes sense with string keys and dynamic objects + if (!isDynamic || !Runtime.PyString_Check(key)) + { + return Runtime.PyObject_GenericGetAttr(ob, key); + } + + string memberName = Runtime.GetManagedString(key)!; + + // Forward requests to GetDynamicMemberNames to the mixin implementation + if (memberName == nameof(DynamicObjectMemberAccessor.GetDynamicMemberNames) + && !HasClrMember(instance, memberName)) + { + using var pyMemberNames = new Func>( + () => dynamicMemberAccessor.GetDynamicMemberNames(dynamicObject) + ).ToPython(); + return pyMemberNames.NewReferenceOrNull(); + } + + // Now, first try to access the Python attribute + var attr = Runtime.PyObject_GenericGetAttr(ob, key); + if (!attr.IsNull()) + return attr; + + // attr is null, so an exception must be set. If that exception is not an AttributeError, + // we return from this function immediately without clearing. All later returns until the + // very end will lead to the AttributeError getting raised. + if (Runtime.PyErr_ExceptionMatches(Exceptions.AttributeError) == 0) + { + return default; + } + + if (HasClrMember(instance, memberName) || IsPythonSpecialAttributeName(memberName)) + { + return default; + } + + bool resolved = false; + object? value = null; + try + { + resolved = dynamicMemberAccessor.TryGetMember(dynamicObject, memberName, out value); + } + catch + { + return default; + } + + if (!resolved) + { + return default; + } + + Runtime.PyErr_Clear(); + + using var pyValue = value.ToPython(); + return pyValue.NewReferenceOrNull(); + } + + public static int tp_setattro_dlr_proxy(BorrowedReference ob, BorrowedReference key, BorrowedReference val) + { + var isDynamic = TryGetDynamicInstance(ob, out object instance, out IDynamicMetaObjectProvider dynamicObject); + + // The whole DLR machinery only makes sense with string keys and dynamic objects + if (!isDynamic || !Runtime.PyString_Check(key)) + { + return Runtime.PyObject_GenericSetAttr(ob, key, val); + } + + string memberName = Runtime.GetManagedString(key)!; + + // For Python-derived types (IPythonDerivedType), the Python descriptor protocol + // (e.g. @property setters) takes priority over DLR member storage. + if (instance is IPythonDerivedType) + { + int pyResult = Runtime.PyObject_GenericSetAttr(ob, key, val); + if (pyResult == 0) + return 0; + + if (Runtime.PyErr_ExceptionMatches(Exceptions.AttributeError) == 0) + return pyResult; + + Runtime.PyErr_Clear(); + // Fall through to DLR fallback below + } + + if (!HasClrMember(instance, memberName) && !IsPythonSpecialAttributeName(memberName)) + { + // Try DLR member storage first + bool handled = false; + + if (val == null) + { + handled = dynamicMemberAccessor.TryDeleteMember(dynamicObject, memberName); + } + else + { + object? managedValue = null; + if (val != Runtime.PyNone && !Converter.ToManaged(val, typeof(object), out managedValue, true)) + return -1; + + handled = dynamicMemberAccessor.TrySetMember(dynamicObject, memberName, managedValue); + } + + if (handled) + return 0; + } + + // Fall back to Python attribute setting + return Runtime.PyObject_GenericSetAttr(ob, key, val); + } + internal static void Initialize() { Debug.Assert(cache.Count == 0, "Cache should be empty", "Some errors may occurred on last shutdown"); + dynamicMemberAccessor.Clear(); using (var plainType = SlotHelper.CreateObjectType()) { subtype_traverse = Util.ReadIntPtr(plainType.Borrow(), TypeOffset.tp_traverse); @@ -64,6 +220,8 @@ internal static void RemoveTypes() } } + dynamicMemberAccessor.Clear(); + foreach (var type in cache.Values) { type.Dispose(); @@ -313,6 +471,13 @@ internal static void InitializeClass(PyType type, ClassBase impl, Type clrType) throw PythonException.ThrowLastAsClrException(); } + if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(clrType)) + { + InitializeSlot(type, TypeOffset.tp_getattro, new Interop.BB_N(tp_getattro_dlr_proxy), slotsHolder); + InitializeSlot(type, TypeOffset.tp_setattro, new Interop.BBB_I32(tp_setattro_dlr_proxy), slotsHolder); + Runtime.PyType_Modified(type.Reference); + } + var dict = Util.ReadRef(type, TypeOffset.tp_dict); string mn = clrType.Namespace ?? ""; using (var mod = Runtime.PyString_FromString(mn)) diff --git a/src/runtime/Types/ClassDerived.cs b/src/runtime/Types/ClassDerived.cs index 592eefd55..69eba2cc2 100644 --- a/src/runtime/Types/ClassDerived.cs +++ b/src/runtime/Types/ClassDerived.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Dynamic; using System.Diagnostics; using System.Linq; using System.Reflection; @@ -232,6 +233,13 @@ internal static Type CreateDerivedType(string name, continue; } + // Avoid re-entrant DLR binder recursion when Python derives from + // DynamicObject-based types (including overrides in intermediate bases). + if (IsDynamicObjectHookMethod(method)) + { + continue; + } + // skip if this property has already been overridden if ((method.Name.StartsWith("get_") || method.Name.StartsWith("set_")) && pyProperties.Contains(method.Name.Substring(4))) @@ -300,6 +308,35 @@ internal static Type CreateDerivedType(string name, return type; } + static bool IsDynamicObjectHookMethod(MethodInfo method) + { + MethodInfo origin = method.GetBaseDefinition(); + Type? originType = origin.DeclaringType; + if (originType == typeof(DynamicObject)) + { + return origin.Name switch + { + nameof(DynamicObject.TryGetMember) + or nameof(DynamicObject.TrySetMember) + or nameof(DynamicObject.TryDeleteMember) + or nameof(DynamicObject.TryInvokeMember) + or nameof(DynamicObject.TryConvert) + or nameof(DynamicObject.TryGetIndex) + or nameof(DynamicObject.TrySetIndex) + or nameof(DynamicObject.GetDynamicMemberNames) + or nameof(IDynamicMetaObjectProvider.GetMetaObject) => true, + _ => false, + }; + } + + if (originType == typeof(IDynamicMetaObjectProvider)) + { + return origin.Name == nameof(IDynamicMetaObjectProvider.GetMetaObject); + } + + return false; + } + /// /// Add a constructor override that calls the python ctor after calling the base type constructor. /// diff --git a/src/runtime/Types/DynamicObjectMemberAccessor.cs b/src/runtime/Types/DynamicObjectMemberAccessor.cs new file mode 100644 index 000000000..9e1018a15 --- /dev/null +++ b/src/runtime/Types/DynamicObjectMemberAccessor.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using Microsoft.CSharp.RuntimeBinder; + +namespace Python.Runtime; + +class DynamicObjectMemberAccessor +{ + const int MaxCacheEntries = 1000; + + readonly ConcurrentLruCache> getters = new(MaxCacheEntries); + readonly ConcurrentLruCache> setters = new(MaxCacheEntries); + readonly ConcurrentLruCache> deleters = new(MaxCacheEntries); + + static readonly CSharpArgumentInfo[] getArgumentInfo = + { + CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), + }; + + static readonly CSharpArgumentInfo[] setArgumentInfo = + { + CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), + CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), + }; + + public bool TryGetMember(IDynamicMetaObjectProvider obj, string memberName, out object? value) + { + if (obj is null) + throw new ArgumentNullException(nameof(obj)); + if (memberName is null) + throw new ArgumentNullException(nameof(memberName)); + + var getter = getters.GetOrAdd(new MemberKey(obj.GetType(), memberName), static key => + { + if (typeof(DynamicObject).IsAssignableFrom(key.Type)) + { + var getBinder = new GetMemberNameBinder(key.MemberName); + return obj => + { + if (((DynamicObject)obj).TryGetMember(getBinder, out object? result)) + { + return result; + } + + throw new RuntimeBinderException($"Could not get member '{key.MemberName}'"); + }; + } + + var binder = Binder.GetMember(CSharpBinderFlags.None, key.MemberName, key.Type, getArgumentInfo); + var callSite = CallSite>.Create(binder); + return obj => callSite.Target(callSite, (IDynamicMetaObjectProvider)obj); + }); + + try + { + value = getter(obj); + return true; + } + catch (RuntimeBinderException) + { + value = null; + return false; + } + } + + public bool TrySetMember(IDynamicMetaObjectProvider obj, string memberName, object? value) + { + if (obj is null) + throw new ArgumentNullException(nameof(obj)); + if (memberName is null) + throw new ArgumentNullException(nameof(memberName)); + + var setter = setters.GetOrAdd(new MemberKey(obj.GetType(), memberName), static key => + { + if (typeof(DynamicObject).IsAssignableFrom(key.Type)) + { + var setBinder = new SetMemberNameBinder(key.MemberName); + return (obj, value) => + { + if (!((DynamicObject)obj).TrySetMember(setBinder, value)) + { + throw new RuntimeBinderException($"Could not set member '{key.MemberName}'"); + } + }; + } + + var binder = Binder.SetMember(CSharpBinderFlags.None, key.MemberName, key.Type, setArgumentInfo); + var callSite = CallSite>.Create(binder); + return (obj, value) => callSite.Target(callSite, (IDynamicMetaObjectProvider)obj, value); + }); + + try + { + setter(obj, value); + return true; + } + catch (RuntimeBinderException) + { + return false; + } + } + + public bool TryDeleteMember(IDynamicMetaObjectProvider obj, string memberName) + { + if (obj is null) + throw new ArgumentNullException(nameof(obj)); + if (memberName is null) + throw new ArgumentNullException(nameof(memberName)); + + var deleter = deleters.GetOrAdd(new MemberKey(obj.GetType(), memberName), static key => + { + if (typeof(DynamicObject).IsAssignableFrom(key.Type)) + { + var binder = new DeleteMemberNameBinder(key.MemberName); + return obj => ((DynamicObject)obj).TryDeleteMember(binder); + } + + if (typeof(ExpandoObject).IsAssignableFrom(key.Type)) + { + return obj => ((IDictionary)(ExpandoObject)obj).Remove(key.MemberName); + } + + return _ => false; + }); + + try + { + return deleter(obj); + } + catch (RuntimeBinderException) + { + return false; + } + } + + public IReadOnlyCollection GetDynamicMemberNames(IDynamicMetaObjectProvider obj) + { + if (obj is null) + throw new ArgumentNullException(nameof(obj)); + + if (obj is ExpandoObject expandoObject) + { + return ((IDictionary)expandoObject).Keys.ToArray(); + } + + if (obj is DynamicObject dynamicObject) + { + return dynamicObject.GetDynamicMemberNames().ToArray(); + } + + var metaObject = obj.GetMetaObject(Expression.Constant(obj)); + return metaObject.GetDynamicMemberNames().ToArray(); + } + + readonly record struct MemberKey(Type Type, string MemberName); + + sealed class DeleteMemberNameBinder : DeleteMemberBinder + { + public DeleteMemberNameBinder(string name) + : base(name, ignoreCase: false) + { + } + + public override DynamicMetaObject FallbackDeleteMember(DynamicMetaObject target, DynamicMetaObject? errorSuggestion) + => errorSuggestion ?? throw new RuntimeBinderException($"Could not delete member '{Name}'"); + } + + sealed class GetMemberNameBinder : GetMemberBinder + { + public GetMemberNameBinder(string name) + : base(name, ignoreCase: false) + { + } + + public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject? errorSuggestion) + => errorSuggestion ?? throw new RuntimeBinderException($"Could not get member '{Name}'"); + } + + sealed class SetMemberNameBinder : SetMemberBinder + { + public SetMemberNameBinder(string name) + : base(name, ignoreCase: false) + { + } + + public override DynamicMetaObject FallbackSetMember( + DynamicMetaObject target, + DynamicMetaObject value, + DynamicMetaObject? errorSuggestion) + => errorSuggestion ?? throw new RuntimeBinderException($"Could not set member '{Name}'"); + } + + public void Clear() + { + getters.Clear(); + setters.Clear(); + deleters.Clear(); + } +} diff --git a/src/runtime/Util/ConcurrentLruCache.cs b/src/runtime/Util/ConcurrentLruCache.cs new file mode 100644 index 000000000..42acc15d1 --- /dev/null +++ b/src/runtime/Util/ConcurrentLruCache.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Python.Runtime; + +internal sealed class ConcurrentLruCache where TKey : notnull +{ + readonly ConcurrentDictionary> map = new(); + readonly LinkedList lru = new(); + readonly object gate = new(); + + sealed record CacheItem(TKey Key, TValue Value); + + public ConcurrentLruCache(int capacity) + { + if (capacity <= 0) + throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero."); + + Capacity = capacity; + } + + public int Capacity { get; private set; } + + public int Count => map.Count; + + public TValue GetOrAdd(TKey key, Func valueFactory) + { + if (valueFactory is null) + throw new ArgumentNullException(nameof(valueFactory)); + + if (TryGetValue(key, out var existing)) + return existing; + + var created = valueFactory(key); + + lock (gate) + { + if (map.TryGetValue(key, out var alreadyAdded)) + { + MoveToFront(alreadyAdded); + return alreadyAdded.Value.Value; + } + + var item = new CacheItem(key, created); + var node = new LinkedListNode(item); + lru.AddFirst(node); + map[key] = node; + EvictOverflow(); + return created; + } + } + + public bool TryGetValue(TKey key, out TValue value) + { + if (map.TryGetValue(key, out var node)) + { + lock (gate) + { + if (map.TryGetValue(key, out node)) + { + MoveToFront(node); + value = node.Value.Value; + return true; + } + } + } + + value = default!; + return false; + } + + public void Clear() + { + lock (gate) + { + lru.Clear(); + map.Clear(); + } + } + + void MoveToFront(LinkedListNode node) + { + if (ReferenceEquals(lru.First, node)) + return; + + lru.Remove(node); + lru.AddFirst(node); + } + + void EvictOverflow() + { + while (map.Count > Capacity) + { + var last = lru.Last; + if (last is null) + return; + + lru.RemoveLast(); + map.TryRemove(last.Value.Key, out _); + } + } +} diff --git a/src/testing/dlrtest.cs b/src/testing/dlrtest.cs new file mode 100644 index 000000000..5805e8871 --- /dev/null +++ b/src/testing/dlrtest.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; + +namespace Python.Test; + +public class DynamicMappingObject : DynamicObject +{ + Dictionary storage; + + Dictionary Storage => storage ??= []; + + // Native members for testing that regular CLR access is unaffected. + public string Label = "default"; + public int Multiplier { get; set; } = 1; + public int Multiply(int value) => value * Multiplier; + + // Test helper: bypass normal member binding and write directly to dynamic storage. + public void SetDynamicValue(string name, object value) => Storage[name] = value; + + // Test helper: retrieve the actual value stored in C# (for verification that None was stored as null) + public object GetDynamicValue(string name) => Storage.TryGetValue(name, out var value) ? value : null; + + + + public override bool TryGetMember(GetMemberBinder binder, out object result) + => Storage.TryGetValue(binder.Name, out result); + + public override bool TrySetMember(SetMemberBinder binder, object value) + { + Storage[binder.Name] = value; + return true; + } + + public override bool TryDeleteMember(DeleteMemberBinder binder) + => binder is not null && Storage.Remove(binder.Name); + + public override IEnumerable GetDynamicMemberNames() => Storage.Keys; +} diff --git a/tests/test_dynamic.py b/tests/test_dynamic.py new file mode 100644 index 000000000..b18ac1fdd --- /dev/null +++ b/tests/test_dynamic.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- + +import pytest +from System.Collections.Generic import Dictionary +from System.Dynamic import ExpandoObject + +from Python.Test import DynamicMappingObject + + +def _mro_names(obj): + return [f"{t.__module__}.{t.__name__}" for t in type(obj).__mro__] + + +@pytest.mark.parametrize( + "obj, expected", + [ + (DynamicMappingObject(), True), + (ExpandoObject(), True), + (Dictionary[str, int](), False), + ], +) +def test_dlr_mixin_presence(obj, expected): + has_mixin = "clr._extras.dlr.DynamicMetaObjectProviderMixin" in _mro_names(obj) + assert has_mixin is expected + + +@pytest.mark.parametrize("obj", [DynamicMappingObject(), ExpandoObject()]) +def test_dynamic_binder(obj): + assert "answer" not in dir(obj) + assert "wrong_answer" not in dir(obj) + + setattr(obj, "answer", 42) + obj.wrong_answer = 54 + + assert obj.answer == 42 + assert obj.wrong_answer == 54 + + assert "answer" in dir(obj) + assert "wrong_answer" in dir(obj) + + +def test_native_members_are_accessible_and_keep_priority(): + obj = DynamicMappingObject() + setattr(obj, "answer", 42) + obj.SetDynamicValue("Multiplier", 999) + + # Native field + assert obj.Label == "default" + obj.Label = "changed" + assert obj.Label == "changed" + + # Native property takes precedence over dynamic fallback + assert obj.Multiplier == 1 + obj.Multiplier = 7 + assert obj.Multiplier == 7 + + # Native method + obj.Multiplier = 3 + assert obj.Multiply(5) == 15 + +def test_dynamic_and_native_members_coexist(): + obj = DynamicMappingObject() + setattr(obj, "answer", 42) + obj.Multiplier = 2 + assert obj.answer == 42 + assert obj.Multiplier == 2 + assert obj.Multiply(10) == 20 + + +@pytest.mark.parametrize("obj", [DynamicMappingObject(), ExpandoObject()]) +def test_set_and_get_dynamic_property(obj): + """Test that setting and getting dynamic properties goes through DLR binder.""" + # Get initial value (should be None for non-existent property) + assert not hasattr(obj, "MyProp") + + # Set a dynamic property to a value + obj.MyProp = 42 + assert obj.MyProp == 42 + + # Set to None and verify it stays None through DLR + obj.MyProp = None + assert obj.MyProp is None + + # Set to another value and verify + obj.MyProp = "hello" + assert obj.MyProp == "hello" + + +def test_update_dynamic_value(): + """Dynamic-only members should use DLR get/set/modify/delete end-to-end.""" + obj = DynamicMappingObject() + assert not hasattr(obj, "DynamicOnly") + + # Initial set should create a dynamic member + obj.DynamicOnly = "initial" + assert obj.DynamicOnly == "initial" + + # Modify flows through TrySetMember + obj.DynamicOnly = "updated" + assert obj.DynamicOnly == "updated" + + # Setting None keeps a present member with None value + obj.DynamicOnly = None + assert obj.DynamicOnly is None + + # Delete flows through TryDeleteMember + del obj.DynamicOnly + assert "DynamicOnly" not in dir(obj) + assert not hasattr(obj, "DynamicOnly") + + +def test_dynamic_set_none_updates_managed_store_after_get(): + """Regression: get->set(None)->get must route through DLR and update managed storage.""" + obj = DynamicMappingObject() + obj.SetDynamicValue("MyProp", "initial") + + x = obj.MyProp + assert x == "initial" + + obj.MyProp = None + + y = obj.MyProp + assert y is None + assert obj.GetDynamicValue("MyProp") is None + + +@pytest.mark.parametrize("obj", [DynamicMappingObject(), ExpandoObject()]) +def test_dynamic_member_lifecycle(obj): + """Dynamic members should support set/modify/get/delete via the DLR binder.""" + name = "LifecycleMember" + + assert not hasattr(obj, name) + + setattr(obj, name, 1) + assert getattr(obj, name) == 1 + + setattr(obj, name, 2) + assert getattr(obj, name) == 2 + + delattr(obj, name) + assert not hasattr(obj, name) + + +def test_derive_from_dynamic_class(): + class MyMappingObject(DynamicMappingObject): + __namespace__ = "PythonNetTest" + + def __init__(self): + self._custom = 0 + + @property + def custom_property(self): + return self._custom + + @custom_property.setter + def custom_property(self, i): + self._custom += i + + + obj = MyMappingObject() + with pytest.raises(AttributeError): + x = obj.unknown_property + + assert obj.custom_property == 0 + + obj.custom_property = 5 + assert obj.custom_property == 5 + + obj.custom_property = 5 + assert obj.custom_property == 10 + + obj.other_property = None + assert obj.other_property is None \ No newline at end of file From 69ba86197ba00acd5dcbfc18a81da695fc8a0dbd Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 5 May 2026 17:03:21 +0200 Subject: [PATCH 187/194] Use explicit IsNull on BorrowedReference --- src/runtime/TypeManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index 30a1a9563..496b5d357 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -171,7 +171,7 @@ public static int tp_setattro_dlr_proxy(BorrowedReference ob, BorrowedReference // Try DLR member storage first bool handled = false; - if (val == null) + if (val.IsNull) { handled = dynamicMemberAccessor.TryDeleteMember(dynamicObject, memberName); } From 2408c4317723777aa37eba87bebf8e45265dca21 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Thu, 7 May 2026 18:59:18 +0200 Subject: [PATCH 188/194] Catch errors in setting/deleting properties - Catch exceptions in TrySet/DeleteMember - Convert the exceptions into Python exceptions - Add tests for the remaining cases - Add a note on why the field has to be lazily initialized (general issue with derived classes) --- src/runtime/TypeManager.cs | 31 ++++++++++++------ src/testing/dlrtest.cs | 65 ++++++++++++++++++++++++++++++++------ tests/test_dynamic.py | 38 +++++++++++++++++++++- 3 files changed, 115 insertions(+), 19 deletions(-) diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index 496b5d357..30a99690a 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -169,19 +169,32 @@ public static int tp_setattro_dlr_proxy(BorrowedReference ob, BorrowedReference if (!HasClrMember(instance, memberName) && !IsPythonSpecialAttributeName(memberName)) { // Try DLR member storage first - bool handled = false; + bool handled; - if (val.IsNull) + try { - handled = dynamicMemberAccessor.TryDeleteMember(dynamicObject, memberName); + if (val.IsNull) + { + handled = dynamicMemberAccessor.TryDeleteMember(dynamicObject, memberName); + } + else + { + object? managedValue = null; + if (val != Runtime.PyNone && !Converter.ToManaged(val, typeof(object), out managedValue, true)) + return -1; + + handled = dynamicMemberAccessor.TrySetMember(dynamicObject, memberName, managedValue); + if (!handled) + { + Exceptions.SetError(Exceptions.AttributeError, $"'{instance.GetType().Name}' object has no attribute '{memberName}'"); + return -1; + } + } } - else + catch (Exception e) { - object? managedValue = null; - if (val != Runtime.PyNone && !Converter.ToManaged(val, typeof(object), out managedValue, true)) - return -1; - - handled = dynamicMemberAccessor.TrySetMember(dynamicObject, memberName, managedValue); + Exceptions.SetError(e); + return -1; } if (handled) diff --git a/src/testing/dlrtest.cs b/src/testing/dlrtest.cs index 5805e8871..b58f5fc82 100644 --- a/src/testing/dlrtest.cs +++ b/src/testing/dlrtest.cs @@ -4,12 +4,40 @@ namespace Python.Test; -public class DynamicMappingObject : DynamicObject +/// +/// Base class for dynamic test helpers. Uses lazy storage initialization so that +/// Python-derived subclasses can safely call DynamicObject member hooks before +/// managed field initializers have run. +/// +public class DynamicStorageObject : DynamicObject { Dictionary storage; - Dictionary Storage => storage ??= []; + // Python-defined subclasses may reach this type without running managed field + // initializers (see ClassDerivedObject.NewObjectToPython). Via the lazy init + // we can ensure that the access is still safe, even when the constructor has + // not run. + protected Dictionary Storage => storage ??= []; + public void AddDynamicMember(string name, object value) => Storage[name] = value; + + public override bool TryGetMember(GetMemberBinder binder, out object result) + => Storage.TryGetValue(binder.Name, out result); + + public override bool TrySetMember(SetMemberBinder binder, object value) + { + Storage[binder.Name] = value; + return true; + } + + public override bool TryDeleteMember(DeleteMemberBinder binder) + => Storage.Remove(binder.Name); + + public override IEnumerable GetDynamicMemberNames() => Storage.Keys; +} + +public class DynamicMappingObject : DynamicStorageObject +{ // Native members for testing that regular CLR access is unaffected. public string Label = "default"; public int Multiplier { get; set; } = 1; @@ -20,20 +48,39 @@ public class DynamicMappingObject : DynamicObject // Test helper: retrieve the actual value stored in C# (for verification that None was stored as null) public object GetDynamicValue(string name) => Storage.TryGetValue(name, out var value) ? value : null; +} - - - public override bool TryGetMember(GetMemberBinder binder, out object result) - => Storage.TryGetValue(binder.Name, out result); - +public class RejectingSetDynamicObject : DynamicStorageObject +{ public override bool TrySetMember(SetMemberBinder binder, object value) { + if (!Storage.ContainsKey(binder.Name)) + return false; + Storage[binder.Name] = value; return true; } +} + +public class ThrowingSetDynamicObject : DynamicStorageObject +{ + public override bool TrySetMember(SetMemberBinder binder, object value) + => throw new InvalidOperationException($"TrySetMember failed for '{binder.Name}'"); +} +public class RejectingDeleteDynamicObject : DynamicStorageObject +{ public override bool TryDeleteMember(DeleteMemberBinder binder) - => binder is not null && Storage.Remove(binder.Name); + { + if (!Storage.ContainsKey(binder.Name)) + return false; - public override IEnumerable GetDynamicMemberNames() => Storage.Keys; + return Storage.Remove(binder.Name); + } +} + +public class ThrowingDeleteDynamicObject : DynamicStorageObject +{ + public override bool TryDeleteMember(DeleteMemberBinder binder) + => throw new InvalidOperationException($"TryDeleteMember failed for '{binder.Name}'"); } diff --git a/tests/test_dynamic.py b/tests/test_dynamic.py index b18ac1fdd..b8caa24b3 100644 --- a/tests/test_dynamic.py +++ b/tests/test_dynamic.py @@ -5,6 +5,10 @@ from System.Dynamic import ExpandoObject from Python.Test import DynamicMappingObject +from Python.Test import RejectingDeleteDynamicObject +from Python.Test import RejectingSetDynamicObject +from Python.Test import ThrowingDeleteDynamicObject +from Python.Test import ThrowingSetDynamicObject def _mro_names(obj): @@ -170,4 +174,36 @@ def custom_property(self, i): assert obj.custom_property == 10 obj.other_property = None - assert obj.other_property is None \ No newline at end of file + assert obj.other_property is None + + +def test_trysetmember_false_raises_attributeerror_instead_of_silent_python_setattr(): + obj = RejectingSetDynamicObject() + + with pytest.raises(AttributeError): + obj.typoed_name = 42 + + assert not hasattr(obj, "typoed_name") + + +def test_trysetmember_exception_is_raised_in_python(): + obj = ThrowingSetDynamicObject() + + with pytest.raises(Exception, match="TrySetMember failed for 'bad_name'"): + obj.bad_name = 42 + + +def test_trydeletemember_false_raises_attributeerror(): + obj = RejectingDeleteDynamicObject() + obj.AddDynamicMember("existing_name", 42) + + with pytest.raises(AttributeError): + del obj.missing_name + + +def test_trydeletemember_exception_is_raised_in_python(): + obj = ThrowingDeleteDynamicObject() + obj.bad_name = 42 + + with pytest.raises(Exception, match="TryDeleteMember failed for 'bad_name'"): + del obj.bad_name \ No newline at end of file From 8b12b56d13e398feea20a7ed68ee2872a17b730f Mon Sep 17 00:00:00 2001 From: greateggsgreg <36009512+greateggsgreg@users.noreply.github.com> Date: Mon, 11 May 2026 07:24:32 -0400 Subject: [PATCH 189/194] Propagate exceptions from TryGetMember in tp_getattro_dlr_proxy (#2718) The dynamic getter swallowed any exception from TryGetMember and returned default to Python with the prior AttributeError still set, so user code observed a misleading AttributeError instead of the real failure. Set a Python exception in the catch arm. We use RuntimeError with the message string rather than Converter.ToPython(e) because wrapping the CLR exception object can trigger type initialisation that re-enters this same slot on the live dynamic object, producing infinite recursion. Mirrors the symmetry already present in the setter (#2706 review, @lostmsu) and adds a regression test alongside the existing ThrowingSetDynamicObject coverage. --- src/runtime/TypeManager.cs | 7 ++++++- src/testing/dlrtest.cs | 6 ++++++ tests/test_dynamic.py | 9 +++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index 30a99690a..3b3c6db1a 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -123,8 +123,13 @@ public static NewReference tp_getattro_dlr_proxy(BorrowedReference ob, BorrowedR { resolved = dynamicMemberAccessor.TryGetMember(dynamicObject, memberName, out value); } - catch + catch (Exception e) { + // Avoid wrapping the CLR exception via Converter.ToPython here: that would trigger + // CLR type initialisation which can re-enter this slot on the same live object, + // causing infinite recursion. A plain RuntimeError with the message is safe. + Runtime.PyErr_Clear(); + Exceptions.SetError(Exceptions.RuntimeError, e.Message); return default; } diff --git a/src/testing/dlrtest.cs b/src/testing/dlrtest.cs index b58f5fc82..783a3a133 100644 --- a/src/testing/dlrtest.cs +++ b/src/testing/dlrtest.cs @@ -62,6 +62,12 @@ public override bool TrySetMember(SetMemberBinder binder, object value) } } +public class ThrowingGetDynamicObject : DynamicStorageObject +{ + public override bool TryGetMember(GetMemberBinder binder, out object result) + => throw new InvalidOperationException($"TryGetMember failed for '{binder.Name}'"); +} + public class ThrowingSetDynamicObject : DynamicStorageObject { public override bool TrySetMember(SetMemberBinder binder, object value) diff --git a/tests/test_dynamic.py b/tests/test_dynamic.py index b8caa24b3..f093ee19a 100644 --- a/tests/test_dynamic.py +++ b/tests/test_dynamic.py @@ -8,6 +8,7 @@ from Python.Test import RejectingDeleteDynamicObject from Python.Test import RejectingSetDynamicObject from Python.Test import ThrowingDeleteDynamicObject +from Python.Test import ThrowingGetDynamicObject from Python.Test import ThrowingSetDynamicObject @@ -186,6 +187,14 @@ def test_trysetmember_false_raises_attributeerror_instead_of_silent_python_setat assert not hasattr(obj, "typoed_name") +def test_trygetmember_exception_is_raised_in_python(): + obj = ThrowingGetDynamicObject() + obj.AddDynamicMember("any_key", 1) + + with pytest.raises(Exception, match="TryGetMember failed for 'any_key'"): + _ = obj.any_key + + def test_trysetmember_exception_is_raised_in_python(): obj = ThrowingSetDynamicObject() From 3a0899084dfc35b726723e015c00763d3d6abf37 Mon Sep 17 00:00:00 2001 From: greateggsgreg Date: Mon, 11 May 2026 22:50:05 -0400 Subject: [PATCH 190/194] Cache HasClrMember and align DLR setter exception path - Cache HasClrMember reflection per (Type, name) so tp_getattro_dlr_proxy / tp_setattro_dlr_proxy avoid repeated GetMember() calls on every attribute access of DLR-aware objects. - Mirror tp_setattro_dlr_proxy's catch arm to the getter's safer SetError(RuntimeError, e.Message) shape instead of SetError(Exception), keeping both slots re-entry-safe on live dynamic objects. Related to #2706. --- src/runtime/TypeManager.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs index 3b3c6db1a..c02d94a1f 100644 --- a/src/runtime/TypeManager.cs +++ b/src/runtime/TypeManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Dynamic; using System.Linq; @@ -41,8 +42,14 @@ internal class TypeManager static readonly DynamicObjectMemberAccessor dynamicMemberAccessor = new(); + // tp_getattro_dlr_proxy / tp_setattro_dlr_proxy hit HasClrMember on every + // attribute access; cache the reflection result per (Type, name). + static readonly ConcurrentDictionary<(Type, string), bool> _hasClrMemberCache = new(); + static bool HasClrMember(object instance, string memberName) => - instance.GetType().GetMember(memberName, BindingFlags.Public | BindingFlags.Instance).Length > 0; + _hasClrMemberCache.GetOrAdd( + (instance.GetType(), memberName), + k => k.Item1.GetMember(k.Item2, BindingFlags.Public | BindingFlags.Instance).Length > 0); static bool IsPythonSpecialAttributeName(string memberName) => memberName.Length > 4 && memberName.StartsWith("__") && memberName.EndsWith("__"); @@ -198,7 +205,9 @@ public static int tp_setattro_dlr_proxy(BorrowedReference ob, BorrowedReference } catch (Exception e) { - Exceptions.SetError(e); + // Same reasoning as the getter: avoid Converter.ToPython(e) to keep this + // slot re-entry-safe on live dynamic objects. + Exceptions.SetError(Exceptions.RuntimeError, e.Message); return -1; } From c25a5bd9c6c46e5d29ea3c4d9f9b9a15c00290c1 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 16 May 2026 20:40:52 +0200 Subject: [PATCH 191/194] Second release candidate for 3.1.0 (dynamic) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index a416f3693..eb734bb99 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.0-rc.0 +3.1.0-rc.1 From 4a834465626a1e87f8ccc8f2c623068e51d8381c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 16:56:07 +0200 Subject: [PATCH 192/194] Merge pull request #2726 from pythonnet/dependabot/uv/uv-d665ee01e3 Bump idna from 3.13 to 3.15 in the uv group across 1 directory --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 3b55148b7..f007f9151 100644 --- a/uv.lock +++ b/uv.lock @@ -347,11 +347,11 @@ wheels = [ [[package]] name = "idna" -version = "3.13" +version = "3.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, ] [[package]] From 3d76836e80284aae8abcac92e1676d7d83178ec1 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 23 May 2026 22:22:52 +0200 Subject: [PATCH 193/194] Update changelog and bump version --- CHANGELOG.md | 735 ++++++++++++++++++++++++++------------------------- version.txt | 2 +- 2 files changed, 370 insertions(+), 367 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54a08fe52..546413849 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,218 +5,231 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. -## Unreleased +## 3.1.0 - 2026-05-23 ### Added -- Support `del obj[...]` for types derived from `IList` and `IDictionary` -- Support for .NET Framework 4.6.1 (#2701) -- Add context manager protocol for .NET IDisposable types, allowing use of `with` statements - for IDisposable objects (#2568) +- Support `del obj[...]` for types derived from `IList` and `IDictionary` (#2533) +- Support for .NET Framework 4.6.1 (#2701) +- Context manager protocol for .NET `IDisposable` types, allowing use of `with` statements + for `IDisposable` objects (#2568) +- Support for Python 3.14 (#2611) +- Implement support for DLR get/set (#2706) ### Changed + +- Derive Python (virtual) environment information (#2652) + ### Fixed -- Fixed crash when trying to `del clrObj[...]` for non-arrays -- ci: properly exclude job (#2542) +- Fixed crash when trying to `del clrObj[...]` for non-arrays +- ci: properly exclude job (#2542) +- Properly detect availability of `BinaryFormatter` (#2639) +- Support for .NET 10 SDK (#2684) +- Fix wheel tags (#2716) +- Name missing from `__all__` on re-import (#2717) + +### Removed + +- Support for Python 3.7 to 3.9 (#2639) ## [3.0.5](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.5) - 2024-12-13 ### Added -- Support for Python 3.13 (#2454) - +- Support for Python 3.13 (#2454) ## [3.0.4](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.4) - 2024-09-19 ### Added -- Added `ToPythonAs()` extension method to allow for explicit conversion +- Added `ToPythonAs()` extension method to allow for explicit conversion using a specific type. ([#2311][i2311]) -- Added `IComparable` and `IEquatable` implementations to `PyInt`, `PyFloat`, +- Added `IComparable` and `IEquatable` implementations to `PyInt`, `PyFloat`, and `PyString` to compare with primitive .NET types like `long`. ### Changed -- Added a `FormatterFactory` member in RuntimeData to create formatters with +- Added a `FormatterFactory` member in RuntimeData to create formatters with parameters. For compatibility, the `FormatterType` member is still present and has precedence when defining both `FormatterFactory` and `FormatterType` -- Added a post-serialization and a pre-deserialization step callbacks to +- Added a post-serialization and a pre-deserialization step callbacks to extend (de)serialization process -- Added an API to stash serialized data on Python capsules +- Added an API to stash serialized data on Python capsules ### Fixed -- Fixed RecursionError for reverse operators on C# operable types from python. See #2240 -- Fixed crash when .NET event has no `AddMethod` -- Fixed probing for assemblies in `sys.path` failing when a path in `sys.path` +- Fixed RecursionError for reverse operators on C# operable types from python. See #2240 +- Fixed crash when .NET event has no `AddMethod` +- Fixed probing for assemblies in `sys.path` failing when a path in `sys.path` has invalid characters. See #2376 -- Fixed possible access violation exception on shutdown. See ([#1977][i1977]) +- Fixed possible access violation exception on shutdown. See ([#1977][i1977]) ## [3.0.3](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.3) - 2023-10-11 ### Added -- Support for Python 3.12 +- Support for Python 3.12 ### Changed -- Use enum name in `repr` +- Use enum name in `repr` ## [3.0.2](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.2) - 2023-08-29 ### Fixed -- Fixed error occuring when inheriting a class containing a virtual generic method -- Make a second call to `pythonnet.load` a no-op, as it was intended -- Added support for multiple inheritance when inheriting from a class and/or multiple interfaces -- Fixed error occuring when calling `GetBuffer` for anything other than `PyBUF.SIMPLE` -- Bumped `clr_loader` dependency to incorporate patches +- Fixed error occuring when inheriting a class containing a virtual generic method +- Make a second call to `pythonnet.load` a no-op, as it was intended +- Added support for multiple inheritance when inheriting from a class and/or multiple interfaces +- Fixed error occuring when calling `GetBuffer` for anything other than `PyBUF.SIMPLE` +- Bumped `clr_loader` dependency to incorporate patches ## [3.0.1](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.1) - 2022-11-03 ### Added -- Support for Python 3.11 +- Support for Python 3.11 ### Changed -- Allow decoders to override conversion of types derived from primitive types +- Allow decoders to override conversion of types derived from primitive types ### Fixed -- Fixed objects leaking when Python attached event handlers to them even if they were later removed -- Fixed `PyInt` conversion to `BigInteger` and `System.String` produced incorrect result for values between 128 and 255. -- Fixed implementing a generic interface with a Python class - +- Fixed objects leaking when Python attached event handlers to them even if they were later removed +- Fixed `PyInt` conversion to `BigInteger` and `System.String` produced incorrect result for values between 128 and 255. +- Fixed implementing a generic interface with a Python class ## [3.0.0](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.0) - 2022-09-29 ### Added -- Ability to instantiate new .NET arrays using `Array[T](dim1, dim2, ...)` syntax -- Python operator method will call C# operator method for supported binary and unary operators ([#1324][p1324]). -- Add GetPythonThreadID and Interrupt methods in PythonEngine -- Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355]) -- Ability to override .NET methods that have `out` or `ref` in Python by returning the modified parameter values in a tuple. ([#1481][i1481]) -- `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec` -- Improved exception handling: - * exceptions can now be converted with codecs - * `InnerException` and `__cause__` are propagated properly -- `__name__` and `__signature__` to reflected .NET methods -- .NET collection types now implement standard Python collection interfaces from `collections.abc`. +- Ability to instantiate new .NET arrays using `Array[T](dim1, dim2, ...)` syntax +- Python operator method will call C# operator method for supported binary and unary operators ([#1324][p1324]). +- Add GetPythonThreadID and Interrupt methods in PythonEngine +- Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355]) +- Ability to override .NET methods that have `out` or `ref` in Python by returning the modified parameter values in a tuple. ([#1481][i1481]) +- `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec` +- Improved exception handling: + +- exceptions can now be converted with codecs +- `InnerException` and `__cause__` are propagated properly + +- `__name__` and `__signature__` to reflected .NET methods +- .NET collection types now implement standard Python collection interfaces from `collections.abc`. See [Mixins/collections.py](src/runtime/Mixins/collections.py). -- you can cast objects to generic .NET interfaces without specifying generic arguments as long as there is no ambiguity. -- .NET arrays implement Python buffer protocol -- Python integer interoperability with `System.Numerics.BigInteger` -- Python.NET will correctly resolve .NET methods, that accept `PyList`, `PyInt`, +- you can cast objects to generic .NET interfaces without specifying generic arguments as long as there is no ambiguity. +- .NET arrays implement Python buffer protocol +- Python integer interoperability with `System.Numerics.BigInteger` +- Python.NET will correctly resolve .NET methods, that accept `PyList`, `PyInt`, and other `PyObject` derived types when called from Python. -- .NET classes, that have `__call__` method are callable from Python -- `PyIterable` type, that wraps any iterable object in Python -- `PythonEngine` properties for supported Python versions: `MinSupportedVersion`, `MaxSupportedVersion`, and `IsSupportedVersion` -- The runtime that is loaded on `import clr` can now be configured via environment variables - +- .NET classes, that have `__call__` method are callable from Python +- `PyIterable` type, that wraps any iterable object in Python +- `PythonEngine` properties for supported Python versions: `MinSupportedVersion`, `MaxSupportedVersion`, and `IsSupportedVersion` +- The runtime that is loaded on `import clr` can now be configured via environment variables ### Changed -- Drop support for Python 2, 3.4, 3.5, and 3.6 -- `wchar_t` size aka `Runtime.UCS` is now determined at runtime -- `clr.AddReference` may now throw errors besides `FileNotFoundException`, that provide more + +- Drop support for Python 2, 3.4, 3.5, and 3.6 +- `wchar_t` size aka `Runtime.UCS` is now determined at runtime +- `clr.AddReference` may now throw errors besides `FileNotFoundException`, that provide more details about the cause of the failure -- `clr.AddReference` no longer adds ".dll" implicitly -- `PyIter(PyObject)` constructor replaced with static `PyIter.GetIter(PyObject)` method -- Python runtime can no longer be shut down if the Python error indicator is set, as it would have unpredictable behavior -- BREAKING: Return values from .NET methods that return an interface are now automatically +- `clr.AddReference` no longer adds ".dll" implicitly +- `PyIter(PyObject)` constructor replaced with static `PyIter.GetIter(PyObject)` method +- Python runtime can no longer be shut down if the Python error indicator is set, as it would have unpredictable behavior +- BREAKING: Return values from .NET methods that return an interface are now automatically wrapped in that interface. This is a breaking change for users that rely on being able to access members that are part of the implementation class, but not the interface. Use the new `__implementation__` or `__raw_implementation__` properties to if you need to "downcast" to the implementation class. -- BREAKING: `==` and `!=` operators on `PyObject` instances now use Python comparison +- BREAKING: `==` and `!=` operators on `PyObject` instances now use Python comparison (previously was equivalent to `object.ReferenceEquals(,)`) -- BREAKING: Parameters marked with `ParameterAttributes.Out` are no longer returned in addition +- BREAKING: Parameters marked with `ParameterAttributes.Out` are no longer returned in addition to the regular method return value (unless they are passed with `ref` or `out` keyword). -- BREAKING: Drop support for the long-deprecated CLR.* prefix. -- `PyObject` now implements `IEnumerable` in addition to `IEnumerable` -- floating point values passed from Python are no longer silently truncated +- BREAKING: Drop support for the long-deprecated CLR.* prefix. +- `PyObject` now implements `IEnumerable` in addition to `IEnumerable` +- floating point values passed from Python are no longer silently truncated when .NET expects an integer [#1342][i1342] -- More specific error messages for method argument mismatch -- members of `PyObject` inherited from `System.Object and `DynamicObject` now autoacquire GIL -- BREAKING: when inheriting from .NET types in Python if you override `__init__` you +- More specific error messages for method argument mismatch +- members of `PyObject` inherited from `System.Object and`DynamicObject` now autoacquire GIL +- BREAKING: when inheriting from .NET types in Python if you override `__init__` you must explicitly call base constructor using `super().__init__(.....)`. Not doing so will lead to undefined behavior. -- BREAKING: most `PyScope` methods will never return `null`. Instead, `PyObject` `None` will be returned. -- BREAKING: `PyScope` was renamed to `PyModule` -- BREAKING: Methods with `ref` or `out` parameters and void return type return a tuple of only the `ref` and `out` parameters. -- BREAKING: to call Python from .NET `Runtime.PythonDLL` property must be set to Python DLL name +- BREAKING: most `PyScope` methods will never return `null`. Instead, `PyObject` `None` will be returned. +- BREAKING: `PyScope` was renamed to `PyModule` +- BREAKING: Methods with `ref` or `out` parameters and void return type return a tuple of only the `ref` and `out` parameters. +- BREAKING: to call Python from .NET `Runtime.PythonDLL` property must be set to Python DLL name or the DLL must be loaded in advance. This must be done before calling any other Python.NET functions. -- BREAKING: `PyObject.Length()` now raises a `PythonException` when object does not support a concept of length. -- BREAKING: disabled implicit conversion from C# enums to Python `int` and back. +- BREAKING: `PyObject.Length()` now raises a `PythonException` when object does not support a concept of length. +- BREAKING: disabled implicit conversion from C# enums to Python `int` and back. One must now either use enum members (e.g. `MyEnum.Option`), or use enum constructor (e.g. `MyEnum(42)` or `MyEnum(42, True)` when `MyEnum` does not have a member with value 42). -- BREAKING: disabled implicit conversion from Python objects implementing sequence protocol to +- BREAKING: disabled implicit conversion from Python objects implementing sequence protocol to .NET arrays when the target .NET type is `System.Object`. The conversion is still attempted when the target type is a `System.Array`. -- Sign Runtime DLL with a strong name -- Implement loading through `clr_loader` instead of the included `ClrModule`, enables +- Sign Runtime DLL with a strong name +- Implement loading through `clr_loader` instead of the included `ClrModule`, enables support for .NET Core -- BREAKING: .NET and Python exceptions are preserved when crossing Python/.NET boundary -- BREAKING: custom encoders are no longer called for instances of `System.Type` -- `PythonException.Restore` no longer clears `PythonException` instance. -- Replaced the old `__import__` hook hack with a PEP302-style Meta Path Loader -- BREAKING: Names of .NET types (e.g. `str(__class__)`) changed to better support generic types -- BREAKING: overload resolution will no longer prefer basic types. Instead, first matching overload will +- BREAKING: .NET and Python exceptions are preserved when crossing Python/.NET boundary +- BREAKING: custom encoders are no longer called for instances of `System.Type` +- `PythonException.Restore` no longer clears `PythonException` instance. +- Replaced the old `__import__` hook hack with a PEP302-style Meta Path Loader +- BREAKING: Names of .NET types (e.g. `str(__class__)`) changed to better support generic types +- BREAKING: overload resolution will no longer prefer basic types. Instead, first matching overload will be chosen. -- BREAKING: acquiring GIL using `Py.GIL` no longer forces `PythonEngine` to initialize -- BREAKING: `Exec` and `Eval` from `PythonEngine` no longer accept raw pointers. -- BREAKING: .NET collections and arrays are no longer automatically converted to +- BREAKING: acquiring GIL using `Py.GIL` no longer forces `PythonEngine` to initialize +- BREAKING: `Exec` and `Eval` from `PythonEngine` no longer accept raw pointers. +- BREAKING: .NET collections and arrays are no longer automatically converted to Python collections. Instead, they implement standard Python collection interfaces from `collections.abc`. See [Mixins/collections.py](src/runtime/Mixins/collections.py). -- BREAKING: When trying to convert Python `int` to `System.Object`, result will +- BREAKING: When trying to convert Python `int` to `System.Object`, result will be of type `PyInt` instead of `System.Int32` due to possible loss of information. Python `float` will continue to be converted to `System.Double`. -- BREAKING: Python.NET will no longer implicitly convert types like `numpy.float64`, that implement `__float__` to +- BREAKING: Python.NET will no longer implicitly convert types like `numpy.float64`, that implement `__float__` to `System.Single` and `System.Double`. An explicit conversion is required on Python or .NET side. -- BREAKING: `PyObject.GetHashCode` can fail. -- BREAKING: Python.NET will no longer implicitly convert any Python object to `System.Boolean`. -- BREAKING: `PyObject.GetAttr(name, default)` now only ignores `AttributeError` (previously ignored all exceptions). -- BREAKING: `PyObject` no longer implements `IEnumerable`. +- BREAKING: `PyObject.GetHashCode` can fail. +- BREAKING: Python.NET will no longer implicitly convert any Python object to `System.Boolean`. +- BREAKING: `PyObject.GetAttr(name, default)` now only ignores `AttributeError` (previously ignored all exceptions). +- BREAKING: `PyObject` no longer implements `IEnumerable`. Instead, `PyIterable` does that. -- BREAKING: `IPyObjectDecoder.CanDecode` `objectType` parameter type changed from `PyObject` to `PyType` +- BREAKING: `IPyObjectDecoder.CanDecode` `objectType` parameter type changed from `PyObject` to `PyType` ### Fixed -- Fix incorrect dereference of wrapper object in `tp_repr`, which may result in a program crash -- Fixed parameterless .NET constructor being silently called when a matching constructor overload is not found ([#238][i238]) -- Fix incorrect dereference in params array handling -- Fixes issue with function resolution when calling overloaded function with keyword arguments from python ([#1097][i1097]) -- Fix `object[]` parameters taking precedence when should not in overload resolution -- Fixed a bug where all .NET class instances were considered Iterable -- Fix incorrect choice of method to invoke when using keyword arguments. -- Fix non-delegate types incorrectly appearing as callable. -- Indexers can now be used with interface objects -- Fixed a bug where indexers could not be used if they were inherited -- Made it possible to use `__len__` also on `ICollection<>` interface objects -- Fixed issue when calling PythonException.Format where another exception would be raise for unnormalized exceptions -- Made it possible to call `ToString`, `GetHashCode`, and `GetType` on inteface objects -- Fixed objects returned by enumerating `PyObject` being disposed too soon -- Incorrectly using a non-generic type with type parameters now produces a helpful Python error instead of throwing NullReferenceException -- `import` may now raise errors with more detail than "No module named X" -- Exception stacktraces on `PythonException.StackTrace` are now properly formatted -- Providing an invalid type parameter to a generic type or method produces a helpful Python error -- Empty parameter names (as can be generated from F#) do not cause crashes -- Unicode strings with surrogates were truncated when converting from Python -- `Reload` mode now supports generic methods (previously Python would stop seeing them after reload) -- Temporarily fixed issue resolving method overload when method signature has `out` parameters ([#1672](i1672)) -- Decimal default parameters are now correctly taken into account +- Fix incorrect dereference of wrapper object in `tp_repr`, which may result in a program crash +- Fixed parameterless .NET constructor being silently called when a matching constructor overload is not found ([#238][i238]) +- Fix incorrect dereference in params array handling +- Fixes issue with function resolution when calling overloaded function with keyword arguments from python ([#1097][i1097]) +- Fix `object[]` parameters taking precedence when should not in overload resolution +- Fixed a bug where all .NET class instances were considered Iterable +- Fix incorrect choice of method to invoke when using keyword arguments. +- Fix non-delegate types incorrectly appearing as callable. +- Indexers can now be used with interface objects +- Fixed a bug where indexers could not be used if they were inherited +- Made it possible to use `__len__` also on `ICollection<>` interface objects +- Fixed issue when calling PythonException.Format where another exception would be raise for unnormalized exceptions +- Made it possible to call `ToString`, `GetHashCode`, and `GetType` on inteface objects +- Fixed objects returned by enumerating `PyObject` being disposed too soon +- Incorrectly using a non-generic type with type parameters now produces a helpful Python error instead of throwing NullReferenceException +- `import` may now raise errors with more detail than "No module named X" +- Exception stacktraces on `PythonException.StackTrace` are now properly formatted +- Providing an invalid type parameter to a generic type or method produces a helpful Python error +- Empty parameter names (as can be generated from F#) do not cause crashes +- Unicode strings with surrogates were truncated when converting from Python +- `Reload` mode now supports generic methods (previously Python would stop seeing them after reload) +- Temporarily fixed issue resolving method overload when method signature has `out` parameters ([#1672](i1672)) +- Decimal default parameters are now correctly taken into account ### Removed -- `ShutdownMode` has been removed. The only shutdown mode supported now is an equivalent of `ShutdownMode.Reload`. +- `ShutdownMode` has been removed. The only shutdown mode supported now is an equivalent of `ShutdownMode.Reload`. There is no need to specify it. -- implicit assembly loading (you have to explicitly `clr.AddReference` before doing import) -- messages in `PythonException` no longer start with exception type -- `PyScopeManager`, `PyScopeException`, `PyScope` (use `PyModule` instead) -- support for .NET Framework 4.0-4.6; Mono before 5.4. Python.NET now requires .NET Standard 2.0 +- implicit assembly loading (you have to explicitly `clr.AddReference` before doing import) +- messages in `PythonException` no longer start with exception type +- `PyScopeManager`, `PyScopeException`, `PyScope` (use `PyModule` instead) +- support for .NET Framework 4.0-4.6; Mono before 5.4. Python.NET now requires .NET Standard 2.0 (see [the matrix](https://docs.microsoft.com/en-us/dotnet/standard/net-standard#net-implementation-support)) ## [2.5.2](https://github.com/pythonnet/pythonnet/releases/tag/v2.5.2) - 2021-02-05 @@ -224,8 +237,9 @@ There is no need to specify it. Bugfix release. ### Fixed -- Fix `object[]` parameters taking precedence when should not in overload resolution -- Empty parameter names (as can be generated from F#) do not cause crashes + +- Fix `object[]` parameters taking precedence when should not in overload resolution +- Empty parameter names (as can be generated from F#) do not cause crashes ## [2.5.1](https://github.com/pythonnet/pythonnet/releases/tag/v2.5.1) - 2020-06-18 @@ -233,8 +247,8 @@ Bugfix release. ### Fixed -- Fix incorrect dereference of wrapper object in `tp_repr`, which may result in a program crash -- Fix incorrect dereference in params array handling +- Fix incorrect dereference of wrapper object in `tp_repr`, which may result in a program crash +- Fix incorrect dereference in params array handling ## [2.5.0](https://github.com/pythonnet/pythonnet/releases/tag/v2.5.0) - 2020-06-14 @@ -242,342 +256,341 @@ This version improves performance on benchmarks significantly compared to 2.3. ### Added -- Automatic NuGet package generation in appveyor and local builds -- Function that sets `Py_NoSiteFlag` to 1. -- Support for Jetson Nano. -- Support for `__len__` for .NET classes that implement ICollection -- `PyExport` attribute to hide .NET types from Python -- `PythonException.Format` method to format exceptions the same as +- Automatic NuGet package generation in appveyor and local builds +- Function that sets `Py_NoSiteFlag` to 1. +- Support for Jetson Nano. +- Support for `__len__` for .NET classes that implement ICollection +- `PyExport` attribute to hide .NET types from Python +- `PythonException.Format` method to format exceptions the same as `traceback.format_exception` -- `Runtime.None` to be able to pass `None` as parameter into Python from .NET -- `PyObject.IsNone()` to check if a Python object is None in .NET. -- Support for Python 3.8 -- Codecs as the designated way to handle automatic conversions between +- `Runtime.None` to be able to pass `None` as parameter into Python from .NET +- `PyObject.IsNone()` to check if a Python object is None in .NET. +- Support for Python 3.8 +- Codecs as the designated way to handle automatic conversions between .NET and Python types -- Added Python 3 buffer api support and PyBuffer interface for fast byte and numpy array read/write ([#980][p980]) +- Added Python 3 buffer api support and PyBuffer interface for fast byte and numpy array read/write ([#980][p980]) ### Changed -- Added argument types information to "No method matches given arguments" message -- Moved wheel import in setup.py inside of a try/except to prevent pip collection failures -- Removes `PyLong_GetMax` and `PyClass_New` when targetting Python3 -- Improved performance of calls from Python to C# -- Added support for converting python iterators to C# arrays -- Changed usage of the obsolete function +- Added argument types information to "No method matches given arguments" message +- Moved wheel import in setup.py inside of a try/except to prevent pip collection failures +- Removes `PyLong_GetMax` and `PyClass_New` when targetting Python3 +- Improved performance of calls from Python to C# +- Added support for converting python iterators to C# arrays +- Changed usage of the obsolete function `GetDelegateForFunctionPointer(IntPtr, Type)` to `GetDelegateForFunctionPointer(IntPtr)` -- When calling C# from Python, enable passing argument of any type to a +- When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881]) -- Added support for kwarg parameters when calling .NET methods from Python -- Changed method for finding MSBuild using vswhere -- Reworked `Finalizer`. Now objects drop into its queue upon finalization, +- Added support for kwarg parameters when calling .NET methods from Python +- Changed method for finding MSBuild using vswhere +- Reworked `Finalizer`. Now objects drop into its queue upon finalization, which is periodically drained when new objects are created. -- Marked `Runtime.OperatingSystemName` and `Runtime.MachineName` as +- Marked `Runtime.OperatingSystemName` and `Runtime.MachineName` as `Obsolete`, should never have been `public` in the first place. They also don't necessarily return a result that matches the `platform` module's. -- Unconditionally depend on `pycparser` for the interop module generation +- Unconditionally depend on `pycparser` for the interop module generation ### Fixed -- Fixed runtime that fails loading when using pythonnet in an environment +- Fixed runtime that fails loading when using pythonnet in an environment together with Nuitka -- Fixes bug where delegates get casts (dotnetcore) -- Determine size of interpreter longs at runtime -- Handling exceptions ocurred in ModuleObject's getattribute -- Fill `__classcell__` correctly for Python subclasses of .NET types -- Fixed issue with params methods that are not passed an array. -- Use UTF8 to encode strings passed to `PyRun_String` on Python 3 +- Fixes bug where delegates get casts (dotnetcore) +- Determine size of interpreter longs at runtime +- Handling exceptions ocurred in ModuleObject's getattribute +- Fill `__classcell__` correctly for Python subclasses of .NET types +- Fixed issue with params methods that are not passed an array. +- Use UTF8 to encode strings passed to `PyRun_String` on Python 3 ## [2.4.0][] - 2019-05-15 ### Added -- Added support for embedding python into dotnet core 2.0 (NetStandard 2.0) -- Added new build system (pythonnet.15.sln) based on dotnetcore-sdk/xplat(crossplatform msbuild). +- Added support for embedding python into dotnet core 2.0 (NetStandard 2.0) +- Added new build system (pythonnet.15.sln) based on dotnetcore-sdk/xplat(crossplatform msbuild). Currently there two side-by-side build systems that produces the same output (net40) from the same sources. After a some transition time, current (mono/ msbuild 14.0) build system will be removed. -- NUnit upgraded to 3.7 (eliminates travis-ci random bug) -- Added C# `PythonEngine.AddShutdownHandler` to help client code clean up on shutdown. -- Added `clr.GetClrType` ([#432][i432])([#433][p433]) -- Allowed passing `None` for nullable args ([#460][p460]) -- Added keyword arguments based on C# syntax for calling CPython methods ([#461][p461]) -- Catches exceptions thrown in C# iterators (yield returns) and rethrows them in python ([#475][i475])([#693][p693]) -- Implemented GetDynamicMemberNames() for PyObject to allow dynamic object members to be visible in the debugger ([#443][i443])([#690][p690]) -- Incorporated reference-style links to issues and pull requests in the CHANGELOG ([#608][i608]) -- Added PyObject finalizer support, Python objects referred by C# can be auto collect now ([#692][p692]). -- Added detailed comments about aproaches and dangers to handle multi-app-domains ([#625][p625]) -- Python 3.7 support, builds and testing added. Defaults changed from Python 3.6 to 3.7 ([#698][p698]) -- Added support for C# types to provide `__repr__` ([#680][p680]) +- NUnit upgraded to 3.7 (eliminates travis-ci random bug) +- Added C# `PythonEngine.AddShutdownHandler` to help client code clean up on shutdown. +- Added `clr.GetClrType` ([#432][i432])([#433][p433]) +- Allowed passing `None` for nullable args ([#460][p460]) +- Added keyword arguments based on C# syntax for calling CPython methods ([#461][p461]) +- Catches exceptions thrown in C# iterators (yield returns) and rethrows them in python ([#475][i475])([#693][p693]) +- Implemented GetDynamicMemberNames() for PyObject to allow dynamic object members to be visible in the debugger ([#443][i443])([#690][p690]) +- Incorporated reference-style links to issues and pull requests in the CHANGELOG ([#608][i608]) +- Added PyObject finalizer support, Python objects referred by C# can be auto collect now ([#692][p692]). +- Added detailed comments about aproaches and dangers to handle multi-app-domains ([#625][p625]) +- Python 3.7 support, builds and testing added. Defaults changed from Python 3.6 to 3.7 ([#698][p698]) +- Added support for C# types to provide `__repr__` ([#680][p680]) ### Changed -- PythonException included C# call stack -- Reattach python exception traceback information (#545) -- PythonEngine.Intialize will now call `Py_InitializeEx` with a default value of 0, so signals will not be configured by default on embedding. This is different from the previous behaviour, where `Py_Initialize` was called instead, which sets initSigs to 1. ([#449][i449]) -- Refactored MethodBinder.Bind in preparation to make it extensible (#829) -- Look for installed Windows 10 sdk's during installation instead of relying on specific versions. -- Remove `LoadLibrary` call. ([#880][p880]) +- PythonException included C# call stack +- Reattach python exception traceback information (#545) +- PythonEngine.Intialize will now call `Py_InitializeEx` with a default value of 0, so signals will not be configured by default on embedding. This is different from the previous behaviour, where `Py_Initialize` was called instead, which sets initSigs to 1. ([#449][i449]) +- Refactored MethodBinder.Bind in preparation to make it extensible (#829) +- Look for installed Windows 10 sdk's during installation instead of relying on specific versions. +- Remove `LoadLibrary` call. ([#880][p880]) ### Fixed -- Fixed secondary PythonEngine.Initialize call, all sensitive static variables now reseted. +- Fixed secondary PythonEngine.Initialize call, all sensitive static variables now reseted. This is a hidden bug. Once python cleaning up enough memory, objects from previous engine run becomes corrupted. ([#534][p534]) -- Fixed Visual Studio 2017 compat ([#434][i434]) for setup.py -- Fixed crashes when integrating pythonnet in Unity3d ([#714][i714]), +- Fixed Visual Studio 2017 compat ([#434][i434]) for setup.py +- Fixed crashes when integrating pythonnet in Unity3d ([#714][i714]), related to unloading the Application Domain -- Fixed interop methods with Py_ssize_t. NetCoreApp 2.0 is more sensitive than net40 and requires this fix. ([#531][p531]) -- Fixed crash on exit of the Python interpreter if a python class +- Fixed interop methods with Py_ssize_t. NetCoreApp 2.0 is more sensitive than net40 and requires this fix. ([#531][p531]) +- Fixed crash on exit of the Python interpreter if a python class derived from a .NET class has a `__namespace__` or `__assembly__` attribute ([#481][i481]) -- Fixed conversion of 'float' and 'double' values ([#486][i486]) -- Fixed 'clrmethod' for python 2 ([#492][i492]) -- Fixed double calling of constructor when deriving from .NET class ([#495][i495]) -- Fixed `clr.GetClrType` when iterating over `System` members ([#607][p607]) -- Fixed `LockRecursionException` when loading assemblies ([#627][i627]) -- Fixed errors breaking .NET Remoting on method invoke ([#276][i276]) -- Fixed PyObject.GetHashCode ([#676][i676]) -- Fix memory leaks due to spurious handle incrementation ([#691][i691]) -- Fix spurious assembly loading exceptions from private types ([#703][i703]) -- Fix inheritance of non-abstract base methods ([#755][i755]) - +- Fixed conversion of 'float' and 'double' values ([#486][i486]) +- Fixed 'clrmethod' for python 2 ([#492][i492]) +- Fixed double calling of constructor when deriving from .NET class ([#495][i495]) +- Fixed `clr.GetClrType` when iterating over `System` members ([#607][p607]) +- Fixed `LockRecursionException` when loading assemblies ([#627][i627]) +- Fixed errors breaking .NET Remoting on method invoke ([#276][i276]) +- Fixed PyObject.GetHashCode ([#676][i676]) +- Fix memory leaks due to spurious handle incrementation ([#691][i691]) +- Fix spurious assembly loading exceptions from private types ([#703][i703]) +- Fix inheritance of non-abstract base methods ([#755][i755]) ## [2.3.0][] - 2017-03-11 ### Added -- Added Code Coverage ([#345][p345]) -- Added `PySys_SetArgvEx` ([#347][p347]) -- Added XML Documentation ([#349][p349]) -- Added `Embedded_Tests` on AppVeyor ([#224][i224])([#353][p353]) -- Added `Embedded_Tests` on Travis ([#224][i224])([#391][p391]) -- Added PY3 settings to solution configuration-manager ([#346][p346]) -- Added `Slack` ([#384][p384])([#383][i383])([#386][p386]) -- Added function of passing an arbitrary .NET object as the value +- Added Code Coverage ([#345][p345]) +- Added `PySys_SetArgvEx` ([#347][p347]) +- Added XML Documentation ([#349][p349]) +- Added `Embedded_Tests` on AppVeyor ([#224][i224])([#353][p353]) +- Added `Embedded_Tests` on Travis ([#224][i224])([#391][p391]) +- Added PY3 settings to solution configuration-manager ([#346][p346]) +- Added `Slack` ([#384][p384])([#383][i383])([#386][p386]) +- Added function of passing an arbitrary .NET object as the value of an attribute of `PyObject` ([#370][i370])([#373][p373]) -- Added `Coverity scan` ([#390][i390]) -- Added `bumpversion` for version control ([#319][i319])([#398][p398]) -- Added `tox` for local testing ([#345][p345]) -- Added `requirements.txt` -- Added to `PythonEngine` methods `Eval` and `Exec` ([#389][p389]) -- Added implementations of `ICustomMarshal` ([#407][p407]) -- Added docker images ([#322][i322]) -- Added hooks in `pyinstaller` and `cx_freeze` for `pythonnet` ([#66][i66]) +- Added `Coverity scan` ([#390][i390]) +- Added `bumpversion` for version control ([#319][i319])([#398][p398]) +- Added `tox` for local testing ([#345][p345]) +- Added `requirements.txt` +- Added to `PythonEngine` methods `Eval` and `Exec` ([#389][p389]) +- Added implementations of `ICustomMarshal` ([#407][p407]) +- Added docker images ([#322][i322]) +- Added hooks in `pyinstaller` and `cx_freeze` for `pythonnet` ([#66][i66]) ### Changed -- Refactored python `unittests` ([#329][p329]) -- Refactored python `setup.py` ([#337][p337]) -- Refactored remaining of Build Directives on `runtime.cs` ([#339][p339]) -- Refactored `Embedded_Tests` to make easier to write tests ([#369][p369]) -- Changed `unittests` to `pytest` ([#368][p368]) -- Upgraded NUnit framework from `2.6.3` to `3.5.0` ([#341][p341]) -- Downgraded NUnit framework from `3.5.0` to `2.6.4` ([#353][p353]) -- Upgraded NUnit framework from `2.6.4` to `3.6.0` ([#371][p371]) -- Unfroze Mono version on Travis ([#345][p345]) -- Changed `conda.recipe` build to only pull-requests ([#345][p345]) -- Combine `Py_DEBUG` and `PYTHON_WITH_PYDEBUG` flags ([#362][i362]) +- Refactored python `unittests` ([#329][p329]) +- Refactored python `setup.py` ([#337][p337]) +- Refactored remaining of Build Directives on `runtime.cs` ([#339][p339]) +- Refactored `Embedded_Tests` to make easier to write tests ([#369][p369]) +- Changed `unittests` to `pytest` ([#368][p368]) +- Upgraded NUnit framework from `2.6.3` to `3.5.0` ([#341][p341]) +- Downgraded NUnit framework from `3.5.0` to `2.6.4` ([#353][p353]) +- Upgraded NUnit framework from `2.6.4` to `3.6.0` ([#371][p371]) +- Unfroze Mono version on Travis ([#345][p345]) +- Changed `conda.recipe` build to only pull-requests ([#345][p345]) +- Combine `Py_DEBUG` and `PYTHON_WITH_PYDEBUG` flags ([#362][i362]) ### Deprecated -- Deprecated `RunString` ([#401][i401]) +- Deprecated `RunString` ([#401][i401]) ### Fixed -- Fixed crash during Initialization ([#262][i262])([#343][p343]) -- Fixed crash during Shutdown ([#365][p365]) -- Fixed multiple build warnings -- Fixed method signature match for Object Type ([#203][i203])([#377][p377]) -- Fixed outdated version number in AssemblyInfo ([#398][p398]) -- Fixed wrong version number in `conda.recipe` ([#398][p398]) -- Fixed fixture location for Python tests and `Embedded_Tests` -- Fixed `PythonException` crash during Shutdown ([#400][p400]) -- Fixed `AppDomain` unload during GC ([#397][i397])([#400][p400]) -- Fixed `Py_Main` & `PySys_SetArgvEx` `no mem error` on `UCS4/PY3` ([#399][p399]) -- Fixed `Python.Runtime.dll.config` on macOS ([#120][i120]) -- Fixed crash on `PythonEngine.Version` ([#413][i413]) -- Fixed `PythonEngine.PythonPath` issues ([#179][i179])([#414][i414])([#415][p415]) -- Fixed missing information on 'No method matches given arguments' by adding the method name +- Fixed crash during Initialization ([#262][i262])([#343][p343]) +- Fixed crash during Shutdown ([#365][p365]) +- Fixed multiple build warnings +- Fixed method signature match for Object Type ([#203][i203])([#377][p377]) +- Fixed outdated version number in AssemblyInfo ([#398][p398]) +- Fixed wrong version number in `conda.recipe` ([#398][p398]) +- Fixed fixture location for Python tests and `Embedded_Tests` +- Fixed `PythonException` crash during Shutdown ([#400][p400]) +- Fixed `AppDomain` unload during GC ([#397][i397])([#400][p400]) +- Fixed `Py_Main` & `PySys_SetArgvEx` `no mem error` on `UCS4/PY3` ([#399][p399]) +- Fixed `Python.Runtime.dll.config` on macOS ([#120][i120]) +- Fixed crash on `PythonEngine.Version` ([#413][i413]) +- Fixed `PythonEngine.PythonPath` issues ([#179][i179])([#414][i414])([#415][p415]) +- Fixed missing information on 'No method matches given arguments' by adding the method name ### Removed -- Removed `six` dependency for `unittests` ([#329][p329]) -- Removed `Mono.Unix` dependency for `UCS4` ([#360][p360]) -- Removed need for `Python.Runtime.dll.config` -- Removed PY32 build option `PYTHON_WITH_WIDE_UNICODE` ([#417][i417]) +- Removed `six` dependency for `unittests` ([#329][p329]) +- Removed `Mono.Unix` dependency for `UCS4` ([#360][p360]) +- Removed need for `Python.Runtime.dll.config` +- Removed PY32 build option `PYTHON_WITH_WIDE_UNICODE` ([#417][i417]) ## [2.2.2][] - 2017-01-29 ### Fixed -- Missing files from packaging ([#336][i336]) +- Missing files from packaging ([#336][i336]) ## [2.2.1][] - 2017-01-26 -- `v2.2.0` had a release issue on PyPi. Bumped to `v2.2.1` +- `v2.2.0` had a release issue on PyPi. Bumped to `v2.2.1` ### Added -- Python 3.6 support ([#310][p310]) -- Added `__version__` to module ([#312][p312]) -- Added `conda` recipe ([#281][p281]) -- Nuget update on build ([#268][p268]) -- Added `__cause__` attribute on exception ([#287][p287]) +- Python 3.6 support ([#310][p310]) +- Added `__version__` to module ([#312][p312]) +- Added `conda` recipe ([#281][p281]) +- Nuget update on build ([#268][p268]) +- Added `__cause__` attribute on exception ([#287][p287]) ### Changed -- License to MIT ([#314][p314]) -- Project clean-up ([#320][p320]) -- Refactor `#if` directives -- Rename Decref/Incref to XDecref/XIncre ([#275][p275]) -- Remove printing if Decref is called with NULL ([#275][p275]) +- License to MIT ([#314][p314]) +- Project clean-up ([#320][p320]) +- Refactor `#if` directives +- Rename Decref/Incref to XDecref/XIncre ([#275][p275]) +- Remove printing if Decref is called with NULL ([#275][p275]) ### Removed -- Python 2.6 support ([#270][i270]) -- Python 3.2 support ([#270][i270]) +- Python 2.6 support ([#270][i270]) +- Python 3.2 support ([#270][i270]) ### Fixed -- Fixed `isinstance` refcount_leak ([#273][p273]) -- Comparison Operators ([#294][p294]) -- Improved Linux support ([#300][p300]) -- Exception pickling ([#286][p286]) +- Fixed `isinstance` refcount_leak ([#273][p273]) +- Comparison Operators ([#294][p294]) +- Improved Linux support ([#300][p300]) +- Exception pickling ([#286][p286]) ## [2.2.0-dev1][] - 2016-09-19 ### Changed -- Switch to C# 6.0 ([#219][p219]) -- `setup.py` improvements for locating build tools ([#208][p208]) -- unmanaged exports updated ([#206][p206]) -- Mono update pinned to 4.2.4.4 ([#233][p233]) +- Switch to C# 6.0 ([#219][p219]) +- `setup.py` improvements for locating build tools ([#208][p208]) +- unmanaged exports updated ([#206][p206]) +- Mono update pinned to 4.2.4.4 ([#233][p233]) ### Fixed -- Fixed relative imports ([#219][p219]) -- Fixed recursive types ([#250][p250]) -- Demo fix - stream reading ([#225][p225]) +- Fixed relative imports ([#219][p219]) +- Fixed recursive types ([#250][p250]) +- Demo fix - stream reading ([#225][p225]) ## [2.1.0][] - 2016-04-12 ### Added -- Added Python 3.2 support. ([#78][p78]) -- Added Python 3.3 support. ([#78][p78]) -- Added Python 3.4 support. ([#78][p78]) -- Added Python 3.5 support. ([#163][p163]) -- Managed types can be sub-classed in Python ([#78][p78]) -- Uses dynamic objects for cleaner code when embedding Python ([#78][p78]) +- Added Python 3.2 support. ([#78][p78]) +- Added Python 3.3 support. ([#78][p78]) +- Added Python 3.4 support. ([#78][p78]) +- Added Python 3.5 support. ([#163][p163]) +- Managed types can be sub-classed in Python ([#78][p78]) +- Uses dynamic objects for cleaner code when embedding Python ([#78][p78]) ### Changed -- Better Linux support (with or without --enable-shared option) ([#78][p78]) +- Better Linux support (with or without --enable-shared option) ([#78][p78]) ### Removed -- Implicit Type Casting ([#131][i131]) +- Implicit Type Casting ([#131][i131]) ## [2.0.0][] - 2015-06-26 -- Release +- Release ## 2.0.0-alpha.2 ### Changed -- First work on Python 2.5 compatibility. The destination version can be +- First work on Python 2.5 compatibility. The destination version can be set by defining PYTHON24 or PYTHON25. Python 2.6 compatibility is in work. -- Added VS 2005 solution and project files including a UnitTest +- Added VS 2005 solution and project files including a UnitTest configuration which runs the unit test suite. -- Enhanced unit test suite. All test cases are combined in a single +- Enhanced unit test suite. All test cases are combined in a single test suite now. -- Fixed bugs in generics support for all Python versions. +- Fixed bugs in generics support for all Python versions. -- Fixed exception bugs for Python 2.5+. When compiled for Python 2.5+ all +- Fixed exception bugs for Python 2.5+. When compiled for Python 2.5+ all managed exceptions are based on Python's `exceptions.Exception` class. -- Added deprecation warnings for importing from `CLR.*` and the CLR module. +- Added deprecation warnings for importing from `CLR.*` and the CLR module. -- Implemented support for methods with variable arguments +- Implemented support for methods with variable arguments `spam(params object[] egg)` -- Fixed Mono support by adding a custom marshaler for UCS-4 unicode, +- Fixed Mono support by adding a custom marshaler for UCS-4 unicode, fixing a some ref counter bugs and creating a new makefile.mono. -- Added a standard python extension to load the clr environment. +- Added a standard python extension to load the clr environment. The `src/monoclr/` directory contains additional sample code like a Python binary linked against `libpython2.x.so` and some example code how to embed Mono and PythonNet in a C application. -- Added yet another python prompt. This time it's a C application that +- Added yet another python prompt. This time it's a C application that embedds both Python and Mono. It may be useful as an example app for others and I need it to debug a nasty bug. -- Implemented `ModuleFunctionAttribute` and added +- Implemented `ModuleFunctionAttribute` and added `ForbidPythonThreadsAttribute`. The latter is required for module functions which invoke Python methods. -- Added `clr.setPreload()`, `clr.getPreload()`, +- Added `clr.setPreload()`, `clr.getPreload()`, `clr.AddReference("assembly name")`, `clr.FindAssembly("name")` and `clr.ListAssemblies(verbose)`. Automatic preloading can be enabled with clr.setPreload/True). Preloading is automatically enabled for interactive Python shells and disabled in all other cases. -- New Makefile that works for Windows and Mono and autodetects the Python +- New Makefile that works for Windows and Mono and autodetects the Python version and UCS 2/4 setting. -- Added code for Python 2.3. PythonNet can be build for Python 2.3 again +- Added code for Python 2.3. PythonNet can be build for Python 2.3 again but it is not fully supported. -- Changed the PythonException.Message value so it displays the name of +- Changed the PythonException.Message value so it displays the name of the exception class `Exception` instead of its representation ``. -- Added `Python.Runtime.dll.config`. +- Added `Python.Runtime.dll.config`. ## 2.0.0-alpha.1 ### Changed -- Moved the Python for .NET project to Sourceforge and moved version +- Moved the Python for .NET project to Sourceforge and moved version control to Subversion. -- Removed `CallConvCdecl` attributes and the IL hack that they supported. +- Removed `CallConvCdecl` attributes and the IL hack that they supported. .NET 2.x now supports `UnmanagedFunctionPointer`, which does the right thing without the hackery required in 1.x. This removes a dependency on ILASM to build the package and better supports Mono (in theory). -- Refactored import and assembly management machinery. The old `CLR.` +- Refactored import and assembly management machinery. The old `CLR.` syntax for import is deprecated, but still supported until 3.x. The recommended style now is to use `from System import xxx`, etc. We also now support `from X import *` correctly. -- Implemented a (lowercase) `clr` module to match IronPython for code +- Implemented a (lowercase) `clr` module to match IronPython for code compatibility. Methods of this module should be used to explicitly load assemblies. Implicit (name-based) assembly loading will still work until 3.x, but it is deprecated. -- Implemented support for generic types and generic methods using the +- Implemented support for generic types and generic methods using the same patterns and syntax as IronPython. See the documentation for usage details. -- Many small and large performance improvements, switched to generic +- Many small and large performance improvements, switched to generic collections for some internals, better algorithms for assembly scanning, etc. -- Fixed an unboxing issue in generated delegate implementation code +- Fixed an unboxing issue in generated delegate implementation code that affected delegates that return value types. ## [1.0.0][] - 2006-04-08 ### Changed -- Backported the refactored import and assembly management from the 2.x +- Backported the refactored import and assembly management from the 2.x line, mainly to improve the possibility of code-compatibility with IronPython. @@ -585,48 +598,48 @@ This version improves performance on benchmarks significantly compared to 2.3. ### Changed -- Changed some uses of Finalize as a static method name that confused the +- Changed some uses of Finalize as a static method name that confused the Mono compiler and people reading the code. Note that this may be a breaking change if anyone was calling `PythonEngine.Finalize()`. If so, you should now use `PythonEngine.Shutdown()`. -- Tweaked assembly lookup to ensure that assemblies can be found in the +- Tweaked assembly lookup to ensure that assemblies can be found in the current working directory, even after changing directories using things like `os.chdir()` from Python. -- Fixed some incorrect finalizers (thanks to Greg Chapman for the report) +- Fixed some incorrect finalizers (thanks to Greg Chapman for the report) that may have caused some threading oddities. -- Tweaked support for out and ref parameters. If a method has a return +- Tweaked support for out and ref parameters. If a method has a return type of void and a single ref or out parameter, that parameter will be returned as the result of the method. This matches the current behavior of IronPython and makes it more likely that code can be moved between Python for .NET and IP in the future. -- Refactored part of the assembly manager to remove a potential case of +- Refactored part of the assembly manager to remove a potential case of thread-deadlock in multi-threaded applications. -- Added a `__str__` method to managed exceptions that returns the Message +- Added a `__str__` method to managed exceptions that returns the Message attribute of the exception and the StackTrace (if available). ## 1.0.0-rc.1 ### Changed -- Implemented a workaround for the fact that exceptions cannot be new-style +- Implemented a workaround for the fact that exceptions cannot be new-style classes in the CPython interpreter. Managed exceptions can now be raised and caught naturally from Python (hooray!) -- Implemented support for invoking methods with out and ref parameters. +- Implemented support for invoking methods with out and ref parameters. Because there is no real equivalent to these in Python, methods that have out or ref parameters will return a tuple. The tuple will contain the result of the method as its first item, followed by out parameter values in the order of their declaration in the method signature. -- Fixed a refcount problem that caused a crash when CLR was imported in +- Fixed a refcount problem that caused a crash when CLR was imported in an existing installed Python interpreter. -- Added an automatic conversion from Python strings to `byte[]`. This makes +- Added an automatic conversion from Python strings to `byte[]`. This makes it easier to pass `byte[]` data to managed methods (or set properties, etc.) as a Python string without having to write explicit conversion code. Also works for sbyte arrays. Note that `byte` and `sbyte` arrays @@ -634,66 +647,66 @@ This version improves performance on benchmarks significantly compared to 2.3. do _not_ get converted to Python strings - they remain instances of `Byte[]` or `SByte[]`. -- Added conversion of generic Python sequences to object arrays when +- Added conversion of generic Python sequences to object arrays when appropriate (thanks to Mackenzie Straight for the patch). -- Added a bit of cautionary documentation for embedders, focused on +- Added a bit of cautionary documentation for embedders, focused on correct handling of the Python global interpreter lock from managed code for code that calls into Python. -- `PyObject.FromManagedObject` now correctly returns the Python None object +- `PyObject.FromManagedObject` now correctly returns the Python None object if the input is a null reference. Also added a new `AsManagedObject` method to `PyObject`, making it easier to convert a Python-wrapped managed object to the real managed object. -- Created a simple installer for windows platforms. +- Created a simple installer for windows platforms. ## 1.0.0-beta.5 ### Changed -- Refactored and fixed threading and global interpreter lock handling, +- Refactored and fixed threading and global interpreter lock handling, which was badly broken before. Also added a number of threading and GIL-handling tests. -- Related to the GIL fixes, added a note to embedders in the README +- Related to the GIL fixes, added a note to embedders in the README about using the AcquireLock and ReleaseLock methods of the PythonEngine class to manage the GIL. -- Fixed a problem in `Single <--> float` conversion for cultures that use +- Fixed a problem in `Single <--> float` conversion for cultures that use different decimal symbols than Python. -- Added a new `ReloadModule` method to the `PythonEngine` class that hooks +- Added a new `ReloadModule` method to the `PythonEngine` class that hooks Python module reloading (`PyImport_ReloadModule`). -- Added a new `StringAsModule` method to the PythonEngine class that can +- Added a new `StringAsModule` method to the PythonEngine class that can create a module from a managed string of code. -- Added a default `__str__` implementation for Python wrappers of managed +- Added a default `__str__` implementation for Python wrappers of managed objects that calls the `ToString` method of the managed object. ## 1.0.0-beta.4 ### Changed -- Fixed a problem that made it impossible to override "special" methods +- Fixed a problem that made it impossible to override "special" methods like `__getitem__` in subclasses of managed classes. Now the tests all pass, and there is much rejoicing. -- Managed classes reflected to Python now have an `__doc__` attribute that +- Managed classes reflected to Python now have an `__doc__` attribute that contains a listing of the class constructor signatures. -- Fixed a problem that prevented passing null (None) for array arguments. +- Fixed a problem that prevented passing null (None) for array arguments. -- Added a number of new argument conversion tests. Thanks to Laurent +- Added a number of new argument conversion tests. Thanks to Laurent Caumont for giving Python for .NET a good workout with managed DirectX. -- Updated the bundled C Python runtime and libraries to Python 2.4. The +- Updated the bundled C Python runtime and libraries to Python 2.4. The current release is known to also run with Python 2.3. It is known _not_ to work with older versions due to changes in CPython type object structure. -- Mostly fixed the differences in the way that import works depending +- Mostly fixed the differences in the way that import works depending on whether you are using the bundled interpreter or an existing Python interpreter. The hack I used makes import work uniformly for imports done in Python modules. Unfortunately, there is still a limitation @@ -706,21 +719,21 @@ This version improves performance on benchmarks significantly compared to 2.3. in control for the duration of that first import, so sub-names won't be found until the next import, which will use the now-installed hook. -- Added support to directly iterate over objects that support IEnumerator +- Added support to directly iterate over objects that support IEnumerator (as well as IEnumerable). Thanks to Greg Chapman for prodding me ;) -- Added a section to the README dealing with rebuilding Python for .NET +- Added a section to the README dealing with rebuilding Python for .NET against other CPython versions. -- Fixed a problem with accessing properties when only the interface for +- Fixed a problem with accessing properties when only the interface for an object is known. For example, `ICollection(ob).Count` failed because Python for .NET mistakenly decided that Count was abstract. -- Fixed some problems with how COM-based objects are exposed and how +- Fixed some problems with how COM-based objects are exposed and how members of inherited interfaces are exposed. Thanks to Bruce Dodson for patches on this. -- Changed the Runtime class to use a const string to target the +- Changed the Runtime class to use a const string to target the appropriate CPython dll in DllImport attributes. Now you only have to change one line to target a new Python version. @@ -728,7 +741,7 @@ This version improves performance on benchmarks significantly compared to 2.3. ### Changed -- A dumb bug that could cause a crash on startup on some platforms was +- A dumb bug that could cause a crash on startup on some platforms was fixed. Decided to update the beta for this, as a number of people were running into the problem. @@ -736,12 +749,12 @@ This version improves performance on benchmarks significantly compared to 2.3. ### Changed -- Exceptions raised as a result of getting or setting properties were +- Exceptions raised as a result of getting or setting properties were not very helpful (target invokation exception). This has been changed to pass through the inner exception the way that methods do, which is much more likely to be the real exception that caused the problem. -- Events were refactored as the implementation was based on some bad +- Events were refactored as the implementation was based on some bad assumptions. As a result, subscription and unsubscription now works correctly. A change from beta 1 is that event objects are no longer directly callable - this was not appropriate, since the internal @@ -749,21 +762,21 @@ This version improves performance on benchmarks significantly compared to 2.3. you should the appropriate `OnSomeEvent` method published by a class to fire an event. -- The distribution did not include the key file, making it a pain for +- The distribution did not include the key file, making it a pain for people to build from source. Added the key file to the distribution buildout for beta 2. -- Assemblies can now be found and loaded if they are on the PYTHONPATH. +- Assemblies can now be found and loaded if they are on the PYTHONPATH. Previously only the appbase and the GAC were checked. The system now checks PYTHONPATH first, then the appbase, then the GAC. -- Fixed a bug in constructor invokation during object instantiation. +- Fixed a bug in constructor invokation during object instantiation. ## 1.0.0-beta.1 ### Changed -- Added the baseline of the managed embedding API. Some of the details +- Added the baseline of the managed embedding API. Some of the details are still subject to change based on some real-world use and feedback. The embedding API is based on the `PyObject` class, along with a number @@ -772,117 +785,115 @@ This version improves performance on benchmarks significantly compared to 2.3. is intended be familar to anyone who has used Python / C++ wrapper libraries like CXX or Boost. -- Started integrating NUnit2 to support unit tests for the embedding +- Started integrating NUnit2 to support unit tests for the embedding layer - still need to add the embedding tests (many already exist, but were written for an older version of NUnit). -- Added Python iteration protocol support for arrays and managed objects +- Added Python iteration protocol support for arrays and managed objects that implement IEnumerable. This means that you can now use the Python idiom `for item in object:` on any array or IEnumerable object. -- Added automatic conversion from Python sequence types to managed array +- Added automatic conversion from Python sequence types to managed array types. This means, for example, that you can now call a managed method like AddRange that expects an array with any Python object that supports the Python sequence protocol, provided the items of the sequence are convertible to the item type of the managed array. -- Added new demo scripts, mostly more substantial winforms examples. +- Added new demo scripts, mostly more substantial winforms examples. -- Finished the unit tests for event support, and fixed lots of problems +- Finished the unit tests for event support, and fixed lots of problems with events and delegates as a result. This is one of the trickier parts of the integration layer, and there is good coverage of these in the unit tests now. -- Did a fair amount of profiling with an eval version of ANTS (which is +- Did a fair amount of profiling with an eval version of ANTS (which is quite nice, BTW) and made a few changes as a result. -- Type management was refactored, fixing the issue that caused segfaults +- Type management was refactored, fixing the issue that caused segfaults when GC was enabled. Unit tests, stress tests and demo apps now all run nicely with Python GC enabled. There are one or two things left to fix, but the fixes should not have any user impact. -- Changed to base PythonNet on Python 2.3.2. This is considered the most +- Changed to base PythonNet on Python 2.3.2. This is considered the most stable release, and a good 25 - 30% faster as well. -- Added a new `CLR.dll` that acts as an extension module that allows an +- Added a new `CLR.dll` that acts as an extension module that allows an existing unmodified Python 2.3 installation to simply `import CLR` to bootstrap the managed integration layer. -- A bug was causing managed methods to only expose overloads declared in +- A bug was causing managed methods to only expose overloads declared in a particular class, hiding inherited overloads of the same name. Fixed the bug and added some unit tests. -- Added a virtual `__doc__` attribute to managed methods that contains +- Added a virtual `__doc__` attribute to managed methods that contains the signature of the method. This also means that the Python `help` function now provides signature info when used on a managed class. -- Calling managed methods and events `unbound` (passing the instance as +- Calling managed methods and events `unbound` (passing the instance as the first argument) now works. There is a caveat for methods - if a class declares both static and instance methods with the same name, it is not possible to call that instance method unbound (the static method will always be called). -- Overload selection for overloaded methods is now much better and uses +- Overload selection for overloaded methods is now much better and uses a method resolution algorithm similar to that used by Jython. -- Changed the managed python.exe wrapper to run as an STA thread, which +- Changed the managed python.exe wrapper to run as an STA thread, which seems to be more compatible with winforms apps. This needs a better solution long-term. One possibility would be a command line switch so that -sta or -mta could control the python.exe apartment state. -- Added support for the Python boolean type (True, False). Bool values +- Added support for the Python boolean type (True, False). Bool values now appear as True or False to Python. ## 1.0.0-alpha.2 ### Changed -- Added a Mono makefile. Thanks to Camilo Uribe for help in testing and +- Added a Mono makefile. Thanks to Camilo Uribe for help in testing and working out problems on Mono. Note that it not currently possible to build PythonNet using mono, due to the use of some IL attributes that the mono assembler / disassembler doesn't support yet. -- Preliminary tests show that PythonNet _does_ actually run under mono, +- Preliminary tests show that PythonNet _does_ actually run under mono, though the test suite bombs out before the end with an "out of memory" error from the mono runtime. It's just a guess at this point, but I suspect there may be a limited pool for allocating certain reflection structures, and Python uses the reflection infrastructure quite heavily. -- Removed decoys like the non-working embedding APIs; lots of internal +- Removed decoys like the non-working embedding APIs; lots of internal refactoring. -- Implemented indexer support. Managed instances that implement indexers +- Implemented indexer support. Managed instances that implement indexers can now be used naturally from Python (e.g. `someobject[0]`). -- Implemented sequence protocol support for managed arrays. +- Implemented sequence protocol support for managed arrays. -- Implemented basic thread state management; calls to managed methods +- Implemented basic thread state management; calls to managed methods no longer block Python. I won't go so far as to say the thread choreography is "finished", as I don't have a comprehensive set of tests to back that up yet (and it will take some work to write a sufficiently large and evil set of tests). -- Fixed a bug that caused conversions of managed strings to PyUnicode to +- Fixed a bug that caused conversions of managed strings to PyUnicode to produce mangled values in certain situations. -- Fixed a number of problems related to subclassing a managed class, +- Fixed a number of problems related to subclassing a managed class, including the fact that it didn't work :) -- Fixed all of the bugs that were causing tests to fail. This release +- Fixed all of the bugs that were causing tests to fail. This release contains all new bugs and new failing tests. Progress! :) ## 1.0.0-alpha.1 ### Added -- Initial (mostly) working experimental release. +- Initial (mostly) working experimental release. [keep a changelog]: http://keepachangelog.com/ [semantic versioning]: http://semver.org/ -[unreleased]: ../../compare/v3.0.1...HEAD - [2.3.0]: ../../compare/v2.2.2...v2.3.0 [2.2.2]: ../../compare/v2.2.1...v2.2.2 @@ -907,7 +918,6 @@ This version improves performance on benchmarks significantly compared to 2.3. [p433]: https://github.com/pythonnet/pythonnet/pull/433 [p460]: https://github.com/pythonnet/pythonnet/pull/460 [p461]: https://github.com/pythonnet/pythonnet/pull/461 -[p433]: https://github.com/pythonnet/pythonnet/pull/433 [i434]: https://github.com/pythonnet/pythonnet/issues/434 [i481]: https://github.com/pythonnet/pythonnet/issues/481 [i486]: https://github.com/pythonnet/pythonnet/issues/486 @@ -932,7 +942,6 @@ This version improves performance on benchmarks significantly compared to 2.3. [i390]: https://github.com/pythonnet/pythonnet/issues/390 [i319]: https://github.com/pythonnet/pythonnet/issues/319 [p398]: https://github.com/pythonnet/pythonnet/pull/398 -[p345]: https://github.com/pythonnet/pythonnet/pull/345 [p389]: https://github.com/pythonnet/pythonnet/pull/389 [p407]: https://github.com/pythonnet/pythonnet/pull/407 [i322]: https://github.com/pythonnet/pythonnet/issues/322 @@ -943,9 +952,7 @@ This version improves performance on benchmarks significantly compared to 2.3. [p369]: https://github.com/pythonnet/pythonnet/pull/369 [p368]: https://github.com/pythonnet/pythonnet/pull/368 [p341]: https://github.com/pythonnet/pythonnet/pull/341 -[p353]: https://github.com/pythonnet/pythonnet/pull/353 [p371]: https://github.com/pythonnet/pythonnet/pull/371 -[p345]: https://github.com/pythonnet/pythonnet/pull/345 [i362]: https://github.com/pythonnet/pythonnet/issues/362 [i401]: https://github.com/pythonnet/pythonnet/issues/401 [i262]: https://github.com/pythonnet/pythonnet/issues/262 @@ -953,7 +960,6 @@ This version improves performance on benchmarks significantly compared to 2.3. [p365]: https://github.com/pythonnet/pythonnet/pull/365 [i203]: https://github.com/pythonnet/pythonnet/issues/203 [p377]: https://github.com/pythonnet/pythonnet/pull/377 -[p398]: https://github.com/pythonnet/pythonnet/pull/398 [p400]: https://github.com/pythonnet/pythonnet/pull/400 [i397]: https://github.com/pythonnet/pythonnet/issues/397 [p399]: https://github.com/pythonnet/pythonnet/pull/399 @@ -962,7 +968,6 @@ This version improves performance on benchmarks significantly compared to 2.3. [i179]: https://github.com/pythonnet/pythonnet/issues/179 [i414]: https://github.com/pythonnet/pythonnet/issues/414 [p415]: https://github.com/pythonnet/pythonnet/pull/415 -[p329]: https://github.com/pythonnet/pythonnet/pull/329 [p360]: https://github.com/pythonnet/pythonnet/pull/360 [i417]: https://github.com/pythonnet/pythonnet/issues/417 [i336]: https://github.com/pythonnet/pythonnet/issues/336 @@ -983,7 +988,6 @@ This version improves performance on benchmarks significantly compared to 2.3. [p208]: https://github.com/pythonnet/pythonnet/pull/208 [p206]: https://github.com/pythonnet/pythonnet/pull/206 [p233]: https://github.com/pythonnet/pythonnet/pull/233 -[p219]: https://github.com/pythonnet/pythonnet/pull/219 [p250]: https://github.com/pythonnet/pythonnet/pull/250 [p225]: https://github.com/pythonnet/pythonnet/pull/225 [p78]: https://github.com/pythonnet/pythonnet/pull/78 @@ -997,6 +1001,5 @@ This version improves performance on benchmarks significantly compared to 2.3. [i1342]: https://github.com/pythonnet/pythonnet/issues/1342 [i238]: https://github.com/pythonnet/pythonnet/issues/238 [i1481]: https://github.com/pythonnet/pythonnet/issues/1481 -[i1672]: https://github.com/pythonnet/pythonnet/pull/1672 [i2311]: https://github.com/pythonnet/pythonnet/issues/2311 [i1977]: https://github.com/pythonnet/pythonnet/issues/1977 diff --git a/version.txt b/version.txt index eb734bb99..fd2a01863 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.0-rc.1 +3.1.0 From fa7b3b77469cbaf0079ae389644c7ce240463b38 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 23 May 2026 22:34:24 +0200 Subject: [PATCH 194/194] Back to dev --- CHANGELOG.md | 8 ++++++++ version.txt | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 546413849..43e8c4f8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ project adheres to [Semantic Versioning][]. This document follows the conventions laid out in [Keep a CHANGELOG][]. +## Unreleased + +### Added + +### Changed + +### Fixed + ## 3.1.0 - 2026-05-23 ### Added diff --git a/version.txt b/version.txt index fd2a01863..df4a76732 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.1.0 +3.2.0-dev