Skip to content

Commit 6c2c798

Browse files
authored
Merge pull request #13 from QuantConnect/lean
Update with Lean enhancements
2 parents 4968832 + 65ac279 commit 6c2c798

File tree

3 files changed

+195
-3
lines changed

3 files changed

+195
-3
lines changed

src/runtime/converter.cs

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ private Converter()
3030
private static Type flagsType;
3131
private static Type boolType;
3232
private static Type typeType;
33+
private static IntPtr decimalCtor;
34+
private static IntPtr dateTimeCtor;
35+
private static IntPtr timeSpanCtor;
36+
private static IntPtr tzInfoCtor;
3337

3438
static Converter()
3539
{
@@ -45,6 +49,37 @@ static Converter()
4549
flagsType = typeof(FlagsAttribute);
4650
boolType = typeof(Boolean);
4751
typeType = typeof(Type);
52+
53+
IntPtr decimalMod = Runtime.PyImport_ImportModule("decimal");
54+
if (decimalMod == null) throw new PythonException();
55+
56+
IntPtr dateTimeMod = Runtime.PyImport_ImportModule("datetime");
57+
if (dateTimeMod == null) throw new PythonException();
58+
59+
decimalCtor = Runtime.PyObject_GetAttrString(decimalMod, "Decimal");
60+
if (decimalCtor == null) throw new PythonException();
61+
62+
dateTimeCtor = Runtime.PyObject_GetAttrString(dateTimeMod, "datetime");
63+
if (dateTimeCtor == null) throw new PythonException();
64+
65+
timeSpanCtor = Runtime.PyObject_GetAttrString(dateTimeMod, "timedelta");
66+
if (timeSpanCtor == null) throw new PythonException();
67+
68+
IntPtr tzInfoMod = PythonEngine.ModuleFromString("custom_tzinfo", @"
69+
from datetime import timedelta, tzinfo
70+
class GMT(tzinfo):
71+
def __init__(self, hours, minutes):
72+
self.hours = hours
73+
self.minutes = minutes
74+
def utcoffset(self, dt):
75+
return timedelta(hours=self.hours, minutes=self.minutes)
76+
def tzname(self, dt):
77+
return f'GMT {self.hours:00}:{self.minutes:00}'
78+
def dst (self, dt):
79+
return timedelta(0)").Handle;
80+
81+
tzInfoCtor = Runtime.PyObject_GetAttrString(tzInfoMod, "GMT");
82+
if (tzInfoCtor == null) throw new PythonException();
4883
}
4984

5085

@@ -71,6 +106,9 @@ internal static Type GetTypeByAlias(IntPtr op)
71106
if (op == Runtime.PyBoolType)
72107
return boolType;
73108

109+
if (op == Runtime.PyDecimalType)
110+
return decimalType;
111+
74112
return null;
75113
}
76114

@@ -100,6 +138,9 @@ internal static IntPtr GetPythonTypeByAlias(Type op)
100138
if (op == boolType)
101139
return Runtime.PyBoolType;
102140

141+
if (op == decimalType)
142+
return Runtime.PyDecimalType;
143+
103144
return IntPtr.Zero;
104145
}
105146

@@ -177,6 +218,14 @@ internal static IntPtr ToPython(object value, Type type)
177218
switch (tc)
178219
{
179220
case TypeCode.Object:
221+
if (value is TimeSpan)
222+
{
223+
var timespan = (TimeSpan)value;
224+
225+
IntPtr timeSpanArgs = Runtime.PyTuple_New(1);
226+
Runtime.PyTuple_SetItem(timeSpanArgs, 0, Runtime.PyFloat_FromDouble(timespan.TotalDays));
227+
return Runtime.PyObject_CallObject(timeSpanCtor, timeSpanArgs);
228+
}
180229
return CLRObject.GetInstHandle(value, type);
181230

182231
case TypeCode.String:
@@ -229,6 +278,29 @@ internal static IntPtr ToPython(object value, Type type)
229278
case TypeCode.UInt64:
230279
return Runtime.PyLong_FromUnsignedLongLong((ulong)value);
231280

281+
case TypeCode.Decimal:
282+
string d2s = ((decimal)value).ToString(nfi);
283+
IntPtr d2p = Runtime.PyString_FromString(d2s);
284+
IntPtr decimalArgs = Runtime.PyTuple_New(1);
285+
Runtime.PyTuple_SetItem(decimalArgs, 0, d2p);
286+
287+
return Runtime.PyObject_CallObject(decimalCtor, decimalArgs);
288+
289+
case TypeCode.DateTime:
290+
var datetime = (DateTime)value;
291+
292+
IntPtr dateTimeArgs = Runtime.PyTuple_New(8);
293+
Runtime.PyTuple_SetItem(dateTimeArgs, 0, Runtime.PyInt_FromInt32(datetime.Year));
294+
Runtime.PyTuple_SetItem(dateTimeArgs, 1, Runtime.PyInt_FromInt32(datetime.Month));
295+
Runtime.PyTuple_SetItem(dateTimeArgs, 2, Runtime.PyInt_FromInt32(datetime.Day));
296+
Runtime.PyTuple_SetItem(dateTimeArgs, 3, Runtime.PyInt_FromInt32(datetime.Hour));
297+
Runtime.PyTuple_SetItem(dateTimeArgs, 4, Runtime.PyInt_FromInt32(datetime.Minute));
298+
Runtime.PyTuple_SetItem(dateTimeArgs, 5, Runtime.PyInt_FromInt32(datetime.Second));
299+
Runtime.PyTuple_SetItem(dateTimeArgs, 6, Runtime.PyInt_FromInt32(datetime.Millisecond));
300+
Runtime.PyTuple_SetItem(dateTimeArgs, 7, TzInfo(datetime.Kind));
301+
302+
return Runtime.PyObject_CallObject(dateTimeCtor, dateTimeArgs);
303+
232304
default:
233305
if (value is IEnumerable)
234306
{
@@ -250,6 +322,16 @@ internal static IntPtr ToPython(object value, Type type)
250322
}
251323
}
252324

