Skip to content

Commit 149e4e5

Browse files
authored
Merge branch 'master' into master
2 parents ea48a09 + 4a9457f commit 149e4e5

12 files changed

+294
-11
lines changed

AUTHORS.md

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
- Luke Stratman ([@lstratman](https://github.com/lstratman))
4242
- Konstantin Posudevskiy ([@konstantin-posudevskiy](https://github.com/konstantin-posudevskiy))
4343
- Matthias Dittrich ([@matthid](https://github.com/matthid))
44+
- Mohamed Koubaa ([@koubaa](https://github.com/koubaa))
4445
- Patrick Stewart ([@patstew](https://github.com/patstew))
4546
- Raphael Nestler ([@rnestler](https://github.com/rnestler))
4647
- Rickard Holmberg ([@rickardraysearch](https://github.com/rickardraysearch))

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
2525
- Fixed runtime that fails loading when using pythonnet in an environment
2626
together with Nuitka
2727
- Fixes bug where delegates get casts (dotnetcore)
28+
- Determine size of interpreter longs at runtime
2829

2930
## [2.4.0][]
3031

@@ -45,6 +46,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
4546
- Added PyObject finalizer support, Python objects referred by C# can be auto collect now ([#692][p692]).
4647
- Added detailed comments about aproaches and dangers to handle multi-app-domains ([#625][p625])
4748
- Python 3.7 support, builds and testing added. Defaults changed from Python 3.6 to 3.7 ([#698][p698])
49+
- Added support for C# types to provide `__repr__` ([#680][p680])
4850

4951
### Changed
5052

README.rst

+5
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,8 @@ https://github.com/pythonnet/pythonnet/wiki
113113
:target: http://stackoverflow.com/questions/tagged/python.net
114114
.. |conda-forge version| image:: https://img.shields.io/conda/vn/conda-forge/pythonnet.svg
115115
:target: https://anaconda.org/conda-forge/pythonnet
116+
117+
Resources
118+
---------
119+
Mailing list: https://mail.python.org/mailman/listinfo/pythondotnet
120+
Chat: https://gitter.im/pythonnet/pythonnet

src/runtime/classbase.cs

+38
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,44 @@ public static IntPtr tp_str(IntPtr ob)
246246
}
247247
}
248248

249+
public static IntPtr tp_repr(IntPtr ob)
250+
{
251+
var co = GetManagedObject(ob) as CLRObject;
252+
if (co == null)
253+
{
254+
return Exceptions.RaiseTypeError("invalid object");
255+
}
256+
try
257+
{
258+
//if __repr__ is defined, use it
259+
var instType = co.inst.GetType();
260+
System.Reflection.MethodInfo methodInfo = instType.GetMethod("__repr__");
261+
if (methodInfo != null && methodInfo.IsPublic)
262+
{
263+
var reprString = methodInfo.Invoke(co.inst, null) as string;
264+
return Runtime.PyString_FromString(reprString);
265+
}
266+
267+
//otherwise use the standard object.__repr__(inst)
268+
IntPtr args = Runtime.PyTuple_New(1);
269+
Runtime.PyTuple_SetItem(args, 0, ob);
270+
IntPtr reprFunc = Runtime.PyObject_GetAttrString(Runtime.PyBaseObjectType, "__repr__");
271+
var output = Runtime.PyObject_Call(reprFunc, args, IntPtr.Zero);
272+
Runtime.XDecref(args);
273+
Runtime.XDecref(reprFunc);
274+
return output;
275+
}
276+
catch (Exception e)
277+
{
278+
if (e.InnerException != null)
279+
{
280+
e = e.InnerException;
281+
}
282+
Exceptions.SetError(e);
283+
return IntPtr.Zero;
284+
}
285+
}
286+
249287

250288
/// <summary>
251289
/// Standard dealloc implementation for instances of reflected types.

src/runtime/converter.cs

+14-1
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,20 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
728728
}
729729
goto type_error;
730730
}
731-
uint ui = (uint)Runtime.PyLong_AsUnsignedLong(op);
731+
732+
uint ui;
733+
try
734+
{
735+
ui = Convert.ToUInt32(Runtime.PyLong_AsUnsignedLong(op));
736+
} catch (OverflowException)
737+
{
738+
// Probably wasn't an overflow in python but was in C# (e.g. if cpython
739+
// longs are 64 bit then 0xFFFFFFFF + 1 will not overflow in
740+
// PyLong_AsUnsignedLong)
741+
Runtime.XDecref(op);
742+
goto overflow;
743+
}
744+
732745

733746
if (Exceptions.ErrorOccurred())
734747
{

src/runtime/exceptions.cs

+23
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,29 @@ internal static Exception ToException(IntPtr ob)
3636
return e;
3737
}
3838

39+
/// <summary>
40+
/// Exception __repr__ implementation
41+
/// </summary>
42+
public new static IntPtr tp_repr(IntPtr ob)
43+
{
44+
Exception e = ToException(ob);
45+
if (e == null)
46+
{
47+
return Exceptions.RaiseTypeError("invalid object");
48+
}
49+
string name = e.GetType().Name;
50+
string message;
51+
if (e.Message != String.Empty)
52+
{
53+
message = String.Format("{0}('{1}')", name, e.Message);
54+
}
55+
else
56+
{
57+
message = String.Format("{0}()", name);
58+
}
59+
return Runtime.PyUnicode_FromString(message);
60+
}
61+
3962
/// <summary>
4063
/// Exception __str__ implementation
4164
/// </summary>

src/runtime/runtime.cs

+30-4
Original file line numberDiff line numberDiff line change
@@ -1036,8 +1036,21 @@ internal static bool PyLong_Check(IntPtr ob)
10361036
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
10371037
internal static extern IntPtr PyLong_FromLong(long value);
10381038

1039-
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1040-
internal static extern IntPtr PyLong_FromUnsignedLong(uint value);
1039+
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl,
1040+
EntryPoint = "PyLong_FromUnsignedLong")]
1041+
internal static extern IntPtr PyLong_FromUnsignedLong32(uint value);
1042+
1043+
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl,
1044+
EntryPoint = "PyLong_FromUnsignedLong")]
1045+
internal static extern IntPtr PyLong_FromUnsignedLong64(ulong value);
1046+
1047+
internal static IntPtr PyLong_FromUnsignedLong(object value)
1048+
{
1049+
if(Is32Bit || IsWindows)
1050+
return PyLong_FromUnsignedLong32(Convert.ToUInt32(value));
1051+
else
1052+
return PyLong_FromUnsignedLong64(Convert.ToUInt64(value));
1053+
}
10411054

10421055
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
10431056
internal static extern IntPtr PyLong_FromDouble(double value);
@@ -1054,8 +1067,21 @@ internal static bool PyLong_Check(IntPtr ob)
10541067
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
10551068
internal static extern int PyLong_AsLong(IntPtr value);
10561069

1057-
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1058-
internal static extern uint PyLong_AsUnsignedLong(IntPtr value);
1070+
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl,
1071+
EntryPoint = "PyLong_AsUnsignedLong")]
1072+
internal static extern uint PyLong_AsUnsignedLong32(IntPtr value);
1073+
1074+
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl,
1075+
EntryPoint = "PyLong_AsUnsignedLong")]
1076+
internal static extern ulong PyLong_AsUnsignedLong64(IntPtr value);
1077+
1078+
internal static object PyLong_AsUnsignedLong(IntPtr value)
1079+
{
1080+
if (Is32Bit || IsWindows)
1081+
return PyLong_AsUnsignedLong32(value);
1082+
else
1083+
return PyLong_AsUnsignedLong64(value);
1084+
}
10591085

10601086
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
10611087
internal static extern long PyLong_AsLongLong(IntPtr value);

src/testing/Python.Test.csproj

+2-1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
<Compile Include="threadtest.cs" />
9292
<Compile Include="doctest.cs" />
9393
<Compile Include="subclasstest.cs" />
94+
<Compile Include="ReprTest.cs" />
9495
</ItemGroup>
9596
<ItemGroup>
9697
<Reference Include="Microsoft.CSharp" />
@@ -111,4 +112,4 @@
111112
<Copy SourceFiles="$(TargetAssembly)" DestinationFolder="$(SolutionDir)\src\tests\fixtures" />
112113
<!--Copy SourceFiles="$(TargetAssemblyPdb)" Condition="Exists('$(TargetAssemblyPdb)')" DestinationFolder="$(PythonBuildDir)" /-->
113114
</Target>
114-
</Project>
115+
</Project>

src/testing/ReprTest.cs

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using System;
2+
using System.Text;
3+
4+
namespace Python.Test
5+
{
6+
/// <summary>
7+
/// Supports repr unit tests.
8+
/// </summary>
9+
public class ReprTest
10+
{
11+
public class Point
12+
{
13+
public Point(double x, double y)
14+
{
15+
X = x;
16+
Y = y;
17+
}
18+
19+
public double X { get; set; }
20+
public double Y { get; set; }
21+
22+
public override string ToString()
23+
{
24+
return base.ToString() + ": X=" + X.ToString() + ", Y=" + Y.ToString();
25+
}
26+
27+
public string __repr__()
28+
{
29+
return "Point(" + X.ToString() + "," + Y.ToString() + ")";
30+
}
31+
}
32+
33+
public class Foo
34+
{
35+
public string __repr__()
36+
{
37+
return "I implement __repr__() but not ToString()!";
38+
}
39+
}
40+
41+
public class Bar
42+
{
43+
public override string ToString()
44+
{
45+
return "I implement ToString() but not __repr__()!";
46+
}
47+
}
48+
49+
public class BazBase
50+
{
51+
public override string ToString()
52+
{
53+
return "Base class implementing ToString()!";
54+
}
55+
}
56+
57+
public class BazMiddle : BazBase
58+
{
59+
public override string ToString()
60+
{
61+
return "Middle class implementing ToString()!";
62+
}
63+
}
64+
65+
//implements ToString via BazMiddle
66+
public class Baz : BazMiddle
67+
{
68+
69+
}
70+
71+
public class Quux
72+
{
73+
public string ToString(string format)
74+
{
75+
return "I implement ToString() with an argument!";
76+
}
77+
}
78+
79+
public class QuuzBase
80+
{
81+
protected string __repr__()
82+
{
83+
return "I implement __repr__ but it isn't public!";
84+
}
85+
}
86+
87+
public class Quuz : QuuzBase
88+
{
89+
90+
}
91+
92+
public class Corge
93+
{
94+
public string __repr__(int i)
95+
{
96+
return "__repr__ implemention with input parameter!";
97+
}
98+
}
99+
100+
public class Grault
101+
{
102+
public int __repr__()
103+
{
104+
return "__repr__ implemention with wrong return type!".Length;
105+
}
106+
}
107+
}
108+
}

src/tests/test_exceptions.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -282,11 +282,8 @@ def test_python_compat_of_managed_exceptions():
282282

283283
assert e.args == (msg,)
284284
assert isinstance(e.args, tuple)
285-
if PY3:
286-
strexp = "OverflowException('Simple message"
287-
assert repr(e)[:len(strexp)] == strexp
288-
elif PY2:
289-
assert repr(e) == "OverflowException(u'Simple message',)"
285+
strexp = "OverflowException('Simple message"
286+
assert repr(e)[:len(strexp)] == strexp
290287

291288

292289
def test_exception_is_instance_of_system_object():

src/tests/test_repr.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""Test __repr__ output"""
4+
5+
import System
6+
import pytest
7+
from Python.Test import ReprTest
8+
9+
def test_basic():
10+
"""Test Point class which implements both ToString and __repr__ without inheritance"""
11+
ob = ReprTest.Point(1,2)
12+
# point implements ToString() and __repr__()
13+
assert ob.__repr__() == "Point(1,2)"
14+
assert str(ob) == "Python.Test.ReprTest+Point: X=1, Y=2"
15+
16+
def test_system_string():
17+
"""Test system string"""
18+
ob = System.String("hello")
19+
assert str(ob) == "hello"
20+
assert "<System.String object at " in ob.__repr__()
21+
22+
def test_str_only():
23+
"""Test class implementing ToString() but not __repr__()"""
24+
ob = ReprTest.Bar()
25+
assert str(ob) == "I implement ToString() but not __repr__()!"
26+
assert "<Python.Test.Bar object at " in ob.__repr__()
27+
28+
def test_hierarchy1():
29+
"""Test inheritance heirarchy with base & middle class implementing ToString"""
30+
ob1 = ReprTest.BazBase()
31+
assert str(ob1) == "Base class implementing ToString()!"
32+
assert "<Python.Test.BazBase object at " in ob1.__repr__()
33+
34+
ob2 = ReprTest.BazMiddle()
35+
assert str(ob2) == "Middle class implementing ToString()!"
36+
assert "<Python.Test.BazMiddle object at " in ob2.__repr__()
37+
38+
ob3 = ReprTest.Baz()
39+
assert str(ob3) == "Middle class implementing ToString()!"
40+
assert "<Python.Test.Baz object at " in ob3.__repr__()
41+
42+
def bad_tostring():
43+
"""Test ToString that can't be used by str()"""
44+
ob = ReprTest.Quux()
45+
assert str(ob) == "Python.Test.ReprTest+Quux"
46+
assert "<Python.Test.Quux object at " in ob.__repr__()
47+
48+
def bad_repr():
49+
"""Test incorrect implementation of repr"""
50+
ob1 = ReprTest.QuuzBase()
51+
assert str(ob1) == "Python.Test.ReprTest+QuuzBase"
52+
assert "<Python.Test.QuuzBase object at " in ob.__repr__()
53+
54+
ob2 = ReprTest.Quuz()
55+
assert str(ob2) == "Python.Test.ReprTest+Quuz"
56+
assert "<Python.Test.Quuz object at " in ob.__repr__()
57+
58+
ob3 = ReprTest.Corge()
59+
with pytest.raises(Exception):
60+
str(ob3)
61+
with pytest.raises(Exception):
62+
ob3.__repr__()
63+
64+
ob4 = ReprTest.Grault()
65+
with pytest.raises(Exception):
66+
str(ob4)
67+
with pytest.raises(Exception):
68+
ob4.__repr__()

src/tests/tests.pyproj

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
<Compile Include="test_recursive_types.py" />
6060
<Compile Include="test_subclass.py" />
6161
<Compile Include="test_thread.py" />
62+
<Compile Include="test_repr.py" />
6263
<Compile Include="utils.py" />
6364
<Compile Include="fixtures\argv-fixture.py" />
6465
</ItemGroup>

0 commit comments

Comments
 (0)