Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PEP8 compliant names binding #83

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
579 changes: 579 additions & 0 deletions src/embed_tests/ClassManagerTests.cs

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions src/embed_tests/TestUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using NUnit.Framework;

using Python.Runtime;

namespace Python.EmbeddingTest
{
[TestFixture]
public class TestUtil
{
[TestCase("TestCamelCaseString", "test_camel_case_string")]
[TestCase("testCamelCaseString", "test_camel_case_string")]
[TestCase("TestCamelCaseString123 ", "test_camel_case_string123")]
[TestCase("_testCamelCaseString123", "_test_camel_case_string123")]
[TestCase("TestCCS", "test_ccs")]
[TestCase("testCCS", "test_ccs")]
[TestCase("CCSTest", "ccs_test")]
[TestCase("test_CamelCaseString", "test_camel_case_string")]
public void ConvertsNameToSnakeCase(string name, string expected)
{
Assert.AreEqual(expected, name.ToSnakeCase());
}
}
}
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.29" GeneratePathProperty="true">
<PackageReference Include="quantconnect.pythonnet" Version="2.0.30" 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.29\lib\net5.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.30\lib\net5.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
</Target>

<Target Name="CopyNewBuild" AfterTargets="Build">
Expand Down
84 changes: 75 additions & 9 deletions src/runtime/ClassManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,18 +336,21 @@
private static ClassInfo GetClassInfo(Type type, ClassBase impl)
{
var ci = new ClassInfo();
var methods = new Dictionary<string, List<MethodBase>>();
var methods = new Dictionary<string, MethodOverloads>();
MethodInfo meth;
ExtensionType ob;
string name;
Type tp;
int i, n;

MemberInfo[] info = type.GetMembers(BindingFlags);
MemberInfo[] info = type.GetMembers(BindingFlags | BindingFlags.FlattenHierarchy);
var local = new HashSet<string>();
var items = new List<MemberInfo>();
MemberInfo m;

var snakeCasedAttributes = new HashSet<string>();
var originalMemberNames = info.Select(mi => mi.Name).ToHashSet();

// Loop through once to find out which names are declared
for (i = 0; i < info.Length; i++)
{
Expand Down Expand Up @@ -430,6 +433,28 @@
}
}

void CheckForSnakeCasedAttribute(string name)
{
if (snakeCasedAttributes.Remove(name))
{
// If the snake cased attribute is a method, we remove it from the list of methods so that it is not added to the class
methods.Remove(name);
}
}

void AddMember(string name, string snakeCasedName, PyObject obj)
{
CheckForSnakeCasedAttribute(name);

ci.members[name] = obj;

if (!originalMemberNames.Contains(snakeCasedName))
{
ci.members[snakeCasedName] = obj;
snakeCasedAttributes.Add(snakeCasedName);
}
}

for (i = 0; i < items.Count; i++)
{
var mi = (MemberInfo)items[i];
Expand All @@ -448,11 +473,26 @@
if (name == "__init__" && !impl.HasCustomNew())
continue;

CheckForSnakeCasedAttribute(name);
if (!methods.TryGetValue(name, out var methodList))
{
methodList = methods[name] = new List<MethodBase>();
methodList = methods[name] = new MethodOverloads(true);
}
methodList.Add(meth);

if (!OperatorMethod.IsOperatorMethod(meth))
{
var snakeCasedMethodName = name.ToSnakeCase();
if (snakeCasedMethodName != name && !originalMemberNames.Contains(snakeCasedMethodName))
{
if (!methods.TryGetValue(snakeCasedMethodName, out methodList))
{
methodList = methods[snakeCasedMethodName] = new MethodOverloads(false);
}
methodList.Add(meth);
snakeCasedAttributes.Add(snakeCasedMethodName);
}
}
continue;

case MemberTypes.Constructor when !impl.HasCustomNew():
Expand All @@ -465,7 +505,7 @@
name = "__init__";
if (!methods.TryGetValue(name, out methodList))
{
methodList = methods[name] = new List<MethodBase>();
methodList = methods[name] = new MethodOverloads(true);
}
methodList.Add(ctor);
continue;
Expand Down Expand Up @@ -493,7 +533,7 @@
}

ob = new PropertyObject(pi);
ci.members[pi.Name] = ob.AllocObject();
AddMember(pi.Name, pi.Name.ToSnakeCase(), ob.AllocObject());
continue;

case MemberTypes.Field:
Expand All @@ -503,7 +543,14 @@
continue;
}
ob = new FieldObject(fi);
ci.members[mi.Name] = ob.AllocObject();

