From 131b466dcaa9cf9135f3b1859c8a41ee229cace8 Mon Sep 17 00:00:00 2001 From: legomanww Date: Sun, 12 Mar 2023 14:01:52 -0700 Subject: [PATCH 01/66] 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 02/66] 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 03/66] 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 04/66] 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 05/66] 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 06/66] 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 07/66] 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 08/66] 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 09/66] 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 10/66] 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 11/66] 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 12/66] 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 13/66] 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 14/66] 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 15/66] 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 16/66] 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 17/66] 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 18/66] 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 19/66] 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 20/66] 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 21/66] 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 22/66] 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 23/66] 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 24/66] 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 25/66] 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 26/66] 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 27/66] 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 28/66] 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 29/66] 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 30/66] 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 31/66] 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 32/66] 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 33/66] 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 34/66] 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 35/66] 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 36/66] 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 37/66] 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 38/66] 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 39/66] 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 40/66] 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 41/66] 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 42/66] 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 43/66] 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 44/66] 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 45/66] 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 46/66] 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 47/66] 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 48/66] 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 49/66] 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 50/66] 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 51/66] 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 52/66] 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 53/66] 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 54/66] 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 55/66] 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 56/66] 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 57/66] 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 58/66] 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 59/66] 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 60/66] 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 61/66] 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 62/66] 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 63/66] 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 64/66] 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 65/66] 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 66/66] 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