Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ARM.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:

- name: Install dependencies
run: |
pip install --upgrade -r requirements.txt
pip install -r requirements.txt
pip install pytest numpy # for tests

- name: Build and Install
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [windows, ubuntu, macos]
os: [windows, ubuntu]
python: ["3.7", "3.8", "3.9", "3.10", "3.11"]
platform: [x64, x86]
exclude:
Expand Down
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +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.
## [3.0.2](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.2) - 2023-08-29

### Fixed

- Fixed error occuring when inheriting a class containing a virtual generic method
- Make a second call to `pythonnet.load` a no-op, as it was intended
- Added support for multiple inheritance when inheriting from a class and/or multiple interfaces
- Fixed error occuring when calling `GetBuffer` for anything other than `PyBUF.SIMPLE`
- Bumped `clr_loader` dependency to incorporate patches

## [3.0.1](https://github.com/pythonnet/pythonnet/releases/tag/v3.0.1) - 2022-11-03

Expand Down
3 changes: 3 additions & 0 deletions doc/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
41 changes: 26 additions & 15 deletions doc/source/dotnet.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -42,28 +42,39 @@ application.

Before interacting with any of the objects or APIs provided by the
``Python.Runtime`` namespace, calling code must have acquired the Python
global interpreter lock by calling the ``PythonEngine.AcquireLock``
method. The only exception to this rule is the
``PythonEngine.Initialize`` method, which may be called at startup
without having acquired the GIL.

When finished using Python APIs, managed code must call a corresponding
``PythonEngine.ReleaseLock`` to release the GIL and allow other threads
to use Python.

A ``using`` statement may be used to acquire and release the GIL:
global interpreter lock by ``using'' ``Py.GIL()``. The only exception to
this rule is the ``PythonEngine.Initialize`` method, which may be called
at startup without having acquired the GIL. The GIL is released again
by disposing the return value of `Py.GIL()`:

.. code:: csharp

using (Py.GIL())
{
PythonEngine.Exec("doStuff()");
}

// or
{
using var _ = Py.GIL()
PythonEngine.Exec("doStuff()");
}

// or
var gil = Py.GIL();
try
{
PythonEngine.Exec("doStuff()");
}
finally
{
gil.Dispose();
}

The AcquireLock and ReleaseLock methods are thin wrappers over the
unmanaged ``PyGILState_Ensure`` and ``PyGILState_Release`` functions
from the Python API, and the documentation for those APIs applies to the
managed versions.
The ``Py.GIL()'' object is a thin wrapper over the unmanaged
``PyGILState_Ensure`` (on construction) and ``PyGILState_Release`` (on
disposal) functions from the Python API, and the documentation for those
APIs applies to the managed versions.

Passing C# Objects to the Python Engine
---------------------------------------
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ license = {text = "MIT"}
readme = "README.rst"

dependencies = [
"clr_loader>=0.2.5,<0.3.0"
"clr_loader>=0.2.6,<0.3.0"
]

requires-python = ">=3.7, <3.12"
Expand Down Expand Up @@ -50,6 +50,7 @@ file = "version.txt"

[tool.setuptools.packages.find]
include = ["pythonnet*"]
exclude = [".gitignore"]

[tool.pytest.ini_options]
xfail_strict = true
Expand Down
6 changes: 5 additions & 1 deletion pythonnet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ def load(runtime: Union[clr_loader.Runtime, str, None] = None, **params: str) ->

The same parameters as for `set_runtime` can be used. By default,
`set_default_runtime` is called if no environment has been set yet and no
parameters are passed."""
parameters are passed.

After a successful call, further invocations will return immediately."""
global _LOADED, _LOADER_ASSEMBLY

if _LOADED:
Expand All @@ -142,6 +144,8 @@ def load(runtime: Union[clr_loader.Runtime, str, None] = None, **params: str) ->

if func(b"") != 0:
raise RuntimeError("Failed to initialize Python.Runtime.dll")

_LOADED = True

import atexit

Expand Down
5 changes: 2 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ codecov

wheel
pycparser
setuptools
clr-loader
clr-loader==0.2.*