var pepName = fi.Name.ToSnakeCase();
if (fi.IsLiteral)
{
pepName = pepName.ToUpper();
}

AddMember(fi.Name, pepName, ob.AllocObject());
continue;

case MemberTypes.Event:
Expand All @@ -515,7 +562,7 @@
ob = ei.AddMethod.IsStatic
? new EventBinding(ei)
: new EventObject(ei);
ci.members[ei.Name] = ob.AllocObject();
AddMember(ei.Name, ei.Name.ToSnakeCase(), ob.AllocObject());
continue;

case MemberTypes.NestedType:
Expand All @@ -527,6 +574,7 @@
}
// Note the given instance might be uninitialized
var pyType = GetClass(tp);
CheckForSnakeCasedAttribute(mi.Name);
// make a copy, that could be disposed later
ci.members[mi.Name] = new ReflectedClrType(pyType);
continue;
Expand All @@ -536,9 +584,9 @@
foreach (var iter in methods)
{
name = iter.Key;
var mlist = iter.Value.ToArray();
var mlist = iter.Value.Methods.ToArray();

ob = new MethodObject(type, name, mlist);
ob = new MethodObject(type, name, mlist, isOriginal: iter.Value.IsOriginal);
ci.members[name] = ob.AllocObject();
if (mlist.Any(OperatorMethod.IsOperatorMethod))
{
Expand Down Expand Up @@ -582,7 +630,7 @@
/// </summary>
private class ClassInfo
{
public Indexer? indexer;

Check warning on line 633 in src/runtime/ClassManager.cs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu, 3.10, x64)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 633 in src/runtime/ClassManager.cs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu, 3.8, x64)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 633 in src/runtime/ClassManager.cs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu, 3.9, x64)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 633 in src/runtime/ClassManager.cs

View workflow job for this annotation

GitHub Actions / Build and Test (macos, 3.8, x64)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 633 in src/runtime/ClassManager.cs

View workflow job for this annotation

GitHub Actions / Build and Test (macos, 3.11, x64)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public readonly Dictionary<string, PyObject> members = new();

internal ClassInfo()
Expand All @@ -590,6 +638,24 @@
indexer = null;
}
}

private class MethodOverloads
{
public List<MethodBase> Methods { get; }

public bool IsOriginal { get; }

public MethodOverloads(bool original = true)
{
Methods = new List<MethodBase>();
IsOriginal = original;
}

public void Add(MethodBase method)
{
Methods.Add(method);
}
}
}

}
44 changes: 37 additions & 7 deletions src/runtime/MethodBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,15 @@ public int Count
}

internal void AddMethod(MethodBase m)
{
AddMethod(m, true);
}

internal void AddMethod(MethodBase m, bool isOriginal)
{
// we added a new method so we have to re sort the method list
init = false;
list.Add(new MethodInformation(m, m.GetParameters()));
list.Add(new MethodInformation(m, m.GetParameters(), isOriginal));
}

/// <summary>
Expand Down Expand Up @@ -118,7 +123,7 @@ internal static MethodInfo[] MatchParameters(MethodBase[] mi, Type[] tp)
return result.ToArray();
}

// Given a generic method and the argsTypes previously matched with it,
// Given a generic method and the argsTypes previously matched with it,
// generate the matching method
internal static MethodInfo ResolveGenericMethod(MethodInfo method, Object[] args)
{
Expand Down Expand Up @@ -436,6 +441,7 @@ internal Binding Bind(BorrowedReference inst, BorrowedReference args, BorrowedRe
kwArgDict[keyStr!] = new PyObject(value);
}
}
var hasNamedArgs = kwArgDict != null && kwArgDict.Count > 0;