325+
private static IntPtr TzInfo(DateTimeKind kind)
326+
{
327+
if (kind == DateTimeKind.Unspecified) return Runtime.PyNone;
328+
var offset = kind == DateTimeKind.Local ? DateTimeOffset.Now.Offset : TimeSpan.Zero;
329+
IntPtr tzInfoArgs = Runtime.PyTuple_New(2);
330+
Runtime.PyTuple_SetItem(tzInfoArgs, 0, Runtime.PyFloat_FromDouble(offset.Hours));
331+
Runtime.PyTuple_SetItem(tzInfoArgs, 1, Runtime.PyFloat_FromDouble(offset.Minutes));
332+
return Runtime.PyObject_CallObject(tzInfoCtor, tzInfoArgs);
333+
}
334+
253335

254336
/// <summary>
255337
/// In a few situations, we don't have any advisory type information
@@ -436,6 +518,26 @@ internal static bool ToManagedValue(IntPtr value, Type obType,
436518
return false;
437519
}
438520

521+
var underlyingType = Nullable.GetUnderlyingType(obType);
522+
if (underlyingType != null)
523+
{
524+
return ToManagedValue(value, underlyingType, out result, setError);
525+
}
526+
527+
var opImplicit = obType.GetMethod("op_Implicit", new[] { obType });
528+
if (opImplicit != null)
529+
{
530+
if (ToManagedValue(value, opImplicit.ReturnType, out result, setError))
531+
{
532+
opImplicit = obType.GetMethod("op_Implicit", new[] { result.GetType() });
533+
if (opImplicit != null)
534+
{
535+
result = opImplicit.Invoke(null, new[] { result });
536+
}
537+
return opImplicit != null;
538+
}
539+
}
540+
439541
return ToPrimitive(value, obType, out result, setError);
440542
}
441543

@@ -452,6 +554,32 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
452554

453555
switch (tc)
454556
{
557+
case TypeCode.Object:
558+
if (obType == typeof(TimeSpan))
559+
{
560+
op = Runtime.PyObject_Str(value);
561+
TimeSpan ts;
562+
var arr = Runtime.GetManagedString(op).Split(',');
563+
string sts = arr.Length == 1 ? arr[0] : arr[1];
564+
if (!TimeSpan.TryParse(sts, out ts))
565+
{
566+
goto type_error;
567+
}
568+
Runtime.XDecref(op);
569+
570+
int days = 0;
571+
if (arr.Length > 1)
572+
{
573+
if (!int.TryParse(arr[0].Split(' ')[0].Trim(), out days))
574+
{
575+
goto type_error;
576+
}
577+
}
578+
result = ts.Add(TimeSpan.FromDays(days));
579+
return true;
580+
}
581+
break;
582+
455583
case TypeCode.String:
456584
string st = Runtime.GetManagedString(value);
457585
if (st == null)
@@ -801,6 +929,30 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
801929
Runtime.XDecref(op);
802930
result = d;
803931
return true;
932+
933+
case TypeCode.Decimal:
934+
op = Runtime.PyObject_Str(value);
935+
decimal m;
936+
string sm = Runtime.GetManagedString(op);
937+
if (!Decimal.TryParse(sm, NumberStyles.Number, nfi, out m))
938+
{
939+
goto type_error;
940+
}
941+
Runtime.XDecref(op);
942+
result = m;
943+
return true;
944+
945+
case TypeCode.DateTime:
946+
op = Runtime.PyObject_Str(value);
947+
DateTime dt;
948+
string sdt = Runtime.GetManagedString(op);
949+
if (!DateTime.TryParse(sdt, out dt))
950+
{
951+
goto type_error;
952+
}
953+
Runtime.XDecref(op);
954+
result = sdt.EndsWith("+00:00") ? dt.ToUniversalTime() : dt;
955+
return true;
804956
}
805957

