diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 11d8699e4..97e352f51 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.6", "3.7", "3.8", "3.9", "3.10"]
+ python: ["3.7", "3.8", "3.9", "3.10", "3.11"]
platform: [x64, x86]
exclude:
- os: ubuntu
@@ -54,15 +54,17 @@ jobs:
run: |
pip install -v .
- - name: Set Python DLL path (non Windows)
+ - name: Set Python DLL path and PYTHONHOME (non Windows)
if: ${{ matrix.os != 'windows' }}
run: |
- python -m pythonnet.find_libpython --export >> $GITHUB_ENV
+ echo PYTHONNET_PYDLL=$(python -m find_libpython) >> $GITHUB_ENV
+ echo PYTHONHOME=$(python -c 'import sys; print(sys.prefix)') >> $GITHUB_ENV
- - name: Set Python DLL path (Windows)
+ - name: Set Python DLL path and PYTHONHOME (Windows)
if: ${{ matrix.os == 'windows' }}
run: |
- python -m pythonnet.find_libpython --export | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+ 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/
diff --git a/pyproject.toml b/pyproject.toml
index b6df82f71..6151e3fff 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,6 +2,55 @@
requires = ["setuptools>=42", "wheel", "pycparser"]
build-backend = "setuptools.build_meta"
+[project]
+name = "pythonnet"
+description = ".NET and Mono integration for Python"
+license = {text = "MIT"}
+
+readme = "README.rst"
+
+dependencies = [
+ "clr_loader>=0.2.2,<0.3.0"
+]
+
+requires-python = ">=3.7, <3.12"
+
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: C#",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Operating System :: Microsoft :: Windows",
+ "Operating System :: POSIX :: Linux",
+ "Operating System :: MacOS :: MacOS X",
+]
+
+dynamic = ["version"]
+
+[[project.authors]]
+name = "The Contributors of the Python.NET Project"
+email = "pythonnet@python.org"
+
+[project.urls]
+Homepage = "https://pythonnet.github.io/"
+Sources = "https://github.com/pythonnet/pythonnet"
+
+[tool.setuptools]
+zip-safe = false
+py-modules = ["clr"]
+
+[tool.setuptools.dynamic.version]
+file = "version.txt"
+
+[tool.setuptools.packages.find]
+include = ["pythonnet*"]
+
[tool.pytest.ini_options]
xfail_strict = true
testpaths = [
diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs
index ca9164a1d..be91d7f45 100644
--- a/src/embed_tests/TestPythonEngineProperties.cs
+++ b/src/embed_tests/TestPythonEngineProperties.cs
@@ -9,6 +9,7 @@ public class TestPythonEngineProperties
[Test]
public static void GetBuildinfoDoesntCrash()
{
+ PythonEngine.Initialize();
using (Py.GIL())
{
string s = PythonEngine.BuildInfo;
@@ -21,6 +22,7 @@ public static void GetBuildinfoDoesntCrash()
[Test]
public static void GetCompilerDoesntCrash()
{
+ PythonEngine.Initialize();
using (Py.GIL())
{
string s = PythonEngine.Compiler;
@@ -34,6 +36,7 @@ public static void GetCompilerDoesntCrash()
[Test]
public static void GetCopyrightDoesntCrash()
{
+ PythonEngine.Initialize();
using (Py.GIL())
{
string s = PythonEngine.Copyright;
@@ -46,6 +49,7 @@ public static void GetCopyrightDoesntCrash()
[Test]
public static void GetPlatformDoesntCrash()
{
+ PythonEngine.Initialize();
using (Py.GIL())
{
string s = PythonEngine.Platform;
@@ -58,6 +62,7 @@ public static void GetPlatformDoesntCrash()
[Test]
public static void GetVersionDoesntCrash()
{
+ PythonEngine.Initialize();
using (Py.GIL())
{
string s = PythonEngine.Version;
@@ -91,9 +96,6 @@ public static void GetProgramNameDefault()
/// Test default behavior of PYTHONHOME. If ENVVAR is set it will
/// return the same value. If not, returns EmptyString.
///
- ///
- /// AppVeyor.yml has been update to tests with ENVVAR set.
- ///
[Test]
public static void GetPythonHomeDefault()
{
@@ -109,22 +111,19 @@ public static void GetPythonHomeDefault()
[Test]
public void SetPythonHome()
{
- // We needs to ensure that engine was started and shutdown at least once before setting dummy home.
- // Otherwise engine will not run with dummy path with random problem.
- if (!PythonEngine.IsInitialized)
- {
- PythonEngine.Initialize();
- }
-
+ PythonEngine.Initialize();
+ var pythonHomeBackup = PythonEngine.PythonHome;
PythonEngine.Shutdown();
- var pythonHomeBackup = PythonEngine.PythonHome;
+ if (pythonHomeBackup == "")
+ Assert.Inconclusive("Can't reset PythonHome to empty string, skipping");
var pythonHome = "/dummypath/";
PythonEngine.PythonHome = pythonHome;
PythonEngine.Initialize();
+ Assert.AreEqual(pythonHome, PythonEngine.PythonHome);
PythonEngine.Shutdown();
// Restoring valid pythonhome.
@@ -134,15 +133,12 @@ public void SetPythonHome()
[Test]
public void SetPythonHomeTwice()
{
- // We needs to ensure that engine was started and shutdown at least once before setting dummy home.
- // Otherwise engine will not run with dummy path with random problem.
- if (!PythonEngine.IsInitialized)
- {
- PythonEngine.Initialize();
- }
+ PythonEngine.Initialize();
+ var pythonHomeBackup = PythonEngine.PythonHome;
PythonEngine.Shutdown();
- var pythonHomeBackup = PythonEngine.PythonHome;
+ if (pythonHomeBackup == "")
+ Assert.Inconclusive("Can't reset PythonHome to empty string, skipping");
var pythonHome = "/dummypath/";
@@ -156,6 +152,26 @@ public void SetPythonHomeTwice()
PythonEngine.PythonHome = pythonHomeBackup;
}
+ [Test]
+ [Ignore("Currently buggy in Python")]
+ public void SetPythonHomeEmptyString()
+ {
+ PythonEngine.Initialize();
+
+ var backup = PythonEngine.PythonHome;
+ if (backup == "")
+ {
+ PythonEngine.Shutdown();
+ Assert.Inconclusive("Can't reset PythonHome to empty string, skipping");
+ }
+ PythonEngine.PythonHome = "";
+
+ Assert.AreEqual("", PythonEngine.PythonHome);
+
+ PythonEngine.PythonHome = backup;
+ PythonEngine.Shutdown();
+ }
+
[Test]
public void SetProgramName()
{
@@ -202,7 +218,7 @@ public void SetPythonPath()
// The list sys.path is initialized with this value on interpreter startup;
// it can be (and usually is) modified later to change the search path for loading modules.
// See https://docs.python.org/3/c-api/init.html#c.Py_GetPath
- // After PythonPath is set, then PythonEngine.PythonPath will correctly return the full search path.
+ // After PythonPath is set, then PythonEngine.PythonPath will correctly return the full search path.
PythonEngine.Shutdown();
diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj
index 708d6572e..b9533b460 100644
--- a/src/perf_tests/Python.PerformanceTests.csproj
+++ b/src/perf_tests/Python.PerformanceTests.csproj
@@ -13,7 +13,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
compile
@@ -25,7 +25,7 @@
-
+
diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs
index d42ff958a..7a9a21990 100644
--- a/src/runtime/Converter.cs
+++ b/src/runtime/Converter.cs
@@ -352,8 +352,8 @@ private static NewReference TzInfo(DateTimeKind kind)
if (kind == DateTimeKind.Unspecified) return new NewReference(Runtime.PyNone);
var offset = kind == DateTimeKind.Local ? DateTimeOffset.Now.Offset : TimeSpan.Zero;
using var tzInfoArgs = Runtime.PyTuple_New(2);
- Runtime.PyTuple_SetItem(tzInfoArgs.Borrow(), 0, Runtime.PyFloat_FromDouble(offset.Hours).Steal());
- Runtime.PyTuple_SetItem(tzInfoArgs.Borrow(), 1, Runtime.PyFloat_FromDouble(offset.Minutes).Steal());
+ Runtime.PyTuple_SetItem(tzInfoArgs.Borrow(), 0, Runtime.PyLong_FromLongLong(offset.Hours).Steal());
+ Runtime.PyTuple_SetItem(tzInfoArgs.Borrow(), 1, Runtime.PyLong_FromLongLong(offset.Minutes).Steal());
var returnValue = Runtime.PyObject_CallObject(tzInfoCtor.Value, tzInfoArgs.Borrow());
return returnValue;
}
diff --git a/src/runtime/Native/TypeOffset311.cs b/src/runtime/Native/TypeOffset311.cs
new file mode 100644
index 000000000..de5afacb9
--- /dev/null
+++ b/src/runtime/Native/TypeOffset311.cs
@@ -0,0 +1,141 @@
+
+// Auto-generated by geninterop.py.
+// DO NOT MODIFY BY HAND.
+
+// Python 3.11: ABI flags: ''
+
+// ReSharper disable InconsistentNaming
+// ReSharper disable IdentifierTypo
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+using Python.Runtime.Native;
+
+namespace Python.Runtime
+{
+ [SuppressMessage("Style", "IDE1006:Naming Styles",
+ Justification = "Following CPython",
+ Scope = "type")]
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal class TypeOffset311 : GeneratedTypeOffsets, ITypeOffsets
+ {
+ public TypeOffset311() { }
+ // Auto-generated from PyHeapTypeObject in Python.h
+ public int ob_refcnt { get; private set; }
+ public int ob_type { get; private set; }
+ public int ob_size { get; private set; }
+ public int tp_name { get; private set; }
+ public int tp_basicsize { get; private set; }
+ public int tp_itemsize { get; private set; }
+ public int tp_dealloc { get; private set; }
+ public int tp_vectorcall_offset { get; private set; }
+ public int tp_getattr { get; private set; }
+ public int tp_setattr { get; private set; }
+ public int tp_as_async { get; private set; }
+ public int tp_repr { get; private set; }
+ public int tp_as_number { get; private set; }
+ public int tp_as_sequence { get; private set; }
+ public int tp_as_mapping { get; private set; }
+ public int tp_hash { get; private set; }
+ public int tp_call { get; private set; }
+ public int tp_str { get; private set; }
+ public int tp_getattro { get; private set; }
+ public int tp_setattro { get; private set; }
+ public int tp_as_buffer { get; private set; }
+ public int tp_flags { get; private set; }
+ public int tp_doc { get; private set; }
+ public int tp_traverse { get; private set; }
+ public int tp_clear { get; private set; }
+ public int tp_richcompare { get; private set; }
+ public int tp_weaklistoffset { get; private set; }
+ public int tp_iter { get; private set; }
+ public int tp_iternext { get; private set; }
+ public int tp_methods { get; private set; }
+ public int tp_members { get; private set; }
+ public int tp_getset { get; private set; }
+ public int tp_base { get; private set; }
+ public int tp_dict { get; private set; }
+ public int tp_descr_get { get; private set; }
+ public int tp_descr_set { get; private set; }
+ public int tp_dictoffset { get; private set; }
+ public int tp_init { get; private set; }
+ public int tp_alloc { get; private set; }
+ public int tp_new { get; private set; }
+ public int tp_free { get; private set; }
+ public int tp_is_gc { get; private set; }
+ public int tp_bases { get; private set; }
+ public int tp_mro { get; private set; }
+ public int tp_cache { get; private set; }
+ public int tp_subclasses { get; private set; }
+ public int tp_weaklist { get; private set; }
+ public int tp_del { get; private set; }
+ public int tp_version_tag { get; private set; }
+ public int tp_finalize { get; private set; }
+ public int tp_vectorcall { get; private set; }
+ public int am_await { get; private set; }
+ public int am_aiter { get; private set; }
+ public int am_anext { get; private set; }
+ public int am_send { get; private set; }
+ public int nb_add { get; private set; }
+ public int nb_subtract { get; private set; }
+ public int nb_multiply { get; private set; }
+ public int nb_remainder { get; private set; }
+ public int nb_divmod { get; private set; }
+ public int nb_power { get; private set; }
+ public int nb_negative { get; private set; }
+ public int nb_positive { get; private set; }
+ public int nb_absolute { get; private set; }
+ public int nb_bool { get; private set; }
+ public int nb_invert { get; private set; }
+ public int nb_lshift { get; private set; }
+ public int nb_rshift { get; private set; }
+ public int nb_and { get; private set; }
+ public int nb_xor { get; private set; }
+ public int nb_or { get; private set; }
+ public int nb_int { get; private set; }
+ public int nb_reserved { get; private set; }
+ public int nb_float { get; private set; }
+ public int nb_inplace_add { get; private set; }
+ public int nb_inplace_subtract { get; private set; }
+ public int nb_inplace_multiply { get; private set; }
+ public int nb_inplace_remainder { get; private set; }
+ public int nb_inplace_power { get; private set; }
+ public int nb_inplace_lshift { get; private set; }
+ public int nb_inplace_rshift { get; private set; }
+ public int nb_inplace_and { get; private set; }
+ public int nb_inplace_xor { get; private set; }
+ public int nb_inplace_or { get; private set; }
+ public int nb_floor_divide { get; private set; }
+ public int nb_true_divide { get; private set; }
+ public int nb_inplace_floor_divide { get; private set; }
+ public int nb_inplace_true_divide { get; private set; }
+ public int nb_index { get; private set; }
+ public int nb_matrix_multiply { get; private set; }
+ public int nb_inplace_matrix_multiply { get; private set; }
+ public int mp_length { get; private set; }
+ public int mp_subscript { get; private set; }
+ public int mp_ass_subscript { get; private set; }
+ public int sq_length { get; private set; }
+ public int sq_concat { get; private set; }
+ public int sq_repeat { get; private set; }
+ public int sq_item { get; private set; }
+ public int was_sq_slice { get; private set; }
+ public int sq_ass_item { get; private set; }
+ public int was_sq_ass_slice { get; private set; }
+ public int sq_contains { get; private set; }
+ public int sq_inplace_concat { get; private set; }
+ public int sq_inplace_repeat { get; private set; }
+ public int bf_getbuffer { get; private set; }
+ public int bf_releasebuffer { get; private set; }
+ public int name { get; private set; }
+ public int ht_slots { get; private set; }
+ public int qualname { get; private set; }
+ public int ht_cached_keys { get; private set; }
+ public int ht_module { get; private set; }
+ public int _ht_tpname { get; private set; }
+ public int spec_cache_getitem { get; private set; }
+ }
+}
diff --git a/src/runtime/Properties/AssemblyInfo.cs b/src/runtime/Properties/AssemblyInfo.cs
index 5eaf718eb..896f2ba0e 100644
--- a/src/runtime/Properties/AssemblyInfo.cs
+++ b/src/runtime/Properties/AssemblyInfo.cs
@@ -4,5 +4,5 @@
[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
-[assembly: AssemblyVersion("2.0.28")]
-[assembly: AssemblyFileVersion("2.0.28")]
+[assembly: AssemblyVersion("2.0.29")]
+[assembly: AssemblyFileVersion("2.0.29")]
diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj
index 4677ea191..6704bd978 100644
--- a/src/runtime/Python.Runtime.csproj
+++ b/src/runtime/Python.Runtime.csproj
@@ -5,7 +5,7 @@
Python.Runtime
Python.Runtime
QuantConnect.pythonnet
- 2.0.28
+ 2.0.29
false
LICENSE
https://github.com/pythonnet/pythonnet
diff --git a/src/runtime/PythonEngine.cs b/src/runtime/PythonEngine.cs
index a93116809..eb0c98ce9 100644
--- a/src/runtime/PythonEngine.cs
+++ b/src/runtime/PythonEngine.cs
@@ -47,6 +47,14 @@ public static bool IsInitialized
get { return initialized; }
}
+ private static void EnsureInitialized()
+ {
+ if (!IsInitialized)
+ throw new InvalidOperationException(
+ "Python must be initialized for this operation"
+ );
+ }
+
/// Set to true to enable GIL debugging assistance.
public static bool DebugGIL { get; set; } = false;
@@ -96,6 +104,7 @@ public static string PythonHome
{
get
{
+ EnsureInitialized();
IntPtr p = Runtime.TryUsingDll(() => Runtime.Py_GetPythonHome());
return UcsMarshaler.PtrToPy3UnicodePy2String(p) ?? "";
}
@@ -103,10 +112,8 @@ public static string PythonHome
{
// this value is null in the beginning
Marshal.FreeHGlobal(_pythonHome);
- _pythonHome = Runtime.TryUsingDll(
- () => UcsMarshaler.Py3UnicodePy2StringtoPtr(value)
- );
- Runtime.Py_SetPythonHome(_pythonHome);
+ _pythonHome = UcsMarshaler.Py3UnicodePy2StringtoPtr(value);
+ Runtime.TryUsingDll(() => Runtime.Py_SetPythonHome(_pythonHome));
}
}
@@ -127,6 +134,10 @@ public static string PythonPath
}
}
+ public static Version MinSupportedVersion => new(3, 7);
+ public static Version MaxSupportedVersion => new(3, 11, int.MaxValue, int.MaxValue);
+ public static bool IsSupportedVersion(Version version) => version >= MinSupportedVersion && version <= MaxSupportedVersion;
+
public static string Version
{
get { return Marshal.PtrToStringAnsi(Runtime.Py_GetVersion()); }
diff --git a/src/runtime/PythonTypes/PyType.cs b/src/runtime/PythonTypes/PyType.cs
index 260800592..af796a5c5 100644
--- a/src/runtime/PythonTypes/PyType.cs
+++ b/src/runtime/PythonTypes/PyType.cs
@@ -155,6 +155,7 @@ private static StolenReference FromSpec(TypeSpec spec, PyTuple? bases = null)
using var nativeSpec = new NativeTypeSpec(spec);
var basesRef = bases is null ? default : bases.Reference;
var result = Runtime.PyType_FromSpecWithBases(in nativeSpec, basesRef);
+ // Runtime.PyErr_Print();
return result.StealOrThrow();
}
}
diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs
index 8634b85d2..a4a6acb05 100644
--- a/src/runtime/Runtime.cs
+++ b/src/runtime/Runtime.cs
@@ -667,6 +667,9 @@ internal static unsafe nint Refcount(BorrowedReference op)
[Pure]
internal static int Refcount32(BorrowedReference op) => checked((int)Refcount(op));
+ internal static void TryUsingDll(Action op) =>
+ TryUsingDll(() => { op(); return 0; });
+
///
/// Call specified function, and handle PythonDLL-related failures.
///
diff --git a/src/runtime/TypeManager.cs b/src/runtime/TypeManager.cs
index c3ae13f9e..3b75738b2 100644
--- a/src/runtime/TypeManager.cs
+++ b/src/runtime/TypeManager.cs
@@ -459,17 +459,20 @@ internal static PyType CreateMetatypeWithGCHandleOffset()
int size = Util.ReadInt32(Runtime.PyTypeType, TypeOffset.tp_basicsize)
+ IntPtr.Size // tp_clr_inst_offset
;
- var result = new PyType(new TypeSpec("clr._internal.GCOffsetBase", basicSize: size,
- new TypeSpec.Slot[]
- {
-
- },
- TypeFlags.Default | TypeFlags.HeapType | TypeFlags.HaveGC),
- bases: new PyTuple(new[] { py_type }));
-
- SetRequiredSlots(result, seen: new HashSet());
- Runtime.PyType_Modified(result);
+ var slots = new[] {
+ new TypeSpec.Slot(TypeSlotID.tp_traverse, subtype_traverse),
+ new TypeSpec.Slot(TypeSlotID.tp_clear, subtype_clear)
+ };
+ var result = new PyType(
+ new TypeSpec(
+ "clr._internal.GCOffsetBase",
+ basicSize: size,
+ slots: slots,
+ TypeFlags.Default | TypeFlags.HeapType | TypeFlags.HaveGC
+ ),
+ bases: new PyTuple(new[] { py_type })
+ );
return result;
}
@@ -601,6 +604,11 @@ internal static PyType AllocateTypeObject(string name, PyType metatype)
Util.WriteRef(type, TypeOffset.name, new NewReference(temp).Steal());
Util.WriteRef(type, TypeOffset.qualname, temp.Steal());
+ // Ensure that tp_traverse and tp_clear are always set, since their
+ // existence is enforced in newer Python versions in PyType_Ready
+ Util.WriteIntPtr(type, TypeOffset.tp_traverse, subtype_traverse);
+ Util.WriteIntPtr(type, TypeOffset.tp_clear, subtype_clear);
+
InheritSubstructs(type.Reference.DangerousGetAddress());
return type;
diff --git a/src/runtime/Types/ManagedType.cs b/src/runtime/Types/ManagedType.cs
index 2ed9d7970..97a19497c 100644
--- a/src/runtime/Types/ManagedType.cs
+++ b/src/runtime/Types/ManagedType.cs
@@ -148,8 +148,9 @@ protected static void ClearObjectDict(BorrowedReference ob)
{
BorrowedReference type = Runtime.PyObject_TYPE(ob);
int instanceDictOffset = Util.ReadInt32(type, TypeOffset.tp_dictoffset);
- Debug.Assert(instanceDictOffset > 0);
- Runtime.Py_CLEAR(ob, instanceDictOffset);
+ // Debug.Assert(instanceDictOffset > 0);
+ if (instanceDictOffset > 0)
+ Runtime.Py_CLEAR(ob, instanceDictOffset);
}
protected static BorrowedReference GetObjectDict(BorrowedReference ob)
diff --git a/tests/conftest.py b/tests/conftest.py
index 89db46eca..6abd2c34d 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -93,6 +93,15 @@ def pytest_configure(config):
check_call(build_cmd)
+ import os
+ os.environ["PYTHONNET_RUNTIME"] = runtime_opt
+ for k, v in runtime_params.items():
+ os.environ[f"PYTHONNET_{runtime_opt.upper()}_{k.upper()}"] = v
+
+ import clr
+
+ sys.path.append(str(bin_path))
+ clr.AddReference("Python.Test")
def pytest_unconfigure(config):
diff --git a/tools/geninterop/geninterop.py b/tools/geninterop/geninterop.py
old mode 100644
new mode 100755
index 0c80c1904..78e4d45c2
--- a/tools/geninterop/geninterop.py
+++ b/tools/geninterop/geninterop.py
@@ -13,40 +13,26 @@
- clang
"""
-from __future__ import print_function
-
-import logging
import os
+import shutil
import sys
import sysconfig
import subprocess
-if sys.version_info.major > 2:
- from io import StringIO
-else:
- from StringIO import StringIO
-
+from io import StringIO
+from pathlib import Path
from pycparser import c_ast, c_parser
-_log = logging.getLogger()
-logging.basicConfig(level=logging.DEBUG)
-
-PY_MAJOR = sys.version_info[0]
-PY_MINOR = sys.version_info[1]
-
# rename some members from their C name when generating the C#
_typeoffset_member_renames = {
"ht_name": "name",
- "ht_qualname": "qualname"
+ "ht_qualname": "qualname",
+ "getitem": "spec_cache_getitem",
}
def _check_output(*args, **kwargs):
- """Check output wrapper for py2/py3 compatibility"""
- output = subprocess.check_output(*args, **kwargs)
- if PY_MAJOR == 2:
- return output
- return output.decode("ascii")
+ return subprocess.check_output(*args, **kwargs, encoding="utf8")
class AstParser(object):
@@ -92,7 +78,7 @@ def visit(self, node):
self.visit_identifier(node)
def visit_ast(self, ast):
- for name, node in ast.children():
+ for _name, node in ast.children():
self.visit(node)
def visit_typedef(self, typedef):
@@ -113,7 +99,7 @@ def visit_struct(self, struct):
self.visit(decl)
self._struct_members_stack.pop(0)
self._struct_stack.pop(0)
- elif self._ptr_decl_depth:
+ elif self._ptr_decl_depth or self._struct_members_stack:
# the struct is empty, but add it as a member to the current
# struct as the current member maybe a pointer to it.
self._add_struct_member(struct.name)
@@ -141,7 +127,8 @@ def _add_struct_member(self, type_name):
current_struct = self._struct_stack[0]
member_name = self._struct_members_stack[0]
struct_members = self._struct_members.setdefault(
- self._get_struct_name(current_struct), [])
+ self._get_struct_name(current_struct), []
+ )
# get the node associated with this type
node = None
@@ -179,7 +166,6 @@ def _get_struct_name(self, node):
class Writer(object):
-
def __init__(self):
self._stream = StringIO()
@@ -193,34 +179,47 @@ def to_string(self):
return self._stream.getvalue()
-def preprocess_python_headers():
+def preprocess_python_headers(*, cc=None, include_py=None):
"""Return Python.h pre-processed, ready for parsing.
Requires clang.
"""
- fake_libc_include = os.path.join(os.path.dirname(__file__),
- "fake_libc_include")
+ this_path = Path(__file__).parent
+
+ fake_libc_include = this_path / "fake_libc_include"
include_dirs = [fake_libc_include]
- include_py = sysconfig.get_config_var("INCLUDEPY")
+ if cc is None:
+ cc = shutil.which("clang")
+ if cc is None:
+ cc = shutil.which("gcc")
+ if cc is None:
+ raise RuntimeError("No suitable C compiler found, need clang or gcc")
+
+ if include_py is None:
+ include_py = sysconfig.get_config_var("INCLUDEPY")
+ include_py = Path(include_py)
+
include_dirs.append(include_py)
- include_args = [c for p in include_dirs for c in ["-I", p]]
+ include_args = [c for p in include_dirs for c in ["-I", str(p)]]
+ # fmt: off
defines = [
"-D", "__attribute__(x)=",
"-D", "__inline__=inline",
"-D", "__asm__=;#pragma asm",
"-D", "__int64=long long",
- "-D", "_POSIX_THREADS"
+ "-D", "_POSIX_THREADS",
]
- if os.name == 'nt':
+ if sys.platform == "win32":
defines.extend([
"-D", "__inline=inline",
"-D", "__ptr32=",
"-D", "__ptr64=",
"-D", "__declspec(x)=",
])
+ #fmt: on
if hasattr(sys, "abiflags"):
if "d" in sys.abiflags:
@@ -228,8 +227,8 @@ def preprocess_python_headers():
if "u" in sys.abiflags:
defines.extend(("-D", "PYTHON_WITH_WIDE_UNICODE"))
- python_h = os.path.join(include_py, "Python.h")
- cmd = ["clang", "-pthread"] + include_args + defines + ["-E", python_h]
+ python_h = include_py / "Python.h"
+ cmd = [cc, "-pthread"] + include_args + defines + ["-E", str(python_h)]
# normalize as the parser doesn't like windows line endings.
lines = []
@@ -240,16 +239,13 @@ def preprocess_python_headers():
return "\n".join(lines)
-
-def gen_interop_head(writer):
+def gen_interop_head(writer, version, abi_flags):
filename = os.path.basename(__file__)
- abi_flags = getattr(sys, "abiflags", "").replace("m", "")
- py_ver = "{0}.{1}".format(PY_MAJOR, PY_MINOR)
- class_definition = """
-// Auto-generated by %s.
+ class_definition = f"""
+// Auto-generated by {filename}.
// DO NOT MODIFY BY HAND.
-// Python %s: ABI flags: '%s'
+// Python {".".join(version[:2])}: ABI flags: '{abi_flags}'
// ReSharper disable InconsistentNaming
// ReSharper disable IdentifierTypo
@@ -261,7 +257,7 @@ def gen_interop_head(writer):
using Python.Runtime.Native;
namespace Python.Runtime
-{""" % (filename, py_ver, abi_flags)
+{{"""
writer.extend(class_definition)
@@ -271,25 +267,24 @@ def gen_interop_tail(writer):
writer.extend(tail)
-def gen_heap_type_members(parser, writer, type_name = None):
+def gen_heap_type_members(parser, writer, type_name):
"""Generate the TypeOffset C# class"""
members = parser.get_struct_members("PyHeapTypeObject")
- type_name = type_name or "TypeOffset{0}{1}".format(PY_MAJOR, PY_MINOR)
- class_definition = """
+ class_definition = f"""
[SuppressMessage("Style", "IDE1006:Naming Styles",
Justification = "Following CPython",
Scope = "type")]
[StructLayout(LayoutKind.Sequential)]
- internal class {0} : GeneratedTypeOffsets, ITypeOffsets
+ internal class {type_name} : GeneratedTypeOffsets, ITypeOffsets
{{
- public {0}() {{ }}
+ public {type_name}() {{ }}
// Auto-generated from PyHeapTypeObject in Python.h
-""".format(type_name)
+"""
# All the members are sizeof(void*) so we don't need to do any
# extra work to determine the size based on the type.
- for name, tpy in members:
+ for name, _type in members:
name = _typeoffset_member_renames.get(name, name)
class_definition += " public int %s { get; private set; }\n" % name
@@ -304,17 +299,18 @@ def gen_structure_code(parser, writer, type_name, indent):
return False
out = writer.append
out(indent, "[StructLayout(LayoutKind.Sequential)]")
- out(indent, "internal struct %s" % type_name)
+ out(indent, f"internal struct {type_name}")
out(indent, "{")
- for name, tpy in members:
- out(indent + 1, "public IntPtr %s;" % name)
+ for name, _type in members:
+ out(indent + 1, f"public IntPtr {name};")
out(indent, "}")
out()
return True
-def main():
+
+def main(*, cc=None, include_py=None, version=None, out=None):
# preprocess Python.h and build the AST
- python_h = preprocess_python_headers()
+ python_h = preprocess_python_headers(cc=cc, include_py=include_py)
parser = c_parser.CParser()
ast = parser.parse(python_h)
@@ -323,21 +319,47 @@ def main():
ast_parser.visit(ast)
writer = Writer()
+
+ if include_py and not version:
+ raise RuntimeError("If the include path is overridden, version must be "
+ "defined"
+ )
+
+ if version:
+ version = version.split('.')
+ else:
+ version = sys.version_info
+
# generate the C# code
- offsets_type_name = "NativeTypeOffset" if len(sys.argv) > 1 else None
- gen_interop_head(writer)
+ abi_flags = getattr(sys, "abiflags", "").replace("m", "")
+ gen_interop_head(writer, version, abi_flags)
- gen_heap_type_members(ast_parser, writer, type_name = offsets_type_name)
+ type_name = f"TypeOffset{version[0]}{version[1]}{abi_flags}"
+ gen_heap_type_members(ast_parser, writer, type_name)
gen_interop_tail(writer)
interop_cs = writer.to_string()
- if len(sys.argv) > 1:
- with open(sys.argv[1], "w") as fh:
- fh.write(interop_cs)
- else:
+ if not out or out == "-":
print(interop_cs)
+ else:
+ with open(out, "w") as fh:
+ fh.write(interop_cs)
if __name__ == "__main__":
- sys.exit(main())
+ import argparse
+
+ a = argparse.ArgumentParser("Interop file generator for Python.NET")
+ a.add_argument("--cc", help="C compiler to use, either clang or gcc")
+ a.add_argument("--include-py", help="Include path of Python")
+ a.add_argument("--version", help="Python version")
+ a.add_argument("--out", help="Output path", default="-")
+ args = a.parse_args()
+
+ sys.exit(main(
+ cc=args.cc,
+ include_py=args.include_py,
+ out=args.out,
+ version=args.version
+ ))