// Fetch our methods we are going to attempt to match and bind too.
var methods = info == null ? GetMethods()
Expand All @@ -447,9 +453,10 @@ internal Binding Bind(BorrowedReference inst, BorrowedReference args, BorrowedRe
// Relevant method variables
var mi = methodInformation.MethodBase;
var pi = methodInformation.ParameterInfo;
// Avoid accessing the parameter names property unless necessary
var paramNames = hasNamedArgs ? methodInformation.ParameterNames : Array.Empty<string>();
int pyArgCount = (int)Runtime.PyTuple_Size(args);


// Special case for operators
bool isOperator = OperatorMethod.IsOperatorMethod(mi);
// Binary operator methods will have 2 CLR args but only one Python arg
Expand Down Expand Up @@ -479,6 +486,7 @@ internal Binding Bind(BorrowedReference inst, BorrowedReference args, BorrowedRe
pyArgCount,
kwArgDict,
pi,
paramNames,
out bool paramsArray,
out ArrayList defaultArgList))
{
Expand All @@ -497,8 +505,8 @@ internal Binding Bind(BorrowedReference inst, BorrowedReference args, BorrowedRe
object arg; // Python -> Clr argument

// Check our KWargs for this parameter
bool hasNamedParam = kwArgDict == null ? false : kwArgDict.TryGetValue(parameter.Name, out tempPyObject);
if(tempPyObject != null)
bool hasNamedParam = kwArgDict == null ? false : kwArgDict.TryGetValue(paramNames[paramIndex], out tempPyObject);
if (tempPyObject != null)
{
op = tempPyObject;
}
Expand Down Expand Up @@ -762,10 +770,16 @@ static BorrowedReference HandleParamsArray(BorrowedReference args, int arrayStar
/// This helper method will perform an initial check to determine if we found a matching
/// method based on its parameters count and type <see cref="Bind(IntPtr,IntPtr,IntPtr,MethodBase,MethodInfo[])"/>
/// </summary>
/// <remarks>
/// We required both the parameters info and the parameters names to perform this check.
/// The CLR method parameters info is required to match the parameters count and type.
/// The names are required to perform an accurate match, since the method can be the snake-cased version.
/// </remarks>
private bool CheckMethodArgumentsMatch(int clrArgCount,
int pyArgCount,
Dictionary<string, PyObject> kwargDict,
ParameterInfo[] parameterInfo,
string[] parameterNames,
out bool paramsArray,
out ArrayList defaultArgList)
{
Expand All @@ -788,7 +802,7 @@ private bool CheckMethodArgumentsMatch(int clrArgCount,
{
// If the method doesn't have all of these kw args, it is not a match
// Otherwise just continue on to see if it is a match
if (!kwargDict.All(x => parameterInfo.Any(pi => x.Key == pi.Name)))
if (!kwargDict.All(x => parameterNames.Any(paramName => x.Key == paramName)))
{
return false;
}
Expand All @@ -808,7 +822,7 @@ private bool CheckMethodArgumentsMatch(int clrArgCount,
defaultArgList = new ArrayList();
for (var v = pyArgCount; v < clrArgCount && match; v++)
{
if (kwargDict != null && kwargDict.ContainsKey(parameterInfo[v].Name))
if (kwargDict != null && kwargDict.ContainsKey(parameterNames[v]))
{
// we have a keyword argument for this parameter,
// no need to check for a default parameter, but put a null
Expand Down Expand Up @@ -973,14 +987,30 @@ internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference a
[Serializable]
internal class MethodInformation
{
private Lazy<string[]> _parametersNames;

public MethodBase MethodBase { get; }

public ParameterInfo[] ParameterInfo { get; }

public bool IsOriginal { get; }

public string[] ParameterNames { get { return _parametersNames.Value; } }

public MethodInformation(MethodBase methodBase, ParameterInfo[] parameterInfo)
: this(methodBase, parameterInfo, true)
{
}

public MethodInformation(MethodBase methodBase, ParameterInfo[] parameterInfo, bool isOriginal)
{
MethodBase = methodBase;
ParameterInfo = parameterInfo;
IsOriginal = isOriginal;

_parametersNames = new Lazy<string[]>(() => IsOriginal
? ParameterInfo.Select(pi => pi.Name).ToArray()
: ParameterInfo.Select(pi => pi.Name.ToSnakeCase()).ToArray());
}

public override string ToString()
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.29")]
[assembly: AssemblyFileVersion("2.0.29")]
[assembly: AssemblyVersion("2.0.30")]
[assembly: AssemblyFileVersion("2.0.30")]
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.29</Version>
<Version>2.0.30</Version>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>
Expand Down
5 changes: 3 additions & 2 deletions src/runtime/Types/MethodObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ internal class MethodObject : ExtensionType
internal PyString? doc;
internal MaybeType type;

public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads = MethodBinder.DefaultAllowThreads)
public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads = MethodBinder.DefaultAllowThreads,
bool isOriginal = true)
{
this.type = type;
this.name = name;
Expand All @@ -37,7 +38,7 @@ public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_t
foreach (MethodBase item in info)
{
this.infoList.Add(item);
binder.AddMethod(item);
binder.AddMethod(item, isOriginal);
if (item.IsStatic)
{
this.is_static = true;
Expand Down
Loading
Loading