Skip to content

Commit a6ad62f

Browse files
authored
PEP8 bug fixes (#85)
* feat: static readonly fields and properties that are callable also lower cased * fix: bind snake-cased field matching existing private field * fix: snake-case string conversion digits handling * Bump version to 2.0.32
1 parent b9f3793 commit a6ad62f

File tree

7 files changed

+260
-44
lines changed

7 files changed

+260
-44
lines changed

src/embed_tests/ClassManagerTests.cs

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Reflection;
45

56
using NUnit.Framework;
67

@@ -104,7 +105,130 @@ public string JoinToString(string thisIsAStringParameter,
104105
return string.Join("-", thisIsAStringParameter, thisIsACharParameter, thisIsAnIntParameter, thisIsAFloatParameter,
105106
thisIsADoubleParameter, thisIsADecimalParameter ?? 123.456m, thisIsABoolParameter, string.Format("{0:MMddyyyy}", thisIsADateTimeParameter));
106107
}
107-
}
108+
109+
public static Action StaticReadonlyActionProperty { get; } = () => Throw();
110+
public static Action<int> StaticReadonlyActionWithParamsProperty { get; } = (i) => Throw();
111+
public static Func<int> StaticReadonlyFuncProperty { get; } = () =>
112+
{
113+
Throw();
114+
return 42;
115+
};
116+
public static Func<int, int> StaticReadonlyFuncWithParamsProperty { get; } = (i) =>
117+
{
118+
Throw();
119+
return i * 2;
120+
};
121+
122+
public static Action StaticReadonlyExpressionBodiedActionProperty => () => Throw();
123+
public static Action<int> StaticReadonlyExpressionBodiedActionWithParamsProperty => (i) => Throw();
124+
public static Func<int> StaticReadonlyExpressionBodiedFuncProperty => () =>
125+
{
126+
Throw();
127+
return 42;
128+
};
129+
public static Func<int, int> StaticReadonlyExpressionBodiedFuncWithParamsProperty => (i) =>
130+
{
131+
Throw();
132+
return i * 2;
133+
};
134+
135+
public static readonly Action StaticReadonlyActionField = () => Throw();
136+
public static readonly Action<int> StaticReadonlyActionWithParamsField = (i) => Throw();
137+
public static readonly Func<int> StaticReadonlyFuncField = () =>
138+
{
139+
Throw();
140+
return 42;
141+
};
142+
public static readonly Func<int, int> StaticReadonlyFuncWithParamsField = (i) =>
143+
{
144+
Throw();
145+
return i * 2;
146+
};
147+
148+
public static readonly Action StaticReadonlyExpressionBodiedActionField = () => Throw();
149+
public static readonly Action<int> StaticReadonlyExpressionBodiedActionWithParamsField = (i) => Throw();
150+
public static readonly Func<int> StaticReadonlyExpressionBodiedFuncField = () =>
151+
{
152+
Throw();
153+
return 42;
154+
};
155+
public static readonly Func<int, int> StaticReadonlyExpressionBodiedFuncWithParamsField = (i) =>
156+
{
157+
Throw();
158+
return i * 2;
159+
};
160+
161+
private static void Throw() => throw new Exception("Pepe");
162+
}
163+
164+
[TestCase("StaticReadonlyActionProperty", "static_readonly_action_property", new object[] { })]
165+
[TestCase("StaticReadonlyActionWithParamsProperty", "static_readonly_action_with_params_property", new object[] { 42 })]
166+
[TestCase("StaticReadonlyFuncProperty", "static_readonly_func_property", new object[] { })]
167+
[TestCase("StaticReadonlyFuncWithParamsProperty", "static_readonly_func_with_params_property", new object[] { 42 })]
168+
[TestCase("StaticReadonlyExpressionBodiedActionProperty", "static_readonly_expression_bodied_action_property", new object[] { })]
169+
[TestCase("StaticReadonlyExpressionBodiedActionWithParamsProperty", "static_readonly_expression_bodied_action_with_params_property", new object[] { 42 })]
170+
[TestCase("StaticReadonlyExpressionBodiedFuncProperty", "static_readonly_expression_bodied_func_property", new object[] { })]
171+
[TestCase("StaticReadonlyExpressionBodiedFuncWithParamsProperty", "static_readonly_expression_bodied_func_with_params_property", new object[] { 42 })]
172+
[TestCase("StaticReadonlyActionField", "static_readonly_action_field", new object[] { })]
173+
[TestCase("StaticReadonlyActionWithParamsField", "static_readonly_action_with_params_field", new object[] { 42 })]
174+
[TestCase("StaticReadonlyFuncField", "static_readonly_func_field", new object[] { })]
175+
[TestCase("StaticReadonlyFuncWithParamsField", "static_readonly_func_with_params_field", new object[] { 42 })]
176+
[TestCase("StaticReadonlyExpressionBodiedActionField", "static_readonly_expression_bodied_action_field", new object[] { })]
177+
[TestCase("StaticReadonlyExpressionBodiedActionWithParamsField", "static_readonly_expression_bodied_action_with_params_field", new object[] { 42 })]
178+
[TestCase("StaticReadonlyExpressionBodiedFuncField", "static_readonly_expression_bodied_func_field", new object[] { })]
179+
[TestCase("StaticReadonlyExpressionBodiedFuncWithParamsField", "static_readonly_expression_bodied_func_with_params_field", new object[] { 42 })]
180+
public void StaticReadonlyCallableFieldsAndPropertiesAreBothUpperAndLowerCased(string propertyName, string snakeCasedName, object[] args)
181+
{
182+
using var obj = new SnakeCaseNamesTesClass().ToPython();
183+
184+
var lowerCasedName = snakeCasedName.ToLowerInvariant();
185+
var upperCasedName = snakeCasedName.ToUpperInvariant();
186+
187+
var memberInfo = typeof(SnakeCaseNamesTesClass).GetMember(propertyName).First();
188+
var callableType = memberInfo switch
189+
{
190+
PropertyInfo propertyInfo => propertyInfo.PropertyType,
191+
FieldInfo fieldInfo => fieldInfo.FieldType,
192+
_ => throw new InvalidOperationException()
193+
};
194+
195+
var property = obj.GetAttr(propertyName).AsManagedObject(callableType);
196+
var lowerCasedProperty = obj.GetAttr(lowerCasedName).AsManagedObject(callableType);
197+
var upperCasedProperty = obj.GetAttr(upperCasedName).AsManagedObject(callableType);
198+
199+
Assert.IsNotNull(property);
200+
Assert.IsNotNull(property as MulticastDelegate);
201+
Assert.AreSame(property, lowerCasedProperty);
202+
Assert.AreSame(property, upperCasedProperty);
203+
204+
var call = () =>
205+
{
206+
try
207+
{
208+
(property as Delegate).DynamicInvoke(args);
209+
}
210+
catch (TargetInvocationException e)
211+
{
212+
throw e.InnerException;
213+
}
214+
};
215+
216+
var exception = Assert.Throws<Exception>(() => call());
217+
Assert.AreEqual("Pepe", exception.Message);
218+
}
219+
220+
[TestCase("PublicStaticReadonlyStringField", "public_static_readonly_string_field")]
221+
[TestCase("PublicStaticReadonlyStringGetterOnlyProperty", "public_static_readonly_string_getter_only_property")]
222+
public void NonCallableStaticReadonlyFieldsAndPropertiesAreOnlyUpperCased(string propertyName, string snakeCasedName)
223+
{
224+
using var obj = new SnakeCaseNamesTesClass().ToPython();
225+
var lowerCasedName = snakeCasedName.ToLowerInvariant();
226+
var upperCasedName = snakeCasedName.ToUpperInvariant();
227+
228+
Assert.IsTrue(obj.HasAttr(propertyName));
229+
Assert.IsTrue(obj.HasAttr(upperCasedName));
230+
Assert.IsFalse(obj.HasAttr(lowerCasedName));
231+
}
108232

