Skip to content

Commit

Permalink
PEP8 style properties and fields dynamic objects check (#90)
Browse files Browse the repository at this point in the history
* Fix: test for PEP8 properties/fields in dynamic objects

* Bump version to 2.0.35

* Minor refactor
  • Loading branch information
jhonabreul committed Apr 19, 2024
1 parent 072346a commit 6172c79
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 19 deletions.
27 changes: 27 additions & 0 deletions src/embed_tests/TestPropertyAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,33 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o
protected static string NonDynamicProtectedStaticProperty { get; set; } = "Default value";

protected string NonDynamicProtectedField = "Default value";

public string NonDynamicField;
}

[TestCase("NonDynamicField")]
[TestCase("NonDynamicProperty")]
public void TestDynamicObjectCanAccessCSharpNonDynamicPropertiesAndFieldsWithPEP8Syntax(string name)
{
using var _ = Py.GIL();

var model = new DynamicFixture();
using var pyModel = model.ToPython();

var pep8Name = name.ToSnakeCase();
pyModel.SetAttr(pep8Name, "Piertotum Locomotor".ToPython());

Assert.IsFalse(model.Properties.ContainsKey(name));
Assert.IsFalse(model.Properties.ContainsKey(pep8Name));

var value = pyModel.GetAttr(pep8Name).As<string>();
Assert.AreEqual("Piertotum Locomotor", value);

var memberInfo = model.GetType().GetMember(name)[0];
var managedValue = memberInfo.MemberType == MemberTypes.Property
? ((PropertyInfo)memberInfo).GetValue(model)
: ((FieldInfo)memberInfo).GetValue(model);
Assert.AreEqual(value, managedValue);
}

public class TestPerson : IComparable, IComparable<TestPerson>
Expand Down
4 changes: 2 additions & 2 deletions src/perf_tests/Python.PerformanceTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
<PackageReference Include="quantconnect.pythonnet" Version="2.0.34" GeneratePathProperty="true">
<PackageReference Include="quantconnect.pythonnet" Version="2.0.35" GeneratePathProperty="true">
<IncludeAssets>compile</IncludeAssets>
</PackageReference>
</ItemGroup>
Expand All @@ -25,7 +25,7 @@
</Target>

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

<Target Name="CopyNewBuild" AfterTargets="Build">
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]

[assembly: AssemblyVersion("2.0.34")]
[assembly: AssemblyFileVersion("2.0.34")]
[assembly: AssemblyVersion("2.0.35")]
[assembly: AssemblyFileVersion("2.0.35")]
2 changes: 1 addition & 1 deletion src/runtime/Python.Runtime.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<RootNamespace>Python.Runtime</RootNamespace>
<AssemblyName>Python.Runtime</AssemblyName>
<PackageId>QuantConnect.pythonnet</PackageId>
<Version>2.0.34</Version>
<Version>2.0.35</Version>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>
Expand Down
33 changes: 19 additions & 14 deletions src/runtime/Types/DynamicClassObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,7 @@ internal DynamicClassObject(Type tp) : base(tp)
/// </summary>
public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference key)
{
var result = Runtime.PyObject_GenericGetAttr(ob, key);

// If AttributeError was raised, we try to get the attribute from the managed object dynamic properties.
if (Exceptions.ExceptionMatches(Exceptions.AttributeError))
if (!TryGetNonDynamicMember(ob, key, out var result))
{
var clrObj = (CLRObject)GetManagedObject(ob)!;

Expand Down Expand Up @@ -103,20 +100,14 @@ public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference k
/// </summary>
public static int tp_setattro(BorrowedReference ob, BorrowedReference key, BorrowedReference val)
{
var clrObj = (CLRObject)GetManagedObject(ob)!;
var name = Runtime.GetManagedString(key);

// If the key corresponds to a valid property or field of the class, we let the default implementation handle it.
var clrObjectType = clrObj.inst.GetType();
var bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
var property = clrObjectType.GetProperty(name, bindingFlags);
var field = property == null ? clrObjectType.GetField(name, bindingFlags) : null;
if ((property != null && property.SetMethod != null) || field != null)
if (TryGetNonDynamicMember(ob, key, out _, clearExceptions: true))
{
return Runtime.PyObject_GenericSetAttr(ob, key, val);
}

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

return 0;
}

private static bool TryGetNonDynamicMember(BorrowedReference ob, BorrowedReference key, out NewReference value, bool clearExceptions = false)
{
value = Runtime.PyObject_GenericGetAttr(ob, key);
// If AttributeError was raised, we try to get the attribute from the managed object dynamic properties.
var result = !Exceptions.ExceptionMatches(Exceptions.AttributeError);

if (clearExceptions)
{
Exceptions.Clear();
}

return result;
}
}
}

0 comments on commit 6172c79

Please sign in to comment.