| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -36,6 +36,7 @@ | |||
| 36 | 36 | - Luke Stratman ([@lstratman](https://github.com/lstratman)) | |
| 37 | 37 | - Konstantin Posudevskiy ([@konstantin-posudevskiy](https://github.com/konstantin-posudevskiy)) | |
| 38 | 38 | - Matthias Dittrich ([@matthid](https://github.com/matthid)) | |
| 39 | + - Mohamed Koubaa ([@koubaa](https://github.com/koubaa)) | ||
| 39 | 40 | - Patrick Stewart ([@patstew](https://github.com/patstew)) | |
| 40 | 41 | - Raphael Nestler ([@rnestler](https://github.com/rnestler)) | |
| 41 | 42 | - Rickard Holmberg ([@rickardraysearch](https://github.com/rickardraysearch)) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,6 +23,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. | |||
| 23 | 23 | - Incorporated reference-style links to issues and pull requests in the CHANGELOG ([#608][i608]) | |
| 24 | 24 | - Added detailed comments about aproaches and dangers to handle multi-app-domains ([#625][p625]) | |
| 25 | 25 | - Python 3.7 support, builds and testing added. Defaults changed from Python 3.6 to 3.7 ([#698][p698]) | |
| 26 | + - Added support for C# types to provide `__repr__` ([#680][p680]) | ||
| 26 | 27 | ||
| 27 | 28 | ### Changed | |
| 28 | 29 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -233,7 +233,75 @@ public static IntPtr tp_str(IntPtr ob) | |||
| 233 | 233 | } | |
| 234 | 234 | try | |
| 235 | 235 | { | |
| 236 | + //As per python doc: | ||
| 237 | + //The return value must be a string object. If a class defines __repr__() but not __str__(), | ||
| 238 | + //then __repr__() is also used when an “informal” string representation of instances of that | ||
| 239 | + //class is required. | ||
| 240 | + //In C#, everything provides ToString(), so the check here will be whether the type explicitly | ||
| 241 | + //provides ToString() or if it is language provided (i.e. the fully qualified type name as a string) | ||
| 242 | + | ||
| 243 | + //First check which type in the object hierarchy provides ToString() | ||
| 244 | + //ToString has two "official" overloads so loop over GetMethods to get the one without parameters | ||
| 245 | + var instType = co.inst.GetType(); | ||
| 246 | + foreach (var method in instType.GetMethods()) | ||
| 247 | + { | ||
| 248 | + | ||
| 249 | + //TODO this could probably be done more cleanly with Linq | ||
| 250 | + if (!method.IsPublic) continue; //skip private/protected methods | ||
| 251 | + if (method.Name != "ToString") continue; //only look for ToString | ||
| 252 | + if (method.DeclaringType == typeof(object)) continue; //ignore default from object | ||
| 253 | + if (method.GetParameters().Length != 0) continue; //ignore Formatter overload of ToString | ||
| 254 | + | ||
| 255 | + //match! something other than object provides a parameter-less overload of ToString | ||
| 256 | + return Runtime.PyString_FromString(co.inst.ToString()); | ||
| 257 | + } | ||
| 258 | + | ||
| 259 | + //If the object defines __repr__, call it. | ||
| 260 | + System.Reflection.MethodInfo reprMethodInfo = instType.GetMethod("__repr__"); | ||
| 261 | + if (reprMethodInfo != null && reprMethodInfo.IsPublic) | ||
| 262 | + { | ||
| 263 | + var reprString = reprMethodInfo.Invoke(co.inst, null) as string; | ||
| 264 | + return Runtime.PyString_FromString(reprString); | ||
| 265 | + } | ||
| 266 | + | ||
| 267 | + //otherwise fallback to object's ToString() implementation | ||
| 236 | 268 | return Runtime.PyString_FromString(co.inst.ToString()); | |
| 269 | + | ||
| 270 | + } | ||
| 271 | + catch (Exception e) | ||
| 272 | + { | ||
| 273 | + if (e.InnerException != null) | ||
| 274 | + { | ||
| 275 | + e = e.InnerException; | ||
| 276 | + } | ||
| 277 | + Exceptions.SetError(e); | ||
| 278 | + return IntPtr.Zero; | ||
| 279 | + } | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + public static IntPtr tp_repr(IntPtr ob) | ||
| 283 | + { | ||
| 284 | + var co = GetManagedObject(ob) as CLRObject; | ||
| 285 | + if (co == null) | ||
| 286 | + { | ||
| 287 | + return Exceptions.RaiseTypeError("invalid object"); | ||
| 288 | + } | ||
| 289 | + try | ||
| 290 | + { | ||
| 291 | + //if __repr__ is defined, use it | ||
| 292 | + var instType = co.inst.GetType(); | ||
| 293 | + System.Reflection.MethodInfo methodInfo = instType.GetMethod("__repr__"); | ||
| 294 | + if (methodInfo != null && methodInfo.IsPublic) | ||
| 295 | + { | ||
| 296 | + var reprString = methodInfo.Invoke(co.inst, null) as string; | ||
| 297 | + return Runtime.PyString_FromString(reprString); | ||
| 298 | + } | ||
| 299 | + | ||
| 300 | + //otherwise use the standard object.__repr__(inst) | ||
| 301 | + IntPtr args = Runtime.PyTuple_New(1); | ||
| 302 | + Runtime.PyTuple_SetItem(args, 0, ob); | ||
| 303 | + IntPtr reprFunc = Runtime.PyObject_GetAttrString(Runtime.PyBaseObjectType, "__repr__"); | ||
| 304 | + return Runtime.PyObject_Call(reprFunc, args, IntPtr.Zero); | ||
| 237 | 305 | } | |
| 238 | 306 | catch (Exception e) | |
| 239 | 307 | { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -36,6 +36,29 @@ internal static Exception ToException(IntPtr ob) | |||
| 36 | 36 | return e; | |
| 37 | 37 | } | |
| 38 | 38 | ||
| 39 | + /// <summary> | ||
| 40 | + /// Exception __repr__ implementation | ||
| 41 | + /// </summary> | ||
| 42 | + public 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 | + | ||
| 39 | 62 | /// <summary> | |
| 40 | 63 | /// Exception __str__ implementation | |
| 41 | 64 | /// </summary> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -91,6 +91,7 @@ | |||
| 91 | 91 | <Compile Include="threadtest.cs" /> | |
| 92 | 92 | <Compile Include="doctest.cs" /> | |
| 93 | 93 | <Compile Include="subclasstest.cs" /> | |
| 94 | + <Compile Include="reprtest.cs" /> | ||
| 94 | 95 | </ItemGroup> | |
| 95 | 96 | <ItemGroup> | |
| 96 | 97 | <Reference Include="Microsoft.CSharp" /> | |
| Original file line number | Diff line number | Diff 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 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,74 @@ | |||
| 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_repr_only(): | ||
| 23 | + """Test class implementing __repr__() but not ToString()""" | ||
| 24 | + ob = ReprTest.Foo() | ||
| 25 | + assert str(ob) == "I implement __repr__() but not ToString()!" | ||
| 26 | + assert ob.__repr__() == "I implement __repr__() but not ToString()!" | ||
| 27 | + | ||
| 28 | + def test_str_only(): | ||
| 29 | + """Test class implementing ToString() but not __repr__()""" | ||
| 30 | + ob = ReprTest.Bar() | ||
| 31 | + assert str(ob) == "I implement ToString() but not __repr__()!" | ||
| 32 | + assert "<Python.Test.Bar object at " in ob.__repr__() | ||
| 33 | + | ||
| 34 | + def test_hierarchy1(): | ||
| 35 | + """Test inheritance heirarchy with base & middle class implementing ToString""" | ||
| 36 | + ob1 = ReprTest.BazBase() | ||
| 37 | + assert str(ob1) == "Base class implementing ToString()!" | ||
| 38 | + assert "<Python.Test.BazBase object at " in ob1.__repr__() | ||
| 39 | + | ||
| 40 | + ob2 = ReprTest.BazMiddle() | ||
| 41 | + assert str(ob2) == "Middle class implementing ToString()!" | ||
| 42 | + assert "<Python.Test.BazMiddle object at " in ob2.__repr__() | ||
| 43 | + | ||
| 44 | + ob3 = ReprTest.Baz() | ||
| 45 | + assert str(ob3) == "Middle class implementing ToString()!" | ||
| 46 | + assert "<Python.Test.Baz object at " in ob3.__repr__() | ||
| 47 | + | ||
| 48 | + def bad_tostring(): | ||
| 49 | + """Test ToString that can't be used by str()""" | ||
| 50 | + ob = ReprTest.Quux() | ||
| 51 | + assert str(ob) == "Python.Test.ReprTest+Quux" | ||
| 52 | + assert "<Python.Test.Quux object at " in ob.__repr__() | ||
| 53 | + | ||
| 54 | + def bad_repr(): | ||
| 55 | + """Test incorrect implementation of repr""" | ||
| 56 | + ob1 = ReprTest.QuuzBase() | ||
| 57 | + assert str(ob1) == "Python.Test.ReprTest+QuuzBase" | ||
| 58 | + assert "<Python.Test.QuuzBase object at " in ob.__repr__() | ||
| 59 | + | ||
| 60 | + ob2 = ReprTest.Quuz() | ||
| 61 | + assert str(ob2) == "Python.Test.ReprTest+Quuz" | ||
| 62 | + assert "<Python.Test.Quuz object at " in ob.__repr__() | ||
| 63 | + | ||
| 64 | + ob3 = ReprTest.Corge() | ||
| 65 | + with pytest.raises(Exception): | ||
| 66 | + str(ob3) | ||
| 67 | + with pytest.raises(Exception): | ||
| 68 | + ob3.__repr__() | ||
| 69 | + | ||
| 70 | + ob4 = ReprTest.Grault() | ||
| 71 | + with pytest.raises(Exception): | ||
| 72 | + str(ob4) | ||
| 73 | + with pytest.raises(Exception): | ||
| 74 | + ob4.__repr__() | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -59,6 +59,7 @@ | |||
| 59 | 59 | <Compile Include="test_recursive_types.py" /> | |
| 60 | 60 | <Compile Include="test_subclass.py" /> | |
| 61 | 61 | <Compile Include="test_thread.py" /> | |
| 62 | + <Compile Include="test_repr.py" /> | ||
| 62 | 63 | <Compile Include="utils.py" /> | |
| 63 | 64 | <Compile Include="fixtures\argv-fixture.py" /> | |
| 64 | 65 | </ItemGroup> | |
| Back | FazBrowse Home | New Git URL |
0 commit comments