109233
[TestCase("AddNumbersAndGetHalf", "add_numbers_and_get_half")]
110234
[TestCase("AddNumbersAndGetHalf_Static", "add_numbers_and_get_half_static")]
@@ -528,6 +652,9 @@ def SetEnumValue3SnakeCase(obj):
528652

529653
private class AlreadyDefinedSnakeCaseMemberTestBaseClass
530654
{
655+
private int private_field = 123;
656+
public int PrivateField = 333;
657+
531658
public virtual int SomeIntProperty { get; set; } = 123;
532659

533660
public int some_int_property { get; set; } = 321;
@@ -593,6 +720,14 @@ public void DoesntBindSnakeCasedMemberIfAlreadyOriginallyDefinedAsMethodInBaseCl
593720
Assert.AreEqual(654, method.Invoke().As<int>());
594721
}
595722

723+
[Test]
724+
public void BindsMemberWithSnakeCasedNameMatchingExistingPrivateMember()
725+
{
726+
using var obj = new AlreadyDefinedSnakeCaseMemberTestBaseClass().ToPython();
727+
728+
Assert.AreEqual(333, obj.GetAttr("private_field").As<int>());
729+
}
730+
596731
private abstract class AlreadyDefinedSnakeCaseMemberTestBaseAbstractClass
597732
{
598733
public abstract int AbstractProperty { get; }

src/embed_tests/TestUtil.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@ public class TestUtil
1313

1414
[TestCase("TestCamelCaseString", "test_camel_case_string")]
1515
[TestCase("testCamelCaseString", "test_camel_case_string")]
16-
[TestCase("TestCamelCaseString123 ", "test_camel_case_string123")]
17-
[TestCase("_testCamelCaseString123", "_test_camel_case_string123")]
16+
[TestCase("TestCamelCaseString123 ", "test_camel_case_string_123")]
17+
[TestCase("_testCamelCaseString123", "_test_camel_case_string_123")]
18+
[TestCase("_testCamelCaseString123WithSuffix", "_test_camel_case_string_123_with_suffix")]
19+
[TestCase("_testCamelCaseString123withSuffix", "_test_camel_case_string_123_with_suffix")]
1820
[TestCase("TestCCS", "test_ccs")]
1921
[TestCase("testCCS", "test_ccs")]
2022
[TestCase("CCSTest", "ccs_test")]
2123
[TestCase("test_CamelCaseString", "test_camel_case_string")]
24+
[TestCase("SP500EMini", "sp_500_e_mini")]
25+
[TestCase("Sentiment30Days", "sentiment_30_days")]
26+
[TestCase("PriceChange1m", "price_change_1m")] // A single digit followed by a lowercase letter
2227
public void ConvertsNameToSnakeCase(string name, string expected)
2328
{
2429
Assert.AreEqual(expected, name.ToSnakeCase());

src/perf_tests/Python.PerformanceTests.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFrameworks>net6.0</TargetFrameworks>
@@ -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.31" GeneratePathProperty="true">
16+
<PackageReference Include="quantconnect.pythonnet" Version="2.0.32" 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.31\lib\net5.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
28+
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.32\lib\net5.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
2929
</Target>
3030

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

src/runtime/ClassManager.cs

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Dynamic;
55
using System.Linq;
66
using System.Reflection;
7-
using System.Runtime.InteropServices;
87
using System.Security;
98

109
using Python.Runtime.StateSerialization;
@@ -304,28 +303,28 @@ internal static bool ShouldBindField(FieldInfo fi)
304303

305304
internal static bool ShouldBindProperty(PropertyInfo pi)
306305
{
307-
MethodInfo? mm;
308-
try
309-
{
310-
mm = pi.GetGetMethod(true);
311-
if (mm == null)
312-
{
313-
mm = pi.GetSetMethod(true);
314-
}
315-
}
316-
catch (SecurityException)
317-
{
318-
// GetGetMethod may try to get a method protected by
319-
// StrongNameIdentityPermission - effectively private.
320-
return false;
321-
}
322-
306+
MethodInfo? mm;
307+
try
308+
{
309+
mm = pi.GetGetMethod(true);
323310
if (mm == null)
324311
{
325-
return false;
312+
mm = pi.GetSetMethod(true);
326313
}
314+
}
315+
catch (SecurityException)
316+
{
317+
// GetGetMethod may try to get a method protected by
318+
// StrongNameIdentityPermission - effectively private.
319+
return false;
320+
}
321+
322+
if (mm == null)
323+
{
324+
return false;
325+
}
327326

328-
return ShouldBindMethod(mm);
327+
return ShouldBindMethod(mm);
329328
}
330329

331330
internal static bool ShouldBindEvent(EventInfo ei)
@@ -349,7 +348,17 @@ private static ClassInfo GetClassInfo(Type type, ClassBase impl)
349348
MemberInfo m;
350349

351350
var snakeCasedAttributes = new HashSet<string>();
352-
var originalMemberNames = info.Select(mi => mi.Name).ToHashSet();
351+
var originalMemberNames = info
352+
.Where(mi => mi switch
353+
{
354+
MethodInfo mei => ShouldBindMethod(mei),
355+
FieldInfo fi => ShouldBindField(fi),
356+
PropertyInfo pi => ShouldBindProperty(pi),
357+
EventInfo ei => ShouldBindEvent(ei),
358+
_ => false
359+
})
360+
.Select(mi => mi.Name)
361+
.ToHashSet();
353362

354363
// Loop through once to find out which names are declared
355364
for (i = 0; i < info.Length; i++)
@@ -442,19 +451,32 @@ void CheckForSnakeCasedAttribute(string name)
442451
}
443452
}
444453

