Skip to content

Commit 12af794

Browse files
rickardraysearchfilmor
authored andcommitted
clrmethod working for python 2 (#494)
* Add Runtime.PyObject_IsIterable and fix PyIter_Check for Python 2 * Add test_clrmethod.py and update AUTHORS and CHANGELOG * Fix test docstrings * Use IntPtr.Zero instead of null for comparisons with IntPtrs * Add test on Runtime.PyObject_IsIterable and Runtime.PyIter_Check * Use the already defined TypeFlags.HaveIter instead of redefining it * Add PyIter_Check and PyObject_IsIterable tests on threading.Lock, which does not have type feature iter * Move the tests from test_runtime.py to TestRuntime.py
1 parent d39f9f6 commit 12af794

File tree

6 files changed

+144
-15
lines changed

6 files changed

+144
-15
lines changed

AUTHORS.md

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
- Matthias Dittrich ([@matthid](https://github.com/matthid))
3232
- Patrick Stewart ([@patstew](https://github.com/patstew))
3333
- Raphael Nestler ([@rnestler](https://github.com/rnestler))
34+
- Rickard Holmberg ([@rickardraysearch](https://github.com/rickardraysearch))
3435
- Sam Winstanley ([@swinstanley](https://github.com/swinstanley))
3536
- Sean Freitag ([@cowboygneox](https://github.com/cowboygneox))
3637
- Serge Weinstock ([@sweinst](https://github.com/sweinst))

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
2121
derived from a .NET class has a `__namespace__` or `__assembly__`
2222
attribute (#481)
2323
- Fixed conversion of 'float' and 'double' values (#486)
24+
- Fixed 'clrmethod' for python 2 (#492)
2425

2526

2627
## [2.3.0][] - 2017-03-11

src/embed_tests/TestRuntime.cs

+42-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using NUnit.Framework;
33
using Python.Runtime;
44

@@ -47,5 +47,46 @@ public static void RefCountTest()
4747

4848
Runtime.Runtime.Py_Finalize();
4949
}
50+
51+
[Test]
52+
public static void PyCheck_Iter_PyObject_IsIterable_Test()
53+
{
54+
Runtime.Runtime.Py_Initialize();
55+
56+
// Tests that a python list is an iterable, but not an iterator
57+
var pyList = Runtime.Runtime.PyList_New(0);
58+
Assert.IsFalse(Runtime.Runtime.PyIter_Check(pyList));
59+
Assert.IsTrue(Runtime.Runtime.PyObject_IsIterable(pyList));
60+
61+
// Tests that a python list iterator is both an iterable and an iterator
62+
var pyListIter = Runtime.Runtime.PyObject_GetIter(pyList);
63+
Assert.IsTrue(Runtime.Runtime.PyObject_IsIterable(pyListIter));
64+
Assert.IsTrue(Runtime.Runtime.PyIter_Check(pyListIter));
65+
66+
// Tests that a python float is neither an iterable nor an iterator
67+
var pyFloat = Runtime.Runtime.PyFloat_FromDouble(2.73);
68+
Assert.IsFalse(Runtime.Runtime.PyObject_IsIterable(pyFloat));
69+
Assert.IsFalse(Runtime.Runtime.PyIter_Check(pyFloat));
70+
71+
Runtime.Runtime.Py_Finalize();
72+
}
73+
74+
[Test]
75+
public static void PyCheck_Iter_PyObject_IsIterable_ThreadingLock_Test()
76+
{
77+
Runtime.Runtime.Py_Initialize();
78+
79+
// Create an instance of threading.Lock, which is one of the very few types that does not have the
80+
// TypeFlags.HaveIter set in Python 2. This tests a different code path in PyObject_IsIterable and PyIter_Check.
81+
var threading = Runtime.Runtime.PyImport_ImportModule("threading");
82+
var threadingDict = Runtime.Runtime.PyModule_GetDict(threading);
83+
var lockType = Runtime.Runtime.PyDict_GetItemString(threadingDict, "Lock");
84+
var lockInstance = Runtime.Runtime.PyObject_CallObject(lockType, Runtime.Runtime.PyTuple_New(0));
85+
86+
Assert.IsFalse(Runtime.Runtime.PyObject_IsIterable(lockInstance));
87+
Assert.IsFalse(Runtime.Runtime.PyIter_Check(lockInstance));
88+
89+
Runtime.Runtime.Py_Finalize();
90+
}
5091
}
5192
}

src/runtime/pyobject.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ public bool IsCallable()
802802
/// </remarks>
803803
public bool IsIterable()
804804
{
805-
return Runtime.PyIter_Check(obj);
805+
return Runtime.PyObject_IsIterable(obj);
806806
}
807807

808808

src/runtime/runtime.cs

+26-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Runtime.InteropServices;
33
using System.Security;
44
using System.Text;
@@ -274,19 +274,17 @@ internal static void Initialize()
274274

275275
Error = new IntPtr(-1);
276276

277-
#if PYTHON3
278277
IntPtr dllLocal = IntPtr.Zero;
279278
if (PythonDll != "__Internal")
280279
{
281-
NativeMethods.LoadLibrary(PythonDll);
280+
dllLocal = NativeMethods.LoadLibrary(PythonDll);
282281
}
283282
_PyObject_NextNotImplemented = NativeMethods.GetProcAddress(dllLocal, "_PyObject_NextNotImplemented");
284283
#if !(MONO_LINUX || MONO_OSX)
285284
if (dllLocal != IntPtr.Zero)
286285
{
287286
NativeMethods.FreeLibrary(dllLocal);
288287
}
289-
#endif
290288
#endif
291289

292290
// Initialize modules that depend on the runtime class.
@@ -349,8 +347,8 @@ internal static int AtExit()
349347

350348
#if PYTHON3
351349
internal static IntPtr PyBytesType;
352-
internal static IntPtr _PyObject_NextNotImplemented;
353350
#endif
351+
internal static IntPtr _PyObject_NextNotImplemented;
354352

355353
internal static IntPtr PyNotImplemented;
356354
internal const int Py_LT = 0;
@@ -780,6 +778,21 @@ internal static string PyObject_GetTypeName(IntPtr op)
780778
return Marshal.PtrToStringAnsi(ppName);
781779
}
782780

781+
/// <summary>
782+
/// Test whether the Python object is an iterable.
783+
/// </summary>
784+
internal static bool PyObject_IsIterable(IntPtr pointer)
785+
{
786+
var ob_type = Marshal.ReadIntPtr(pointer, ObjectOffset.ob_type);
787+
#if PYTHON2
788+
long tp_flags = Marshal.ReadInt64(ob_type, TypeOffset.tp_flags);
789+
if ((tp_flags & TypeFlags.HaveIter) == 0)
790+
return false;
791+
#endif
792+
IntPtr tp_iter = Marshal.ReadIntPtr(ob_type, TypeOffset.tp_iter);
793+
return tp_iter != IntPtr.Zero;
794+
}
795+
783796
[DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)]
784797
internal static extern int PyObject_HasAttrString(IntPtr pointer, string name);
785798

@@ -1425,17 +1438,17 @@ internal static bool PyTuple_Check(IntPtr ob)
14251438
// Python iterator API
14261439
//====================================================================
14271440

1428-
#if PYTHON2
1429-
[DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)]
1430-
internal static extern bool PyIter_Check(IntPtr pointer);
1431-
#elif PYTHON3
14321441
internal static bool PyIter_Check(IntPtr pointer)
14331442
{
1434-
var ob_type = (IntPtr)Marshal.PtrToStructure(pointer + ObjectOffset.ob_type, typeof(IntPtr));
1435-
IntPtr tp_iternext = ob_type + TypeOffset.tp_iternext;
1436-
return tp_iternext != null && tp_iternext != _PyObject_NextNotImplemented;
1437-
}
1443+
var ob_type = Marshal.ReadIntPtr(pointer, ObjectOffset.ob_type);
1444+
#if PYTHON2
1445+
long tp_flags = Marshal.ReadInt64(ob_type, TypeOffset.tp_flags);
1446+
if ((tp_flags & TypeFlags.HaveIter) == 0)
1447+
return false;
14381448
#endif
1449+
IntPtr tp_iternext = Marshal.ReadIntPtr(ob_type, TypeOffset.tp_iternext);
1450+
return tp_iternext != IntPtr.Zero && tp_iternext != _PyObject_NextNotImplemented;
1451+
}
14391452

14401453
[DllImport(PythonDll, CallingConvention = CallingConvention.Cdecl)]
14411454
internal static extern IntPtr PyIter_Next(IntPtr pointer);

src/tests/test_clrmethod.py

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""Test clrmethod and clrproperty support for calling methods and getting/setting python properties from CLR."""
4+
5+
import Python.Test as Test
6+
import System
7+
import pytest
8+
import clr
9+
10+
class ExampleClrClass(System.Object):
11+
__namespace__ = "PyTest"
12+
def __init__(self):
13+
self._x = 3
14+
@clr.clrmethod(int, [int])
15+
def test(self, x):
16+
return x*2
17+
18+
def get_X(self):
19+
return self._x
20+
def set_X(self, value):
21+
self._x = value
22+
X = clr.clrproperty(int, get_X, set_X)
23+
24+
@clr.clrproperty(int)
25+
def Y(self):
26+
return self._x * 2
27+
28+
def test_set_and_get_property_from_py():
29+
"""Test setting and getting clr-accessible properties from python."""
30+
t = ExampleClrClass()
31+
assert t.X == 3
32+
assert t.Y == 3 * 2
33+
t.X = 4
34+
assert t.X == 4
35+
assert t.Y == 4 * 2
36+
37+
def test_set_and_get_property_from_clr():
38+
"""Test setting and getting clr-accessible properties from the clr."""
39+
t = ExampleClrClass()
40+
assert t.GetType().GetProperty("X").GetValue(t) == 3
41+
assert t.GetType().GetProperty("Y").GetValue(t) == 3 * 2
42+
t.GetType().GetProperty("X").SetValue(t, 4)
43+
assert t.GetType().GetProperty("X").GetValue(t) == 4
44+
assert t.GetType().GetProperty("Y").GetValue(t) == 4 * 2
45+
46+
47+
def test_set_and_get_property_from_clr_and_py():
48+
"""Test setting and getting clr-accessible properties alternatingly from the clr and from python."""
49+
t = ExampleClrClass()
50+
assert t.GetType().GetProperty("X").GetValue(t) == 3
51+
assert t.GetType().GetProperty("Y").GetValue(t) == 3 * 2
52+
assert t.X == 3
53+
assert t.Y == 3 * 2
54+
t.GetType().GetProperty("X").SetValue(t, 4)
55+
assert t.GetType().GetProperty("X").GetValue(t) == 4
56+
assert t.GetType().GetProperty("Y").GetValue(t) == 4 * 2
57+
assert t.X == 4
58+
assert t.Y == 4 * 2
59+
t.X = 5
60+
assert t.GetType().GetProperty("X").GetValue(t) == 5
61+
assert t.GetType().GetProperty("Y").GetValue(t) == 5 * 2
62+
assert t.X == 5
63+
assert t.Y == 5 * 2
64+
65+
def test_method_invocation_from_py():
66+
"""Test calling a clr-accessible method from python."""
67+
t = ExampleClrClass()
68+
assert t.test(41) == 41*2
69+
70+
def test_method_invocation_from_clr():
71+
"""Test calling a clr-accessible method from the clr."""
72+
t = ExampleClrClass()
73+
assert t.GetType().GetMethod("test").Invoke(t, [37]) == 37*2

0 commit comments

Comments
 (0)