# Discover libpython
find_libpython
find_libpython==0.3.*
36 changes: 36 additions & 0 deletions src/embed_tests/TestPyBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>()));
Assert.That(buf.Shape, Is.EqualTo(shape.As<long[]>()));
Assert.That(buf.Strides, Is.EqualTo(strides.As<long[]>()));
Assert.That(buf.IsContiguous(BufferOrderStyle.C), Is.EqualTo(contiguous.As<bool>()));
});
}

[MethodImpl(MethodImplOptions.NoInlining)]
static void MakeBufAndLeak(PyObject bufProvider)
{
Expand All @@ -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;
}
}
}
}
}
1 change: 1 addition & 0 deletions src/python_tests_runner/PythonTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ static IEnumerable<string[]> PythonTestCases()
// Add the test that you want to debug here.
yield return new[] { "test_indexer", "test_boolean_indexer" };
yield return new[] { "test_delegate", "test_bool_delegate" };
yield return new[] { "test_subclass", "test_implement_interface_and_class" };
}

/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions src/runtime/PythonTypes/PyBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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();
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/StateSerialization/MaybeType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal struct MaybeType : ISerializable
const string SerializationName = "n";
readonly string name;
readonly Type type;

public string DeletedMessage
{
get
Expand Down Expand Up @@ -61,4 +61,4 @@ public void GetObjectData(SerializationInfo serializationInfo, StreamingContext
serializationInfo.AddValue(SerializationName, name);
}
}
}
}
17 changes: 5 additions & 12 deletions src/runtime/TypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ static PyTuple GetBaseTypeTuple(Type clrType)
return new PyTuple(bases);
}

internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedReference py_base_type, BorrowedReference dictRef)
internal static NewReference CreateSubType(BorrowedReference py_name, ClassBase py_base_type, IList<Type> interfaces, BorrowedReference dictRef)
{
// Utility to create a subtype of a managed type with the ability for the
// a python subtype able to override the managed implementation
Expand Down Expand Up @@ -415,17 +415,10 @@ internal static NewReference CreateSubType(BorrowedReference py_name, BorrowedRe
}

// create the new managed type subclassing the base managed type
if (ManagedType.GetManagedObject(py_base_type) is ClassBase baseClass)
{
return ReflectedClrType.CreateSubclass(baseClass, name,
ns: (string?)namespaceStr,
assembly: (string?)assembly,
dict: dictRef);
}
else
{
return Exceptions.RaiseTypeError("invalid base class, expected CLR class type");
}
return ReflectedClrType.CreateSubclass(py_base_type, interfaces, name,
ns: (string?)namespaceStr,
assembly: (string?)assembly,
dict: dictRef);
}

internal static IntPtr WriteMethodDef(IntPtr mdef, IntPtr name, IntPtr func, PyMethodFlags flags, IntPtr doc)
Expand Down
10 changes: 7 additions & 3 deletions src/runtime/Types/ClassDerived.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ internal static NewReference ToPython(IPythonDerivedType obj)
/// </summary>
internal static Type CreateDerivedType(string name,
Type baseType,
IList<Type> typeInterfaces,
BorrowedReference py_dict,
string? namespaceStr,
string? assemblyName,
Expand All @@ -163,7 +164,9 @@ internal static Type CreateDerivedType(string name,
ModuleBuilder moduleBuilder = GetModuleBuilder(assemblyName, moduleName);

Type baseClass = baseType;
var interfaces = new List<Type> { typeof(IPythonDerivedType) };
var interfaces = new HashSet<Type> { typeof(IPythonDerivedType) };
foreach(var interfaceType in typeInterfaces)
interfaces.Add(interfaceType);

// if the base type is an interface then use System.Object as the base class
// and add the base type to the list of interfaces this new class will implement.
Expand Down Expand Up @@ -214,8 +217,9 @@ internal static Type CreateDerivedType(string name,
}
}

// override any virtual methods not already overridden by the properties above
MethodInfo[] methods = baseType.GetMethods();
// override any virtual not already overridden by the properties above
// also override any interface method.
var methods = baseType.GetMethods().Concat(interfaces.SelectMany(x => x.GetMethods()));
var virtualMethods = new HashSet<string>();
foreach (MethodInfo method in methods)
{
Expand Down
Loading