445-
void AddMember(string name, string snakeCasedName, PyObject obj)
454+
void AddSnakeCasedMember(string snakeCasedName, PyObject obj)
446455
{
447-
CheckForSnakeCasedAttribute(name);
448-
449-
ci.members[name] = obj;
450-
451456
if (!originalMemberNames.Contains(snakeCasedName))
452457
{
453458
ci.members[snakeCasedName] = obj;
454459
snakeCasedAttributes.Add(snakeCasedName);
455460
}
456461
}
457462

463+
void AddMember(string name, string snakeCasedName, bool isStaticReadonlyCallable, ExtensionType obj)
464+
{
465+
CheckForSnakeCasedAttribute(name);
466+
467+
var allocatedObj = obj.AllocObject();
468+
ci.members[name] = allocatedObj;
469+
470+
AddSnakeCasedMember(snakeCasedName, allocatedObj);
471+
472+
// static readonly callable fields and properties snake-case version will be available
473+
// both upper-cased (as constants) and lower-cased (as regular fields)
474+
if (isStaticReadonlyCallable)
475+
{
476+
AddSnakeCasedMember(snakeCasedName.ToLowerInvariant(), allocatedObj);
477+
}
478+
}
479+
458480
for (i = 0; i < items.Count; i++)
459481
{
460482
var mi = (MemberInfo)items[i];
@@ -513,7 +535,7 @@ void AddMember(string name, string snakeCasedName, PyObject obj)
513535
case MemberTypes.Property:
514536
var pi = (PropertyInfo)mi;
515537

516-
if(!ShouldBindProperty(pi))
538+
if (!ShouldBindProperty(pi))
517539
{
518540
continue;
519541
}
@@ -533,7 +555,7 @@ void AddMember(string name, string snakeCasedName, PyObject obj)
533555
}
534556

