Skip to content

Commit 6172c79

Browse files
authored
PEP8 style properties and fields dynamic objects check (#90)
* Fix: test for PEP8 properties/fields in dynamic objects * Bump version to 2.0.35 * Minor refactor
1 parent 072346a commit 6172c79

File tree

5 files changed

+51
-19
lines changed

5 files changed

+51
-19
lines changed

src/embed_tests/TestPropertyAccess.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,33 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o
966966
protected static string NonDynamicProtectedStaticProperty { get; set; } = "Default value";
967967

968968
protected string NonDynamicProtectedField = "Default value";
969+
970+
public string NonDynamicField;
971+
}
972+
973+
[TestCase("NonDynamicField")]
974+
[TestCase("NonDynamicProperty")]
975+
public void TestDynamicObjectCanAccessCSharpNonDynamicPropertiesAndFieldsWithPEP8Syntax(string name)
976+
{
977+
using var _ = Py.GIL();
978+
979+
var model = new DynamicFixture();
980+
using var pyModel = model.ToPython();
981+
982+
var pep8Name = name.ToSnakeCase();
983+
pyModel.SetAttr(pep8Name, "Piertotum Locomotor".ToPython());
984+
985+
Assert.IsFalse(model.Properties.ContainsKey(name));
986+
Assert.IsFalse(model.Properties.ContainsKey(pep8Name));
987+
988+
var value = pyModel.GetAttr(pep8Name).As<string>();
989+
Assert.AreEqual("Piertotum Locomotor", value);
990+
991+
var memberInfo = model.GetType().GetMember(name)[0];
992+
var managedValue = memberInfo.MemberType == MemberTypes.Property
993+
? ((PropertyInfo)memberInfo).GetValue(model)
994+
: ((FieldInfo)memberInfo).GetValue(model);
995+
Assert.AreEqual(value, managedValue);
969996
}
970997

971998
public class TestPerson : IComparable, IComparable<TestPerson>

src/perf_tests/Python.PerformanceTests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1414
</PackageReference>
1515
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
16-
<PackageReference Include="quantconnect.pythonnet" Version="2.0.34" GeneratePathProperty="true">
16+
<PackageReference Include="quantconnect.pythonnet" Version="2.0.35" GeneratePathProperty="true">
1717
<IncludeAssets>compile</IncludeAssets>
1818
</PackageReference>
1919
</ItemGroup>
@@ -25,7 +25,7 @@
2525
</Target>
2626

2727
<Target Name="CopyBaseline" AfterTargets="Build">
28-
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.34\lib\net5.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
28+
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.35\lib\net5.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
2929
</Target>
3030

3131
<Target Name="CopyNewBuild" AfterTargets="Build">

src/runtime/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
55
[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
66

7-
[assembly: AssemblyVersion("2.0.34")]
8-
[assembly: AssemblyFileVersion("2.0.34")]
7+
[assembly: AssemblyVersion("2.0.35")]
8+
[assembly: AssemblyFileVersion("2.0.35")]

src/runtime/Python.Runtime.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<RootNamespace>Python.Runtime</RootNamespace>
66
<AssemblyName>Python.Runtime</AssemblyName>
77
<PackageId>QuantConnect.pythonnet</PackageId>
8-
<Version>2.0.34</Version>
8+
<Version>2.0.35</Version>
99
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
1010
<PackageLicenseFile>LICENSE</PackageLicenseFile>
1111
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>

src/runtime/Types/DynamicClassObject.cs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,7 @@ private static CallSite<Func<CallSite, object, object, object>> SetAttrCallSite(
6666
/// </summary>
6767
public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference key)
6868
{
69-
var result = Runtime.PyObject_GenericGetAttr(ob, key);
70-
71-
// If AttributeError was raised, we try to get the attribute from the managed object dynamic properties.
72-
if (Exceptions.ExceptionMatches(Exceptions.AttributeError))
69+
if (!TryGetNonDynamicMember(ob, key, out var result))
7370
{
7471
var clrObj = (CLRObject)GetManagedObject(ob)!;
7572

@@ -103,20 +100,14 @@ public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference k
103100
/// </summary>
104101
public static int tp_setattro(BorrowedReference ob, BorrowedReference key, BorrowedReference val)
105102
{
106-
var clrObj = (CLRObject)GetManagedObject(ob)!;
107-
var name = Runtime.GetManagedString(key);
108-
109-
// If the key corresponds to a valid property or field of the class, we let the default implementation handle it.
110-
var clrObjectType = clrObj.inst.GetType();
111-
var bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
112-
var property = clrObjectType.GetProperty(name, bindingFlags);
113-
var field = property == null ? clrObjectType.GetField(name, bindingFlags) : null;
114-
if ((property != null && property.SetMethod != null) || field != null)
103+
if (TryGetNonDynamicMember(ob, key, out _, clearExceptions: true))
115104
{
116105
return Runtime.PyObject_GenericSetAttr(ob, key, val);
117106
}
118107

119-
var callsite = SetAttrCallSite(name, clrObjectType);
108+
var clrObj = (CLRObject)GetManagedObject(ob)!;
109+
var name = Runtime.GetManagedString(key);
110+
var callsite = SetAttrCallSite(name, clrObj.inst.GetType());
120111
try
121112
{
122113
callsite.Target(callsite, clrObj.inst, PyObject.FromNullableReference(val));
@@ -129,5 +120,19 @@ public static int tp_setattro(BorrowedReference ob, BorrowedReference key, Borro
129120

130121
return 0;
131122
}
123+
124+
private static bool TryGetNonDynamicMember(BorrowedReference ob, BorrowedReference key, out NewReference value, bool clearExceptions = false)
125+
{
126+
value = Runtime.PyObject_GenericGetAttr(ob, key);
127+
// If AttributeError was raised, we try to get the attribute from the managed object dynamic properties.
128+
var result = !Exceptions.ExceptionMatches(Exceptions.AttributeError);
129+
130+
if (clearExceptions)
131+
{
132+
Exceptions.Clear();
133+
}
134+
135+
return result;
136+
}
132137
}
133138
}

0 commit comments

Comments
 (0)