806958

@@ -845,7 +997,7 @@ private static bool ToArray(IntPtr value, Type obType, out object result, bool s
845997
int size = Runtime.PySequence_Size(value);
846998
result = null;
847999

848-
if (size < 0)
1000+
if (size < 0 || elementType.IsGenericType)
8491001
{
8501002
if (setError)
8511003
{

src/runtime/methodbinder.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,13 @@ internal static int GetPrecedence(MethodBase mi)
186186
val += ArgPrecedence(pi[i].ParameterType);
187187
}
188188

189+
var info = mi as MethodInfo;
190+
if (info != null)
191+
{
192+
val += ArgPrecedence(info.ReturnType);
193+
val += mi.DeclaringType == mi.ReflectedType ? 0 : 3000;
194+
}
195+
189196
return val;
190197
}
191198

@@ -200,6 +207,11 @@ internal static int ArgPrecedence(Type t)
200207
return 3000;
201208
}
202209

210+
if (t.IsAssignableFrom(typeof(PyObject)))
211+
{
212+
return -1;
213+
}
214+
203215
TypeCode tc = Type.GetTypeCode(t);
204216
// TODO: Clean up
205217
switch (tc)
@@ -394,14 +406,33 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
394406
}
395407
if (!typematch)
396408
{
409+
// this takes care of nullables
410+
var underlyingType = Nullable.GetUnderlyingType(pi[n].ParameterType);
411+
if (underlyingType == null)
412+
{
413+
underlyingType = pi[n].ParameterType;
414+
}
397415
// this takes care of enum values
398-
TypeCode argtypecode = Type.GetTypeCode(pi[n].ParameterType);
416+
TypeCode argtypecode = Type.GetTypeCode(underlyingType);
399417
TypeCode paramtypecode = Type.GetTypeCode(clrtype);
400418
if (argtypecode == paramtypecode)
401419
{
402420
typematch = true;
403421
clrtype = pi[n].ParameterType;
404422
}
423+
// accepts non-decimal numbers in decimal parameters
424+
if (underlyingType == typeof(decimal))
425+
{
426+
clrtype = pi[n].ParameterType;
427+
typematch = Converter.ToManaged(op, clrtype, out arg, false);
428+
}
429+
// this takes care of implicit conversions
430+
var opImplicit = pi[n].ParameterType.GetMethod("op_Implicit", new[] { clrtype });
431+
if (opImplicit != null)
432+
{
433+
typematch = opImplicit.ReturnType == pi[n].ParameterType;
434+
clrtype = pi[n].ParameterType;
435+
}
405436
}
406437
Runtime.XDecref(pyoptype);
407438
if (!typematch)
@@ -507,7 +538,7 @@ internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase i
507538

508539
if (binding == null)
509540
{
510-
Exceptions.SetError(Exceptions.TypeError, "No method matches given arguments");
541+
Exceptions.SetError(Exceptions.TypeError, $"No method matches given arguments for {methodinfo[0].Name}");
511542
return IntPtr.Zero;
512543
}
513544

src/runtime/runtime.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,14 @@ internal static void Initialize()
296296
PyFloatType = PyObject_Type(op);
297297
XDecref(op);
298298

299+
IntPtr decimalMod = PyImport_ImportModule("decimal");
300+
IntPtr decimalCtor = PyObject_GetAttrString(decimalMod, "Decimal");
301+
op = PyObject_CallObject(decimalCtor, IntPtr.Zero);
302+
PyDecimalType = PyObject_Type(op);
303+
XDecref(op);
304+
XDecref(decimalMod);
305+
XDecref(decimalCtor);
306+
299307
#if PYTHON3
300308
PyClassType = IntPtr.Zero;
301309
PyInstanceType = IntPtr.Zero;
@@ -389,6 +397,7 @@ internal static int AtExit()
389397
internal static IntPtr PyBoolType;
390398
internal static IntPtr PyNoneType;
391399
internal static IntPtr PyTypeType;
400+
internal static IntPtr PyDecimalType;
392401

393402
#if PYTHON3
394403
internal static IntPtr PyBytesType;

0 commit comments

Comments
 (0)