535557
ob = new PropertyObject(pi);
536-
AddMember(pi.Name, pi.ToSnakeCase(), ob.AllocObject());
558+
AddMember(pi.Name, pi.ToSnakeCase(), pi.IsStaticReadonlyCallable(), ob);
537559
continue;
538560

539561
case MemberTypes.Field:
@@ -543,7 +565,7 @@ void AddMember(string name, string snakeCasedName, PyObject obj)
543565
continue;
544566
}
545567
ob = new FieldObject(fi);
546-
AddMember(fi.Name, fi.ToSnakeCase(), ob.AllocObject());
568+
AddMember(fi.Name, fi.ToSnakeCase(), fi.IsStaticReadonlyCallable(), ob);
547569
continue;
548570

549571
case MemberTypes.Event:
@@ -555,7 +577,7 @@ void AddMember(string name, string snakeCasedName, PyObject obj)
555577
ob = ei.AddMethod.IsStatic
556578
? new EventBinding(ei)
557579
: new EventObject(ei);
558-
AddMember(ei.Name, ei.Name.ToSnakeCase(), ob.AllocObject());
580+
AddMember(ei.Name, ei.Name.ToSnakeCase(), false, ob);
559581
continue;
560582

561583
case MemberTypes.NestedType:
@@ -601,7 +623,8 @@ void AddMember(string name, string snakeCasedName, PyObject obj)
601623
var parent = type.BaseType;
602624
while (parent != null && ci.indexer == null)
603625
{
604-
foreach (var prop in parent.GetProperties()) {
626+
foreach (var prop in parent.GetProperties())
627+
{
605628
var args = prop.GetIndexParameters();
606629
if (args.GetLength(0) > 0)
607630
{

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.31")]
8-
[assembly: AssemblyFileVersion("2.0.31")]
7+
[assembly: AssemblyVersion("2.0.32")]
8+
[assembly: AssemblyFileVersion("2.0.32")]

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.31</Version>
8+
<Version>2.0.32</Version>
99
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
1010
<PackageLicenseFile>LICENSE</PackageLicenseFile>
1111
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>

0 commit comments

Comments
 (0)