diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b5dd1816..9f0f212e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,9 @@ or the DLL must be loaded in advance. This must be done before calling any other - BREAKING: disabled implicit conversion from C# enums to Python `int` and back. One must now either use enum members (e.g. `MyEnum.Option`), or use enum constructor (e.g. `MyEnum(42)` or `MyEnum(42, True)` when `MyEnum` does not have a member with value 42). +- BREAKING: disabled implicit conversion from Python objects implementing sequence protocol to +.NET arrays when the target .NET type is `System.Object`. The conversion is still attempted when the +target type is a `System.Array`. - Sign Runtime DLL with a strong name - Implement loading through `clr_loader` instead of the included `ClrModule`, enables support for .NET Core diff --git a/Directory.Build.props b/Directory.Build.props index 8c5b53685..72a519f4f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ Python.NET 10.0 false - $([System.IO.File]::ReadAllText("version.txt")) + $([System.IO.File]::ReadAllText("version.txt").Trim()) $(FullVersion.Split('-', 2)[0]) $(FullVersion.Split('-', 2)[1]) diff --git a/MANIFEST.in b/MANIFEST.in index 6458d5778..71473c2c3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ graft src/runtime prune src/runtime/obj prune src/runtime/bin +include src/pythonnet.snk include Directory.Build.* include pythonnet.sln include version.txt diff --git a/README.rst b/README.rst index c2c2d53e1..ba4a56fbf 100644 --- a/README.rst +++ b/README.rst @@ -50,6 +50,8 @@ Embedding Python in .NET (internal, derived from ``MissingMethodException``) upon calling ``Initialize``. Typical values are ``python38.dll`` (Windows), ``libpython3.8.dylib`` (Mac), ``libpython3.8.so`` (most other Unix-like operating systems). +- Then call ``PythonEngine.Initialize()``. If you plan to use Python objects from + multiple threads, also call ``PythonEngine.BeginAllowThreads()``. - All calls to python should be inside a ``using (Py.GIL()) {/* Your code here */}`` block. - Import python modules using ``dynamic mod = Py.Import("mod")``, then diff --git a/pyproject.toml b/pyproject.toml index 5ee89d3b7..39c7c14fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ license = {text = "MIT"} readme = "README.rst" dependencies = [ - "clr_loader>=0.1.7" + "clr_loader>=0.2.2,<0.3.0" ] classifiers = [ diff --git a/pythonnet.sln b/pythonnet.sln index eb97cfbd0..d1a47892e 100644 --- a/pythonnet.sln +++ b/pythonnet.sln @@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F CHANGELOG.md = CHANGELOG.md LICENSE = LICENSE README.rst = README.rst + version.txt = version.txt EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CI", "CI", "{D301657F-5EAF-4534-B280-B858D651B2E5}" diff --git a/pythonnet/__init__.py b/pythonnet/__init__.py index 9876a0bec..2cde01233 100644 --- a/pythonnet/__init__.py +++ b/pythonnet/__init__.py @@ -1,12 +1,12 @@ import sys from pathlib import Path -from typing import Dict, Optional, Union +from typing import Dict, Optional, Union, Any import clr_loader __all__ = ["set_runtime", "set_runtime_from_env", "load"] _RUNTIME: Optional[clr_loader.Runtime] = None -_LOADER_ASSEMBLY: Optional[clr_loader.wrappers.Assembly] = None +_LOADER_ASSEMBLY: Optional[clr_loader.Assembly] = None _LOADED: bool = False @@ -27,6 +27,15 @@ def set_runtime(runtime: Union[clr_loader.Runtime, str], **params: str) -> None: _RUNTIME = runtime +def get_runtime_info() -> Optional[clr_loader.RuntimeInfo]: + """Retrieve information on the configured runtime""" + + if _RUNTIME is None: + return None + else: + return _RUNTIME.info() + + def _get_params_from_env(prefix: str) -> Dict[str, str]: from os import environ @@ -43,9 +52,11 @@ def _get_params_from_env(prefix: str) -> Dict[str, str]: def _create_runtime_from_spec( - spec: str, params: Optional[Dict[str, str]] = None + spec: str, params: Optional[Dict[str, Any]] = None ) -> clr_loader.Runtime: + was_default = False if spec == "default": + was_default = True if sys.platform == "win32": spec = "netfx" else: @@ -53,14 +64,29 @@ def _create_runtime_from_spec( params = params or _get_params_from_env(spec) - if spec == "netfx": - return clr_loader.get_netfx(**params) - elif spec == "mono": - return clr_loader.get_mono(**params) - elif spec == "coreclr": - return clr_loader.get_coreclr(**params) - else: - raise RuntimeError(f"Invalid runtime name: '{spec}'") + try: + if spec == "netfx": + return clr_loader.get_netfx(**params) + elif spec == "mono": + return clr_loader.get_mono(**params) + elif spec == "coreclr": + return clr_loader.get_coreclr(**params) + else: + raise RuntimeError(f"Invalid runtime name: '{spec}'") + except Exception as exc: + if was_default: + raise RuntimeError( + f"""Failed to create a default .NET runtime, which would + have been "{spec}" on this system. Either install a + compatible runtime or configure it explicitly via + `set_runtime` or the `PYTHONNET_*` environment variables + (see set_runtime_from_env).""" + ) from exc + else: + raise RuntimeError( + f"""Failed to create a .NET runtime ({spec}) using the + parameters {params}.""" + ) from exc def set_runtime_from_env() -> None: @@ -85,9 +111,7 @@ def set_runtime_from_env() -> None: set_runtime(runtime) -def load( - runtime: Union[clr_loader.Runtime, str, None] = None, **params: str -) -> None: +def load(runtime: Union[clr_loader.Runtime, str, None] = None, **params: str) -> None: """Load Python.NET in the specified runtime The same parameters as for `set_runtime` can be used. By default, @@ -109,9 +133,9 @@ def load( dll_path = Path(__file__).parent / "runtime" / "Python.Runtime.dll" - _LOADER_ASSEMBLY = _RUNTIME.get_assembly(str(dll_path)) + _LOADER_ASSEMBLY = assembly = _RUNTIME.get_assembly(str(dll_path)) + func = assembly.get_function("Python.Runtime.Loader.Initialize") - func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Initialize"] if func(b"") != 0: raise RuntimeError("Failed to initialize Python.Runtime.dll") @@ -125,12 +149,12 @@ def unload() -> None: global _RUNTIME, _LOADER_ASSEMBLY if _LOADER_ASSEMBLY is not None: - func = _LOADER_ASSEMBLY["Python.Runtime.Loader.Shutdown"] + func = _LOADER_ASSEMBLY.get_function("Python.Runtime.Loader.Shutdown") if func(b"full_shutdown") != 0: raise RuntimeError("Failed to call Python.NET shutdown") _LOADER_ASSEMBLY = None if _RUNTIME is not None: - # TODO: Add explicit `close` to clr_loader + _RUNTIME.shutdown() _RUNTIME = None diff --git a/src/embed_tests/Codecs.cs b/src/embed_tests/Codecs.cs index c9e83f03a..be416bc15 100644 --- a/src/embed_tests/Codecs.cs +++ b/src/embed_tests/Codecs.cs @@ -285,7 +285,6 @@ public void IterableDecoderTest() } // regression for https://github.com/pythonnet/pythonnet/issues/1427 - [Ignore("https://github.com/pythonnet/pythonnet/issues/1256")] [Test] public void PythonRegisteredDecoder_NoStackOverflowOnSystemType() { diff --git a/src/runtime/AssemblyManager.cs b/src/runtime/AssemblyManager.cs index d09d2d76e..a8bbd1f6c 100644 --- a/src/runtime/AssemblyManager.cs +++ b/src/runtime/AssemblyManager.cs @@ -334,7 +334,7 @@ public static bool IsValidNamespace(string name) } /// - /// Returns an IEnumerable containing the namepsaces exported + /// Returns an enumerable collection containing the namepsaces exported /// by loaded assemblies in the current app domain. /// public static IEnumerable GetNamespaces () diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index e1820f05b..2e0edc3b5 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -389,11 +389,6 @@ internal static bool ToManagedValue(BorrowedReference value, Type obType, return true; } - if (Runtime.PySequence_Check(value)) - { - return ToArray(value, typeof(object[]), out result, setError); - } - result = new PyObject(value); return true; } diff --git a/src/runtime/Native/ITypeOffsets.cs b/src/runtime/Native/ITypeOffsets.cs index 2c4fdf59a..7d71b4b91 100644 --- a/src/runtime/Native/ITypeOffsets.cs +++ b/src/runtime/Native/ITypeOffsets.cs @@ -22,6 +22,7 @@ interface ITypeOffsets int nb_true_divide { get; } int nb_and { get; } int nb_int { get; } + int nb_float { get; } int nb_or { get; } int nb_xor { get; } int nb_lshift { get; } @@ -30,6 +31,7 @@ interface ITypeOffsets int nb_invert { get; } int nb_inplace_add { get; } int nb_inplace_subtract { get; } + int nb_index { get; } int ob_size { get; } int ob_type { get; } int qualname { get; } diff --git a/src/runtime/Native/LibraryLoader.cs b/src/runtime/Native/LibraryLoader.cs index 6d67bea61..05f960481 100644 --- a/src/runtime/Native/LibraryLoader.cs +++ b/src/runtime/Native/LibraryLoader.cs @@ -137,17 +137,17 @@ public IntPtr GetFunction(IntPtr hModule, string procedureName) static IntPtr[] GetAllModules() { - var self = Process.GetCurrentProcess().Handle; + using var self = Process.GetCurrentProcess(); uint bytes = 0; var result = new IntPtr[0]; - if (!EnumProcessModules(self, result, bytes, out var needsBytes)) + if (!EnumProcessModules(self.Handle, result, bytes, out var needsBytes)) throw new Win32Exception(); while (bytes < needsBytes) { bytes = needsBytes; result = new IntPtr[bytes / IntPtr.Size]; - if (!EnumProcessModules(self, result, bytes, out needsBytes)) + if (!EnumProcessModules(self.Handle, result, bytes, out needsBytes)) throw new Win32Exception(); } return result.Take((int)(needsBytes / IntPtr.Size)).ToArray(); diff --git a/src/runtime/Native/TypeOffset.cs b/src/runtime/Native/TypeOffset.cs index 59c5c3ee0..803b86bfa 100644 --- a/src/runtime/Native/TypeOffset.cs +++ b/src/runtime/Native/TypeOffset.cs @@ -31,12 +31,14 @@ static partial class TypeOffset internal static int nb_or { get; private set; } internal static int nb_xor { get; private set; } internal static int nb_int { get; private set; } + internal static int nb_float { get; private set; } internal static int nb_lshift { get; private set; } internal static int nb_rshift { get; private set; } internal static int nb_remainder { get; private set; } internal static int nb_invert { get; private set; } internal static int nb_inplace_add { get; private set; } internal static int nb_inplace_subtract { get; private set; } + internal static int nb_index { get; private set; } internal static int ob_size { get; private set; } internal static int ob_type { get; private set; } internal static int qualname { get; private set; } diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index fad5b9da8..5072f23cd 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -23,6 +23,8 @@ true snupkg + True + ..\pythonnet.snk true diff --git a/src/runtime/README.md b/src/runtime/README.md index ab75280ed..6c8eb2a6c 100644 --- a/src/runtime/README.md +++ b/src/runtime/README.md @@ -8,6 +8,9 @@ integrate Python engine and use Python libraries. (internal, derived from `MissingMethodException`) upon calling `Initialize`. Typical values are `python38.dll` (Windows), `libpython3.8.dylib` (Mac), `libpython3.8.so` (most other *nix). Full path may be required. +- Then call `PythonEngine.Initialize()`. If you plan to [use Python objects from + multiple threads](https://github.com/pythonnet/pythonnet/wiki/Threading), + also call `PythonEngine.BeginAllowThreads()`. - All calls to Python should be inside a `using (Py.GIL()) {/* Your code here */}` block. - Import python modules using `dynamic mod = Py.Import("mod")`, then diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index 88a8ed173..4dc904f43 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -99,6 +99,7 @@ internal static int GetRun() internal static bool HostedInPython; internal static bool ProcessIsTerminating; + /// /// Initialize the runtime... /// /// Always call this method from the Main thread. After the diff --git a/src/runtime/Types/ArrayObject.cs b/src/runtime/Types/ArrayObject.cs index bda717e56..b95934baf 100644 --- a/src/runtime/Types/ArrayObject.cs +++ b/src/runtime/Types/ArrayObject.cs @@ -520,7 +520,7 @@ static IntPtr AllocateBufferProcs() #endregion /// - /// + /// /// public static void InitializeSlots(PyType type, ISet initialized, SlotsHolder slotsHolder) { diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 1e3c325cc..7296a1900 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -529,6 +529,37 @@ static NewReference tp_call_impl(BorrowedReference ob, BorrowedReference args, B return callBinder.Invoke(ob, args, kw); } + static NewReference DoConvert(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + using var python = self.inst.ToPython(); + return python.NewReferenceOrNull(); + } + + static NewReference DoConvertInt(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + return Runtime.PyLong_FromLongLong(Convert.ToInt64(self.inst)); + } + + static NewReference DoConvertUInt(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + return Runtime.PyLong_FromUnsignedLongLong(Convert.ToUInt64(self.inst)); + } + + static NewReference DoConvertBooleanInt(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + return Runtime.PyInt_FromInt32((bool)self.inst ? 1 : 0); + } + + static NewReference DoConvertFloat(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + return Runtime.PyFloat_FromDouble(Convert.ToDouble(self.inst)); + } + static IEnumerable GetCallImplementations(Type type) => type.GetMethods(BindingFlags.Public | BindingFlags.Instance) .Where(m => m.Name == "__call__"); @@ -564,6 +595,32 @@ public virtual void InitializeSlots(BorrowedReference pyType, SlotsHolder slotsH { TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_length, new Interop.B_P(MpLengthSlot.impl), slotsHolder); } + + switch (Type.GetTypeCode(type.Value)) + { + case TypeCode.Boolean: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_int, new Interop.B_N(DoConvertInt), slotsHolder); + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_index, new Interop.B_N(DoConvertInt), slotsHolder); + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); + break; + case TypeCode.Byte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_int, new Interop.B_N(DoConvertUInt), slotsHolder); + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_index, new Interop.B_N(DoConvertUInt), slotsHolder); + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); + break; + case TypeCode.Double: + case TypeCode.Single: + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_int, new Interop.B_N(DoConvertInt), slotsHolder); + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); + break; + } } public virtual bool HasCustomNew() => this.GetType().GetMethod("tp_new") is not null; diff --git a/src/runtime/Types/EventBinding.cs b/src/runtime/Types/EventBinding.cs index 9eb2382ec..5c47d4aab 100644 --- a/src/runtime/Types/EventBinding.cs +++ b/src/runtime/Types/EventBinding.cs @@ -70,7 +70,6 @@ public static NewReference nb_inplace_subtract(BorrowedReference ob, BorrowedRef return new NewReference(ob); } - /// public static int tp_descr_set(BorrowedReference ds, BorrowedReference ob, BorrowedReference val) => EventObject.tp_descr_set(ds, ob, val); diff --git a/src/runtime/Types/OperatorMethod.cs b/src/runtime/Types/OperatorMethod.cs index 0c6127f65..e3cc23370 100644 --- a/src/runtime/Types/OperatorMethod.cs +++ b/src/runtime/Types/OperatorMethod.cs @@ -53,6 +53,8 @@ static OperatorMethod() ["op_UnaryPlus"] = new SlotDefinition("__pos__", TypeOffset.nb_positive), ["__int__"] = new SlotDefinition("__int__", TypeOffset.nb_int), + ["__float__"] = new SlotDefinition("__float__", TypeOffset.nb_float), + ["__index__"] = new SlotDefinition("__index__", TypeOffset.nb_index), }; ComparisonOpMap = new Dictionary { diff --git a/src/runtime/Types/ReflectedClrType.cs b/src/runtime/Types/ReflectedClrType.cs index 2e8f95924..b787939be 100644 --- a/src/runtime/Types/ReflectedClrType.cs +++ b/src/runtime/Types/ReflectedClrType.cs @@ -22,7 +22,6 @@ internal ReflectedClrType(BorrowedReference original) : base(original) { } /// /// /// Returned might be partially initialized. - /// If you need fully initialized type, use /// public static ReflectedClrType GetOrCreate(Type type) { diff --git a/src/runtime/Util/OpsHelper.cs b/src/runtime/Util/OpsHelper.cs index 12d0c7aa6..7c35f27eb 100644 --- a/src/runtime/Util/OpsHelper.cs +++ b/src/runtime/Util/OpsHelper.cs @@ -82,10 +82,14 @@ internal static class EnumOps where T : Enum { [ForbidPythonThreads] #pragma warning disable IDE1006 // Naming Styles - must match Python - public static PyInt __int__(T value) + public static PyInt __index__(T value) #pragma warning restore IDE1006 // Naming Styles => typeof(T).GetEnumUnderlyingType() == typeof(UInt64) ? new PyInt(Convert.ToUInt64(value)) : new PyInt(Convert.ToInt64(value)); + [ForbidPythonThreads] +#pragma warning disable IDE1006 // Naming Styles - must match Python + public static PyInt __int__(T value) => __index__(value); +#pragma warning restore IDE1006 // Naming Styles } } diff --git a/tests/conftest.py b/tests/conftest.py index fcd1d224a..e61e3680e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -53,10 +53,12 @@ def pytest_configure(config): runtime_params = {} if runtime_opt == "coreclr": - fw = "net6.0" - runtime_params["runtime_config"] = str( - bin_path / "Python.Test.runtimeconfig.json" - ) + # This is optional now: + # + # fw = "net6.0" + # runtime_params["runtime_config"] = str( + # bin_path / "Python.Test.runtimeconfig.json" + # ) collect_ignore.append("domain_tests/test_domain_reload.py") else: domain_tests_dir = cwd / "domain_tests" diff --git a/tests/test_conversion.py b/tests/test_conversion.py index a5b4c6fd9..bb686dd52 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -577,6 +577,13 @@ class Foo(object): ob.ObjectField = Foo assert ob.ObjectField == Foo + class PseudoSeq: + def __getitem__(self, idx): + return 0 + + ob.ObjectField = PseudoSeq() + assert ob.ObjectField.__class__.__name__ == "PseudoSeq" + def test_null_conversion(): """Test null conversion.""" @@ -713,3 +720,35 @@ def test_intptr_construction(): with pytest.raises(OverflowError): UIntPtr(v) +def test_explicit_conversion(): + from operator import index + from System import ( + Int64, UInt64, Int32, UInt32, Int16, UInt16, Byte, SByte, Boolean + ) + from System import Double, Single + + assert int(Boolean(False)) == 0 + assert int(Boolean(True)) == 1 + + for t in [UInt64, UInt32, UInt16, Byte]: + assert index(t(127)) == 127 + assert int(t(127)) == 127 + assert float(t(127)) == 127.0 + + for t in [Int64, Int32, Int16, SByte]: + assert index(t(127)) == 127 + assert index(t(-127)) == -127 + assert int(t(127)) == 127 + assert int(t(-127)) == -127 + assert float(t(127)) == 127.0 + assert float(t(-127)) == -127.0 + + assert int(Int64(Int64.MaxValue)) == 2**63 - 1 + assert int(Int64(Int64.MinValue)) == -2**63 + assert int(UInt64(UInt64.MaxValue)) == 2**64 - 1 + + for t in [Single, Double]: + assert float(t(0.125)) == 0.125 + assert int(t(123.4)) == 123 + with pytest.raises(TypeError): + index(t(123.4)) diff --git a/tests/test_module.py b/tests/test_module.py index 4e1a1a1ef..ddfa7bb36 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -31,6 +31,7 @@ def test_import_clr(): def test_version_clr(): import clr assert clr.__version__ >= "3.0.0" + assert clr.__version__[-1] != "\n" def test_preload_var(): diff --git a/version.txt b/version.txt index dc72b3783..916e2438f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.0-rc4 +3.0.0-rc5 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy