/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using Python.Runtime; using System.Collections.Generic; namespace QuantConnect.Python { /// /// Base class for Python wrapper classes /// public class BasePythonWrapper { private PyObject _instance; private object _underlyingClrObject; private Dictionary _pythonMethods; private Dictionary _pythonPropertyNames; private readonly bool _validateInterface; /// /// Gets the underlying python instance /// protected PyObject Instance => _instance; /// /// Creates a new instance of the class /// /// Whether to perform validations for interface implementation public BasePythonWrapper(bool validateInterface = true) { _pythonMethods = new(); _pythonPropertyNames = new(); _validateInterface = validateInterface; } /// /// Creates a new instance of the class with the specified instance /// /// The underlying python instance /// Whether to perform validations for interface implementation public BasePythonWrapper(PyObject instance, bool validateInterface = true) : this(validateInterface) { SetPythonInstance(instance); } /// /// Sets the python instance /// /// The underlying python instance public void SetPythonInstance(PyObject instance) { if (_instance != null) { _pythonMethods.Clear(); _pythonPropertyNames.Clear(); } _instance = _validateInterface ? instance.ValidateImplementationOf() : instance; _instance.TryConvert(out _underlyingClrObject); } /// /// Gets the Python instance property with the specified name /// /// The name of the property public T GetProperty(string propertyName) { using var _ = Py.GIL(); return GetProperty(propertyName).GetAndDispose(); } /// /// Gets the Python instance property with the specified name /// /// The name of the property public PyObject GetProperty(string propertyName) { using var _ = Py.GIL(); return _instance.GetAttr(GetPropertyName(propertyName)); } /// /// Sets the Python instance property with the specified name /// /// The name of the property /// The property value public void SetProperty(string propertyName, object value) { using var _ = Py.GIL(); _instance.SetAttr(GetPropertyName(propertyName), value.ToPython()); } /// /// Gets the Python instance event with the specified name /// /// The name of the event public dynamic GetEvent(string name) { using var _ = Py.GIL(); return _instance.GetAttr(GetPropertyName(name, true)); } /// /// Determines whether the Python instance has the specified attribute /// /// The attribute name /// Whether the Python instance has the specified attribute public bool HasAttr(string name) { using var _ = Py.GIL(); return _instance.HasAttr(name) || _instance.HasAttr(name.ToSnakeCase()); } /// /// Gets the Python instances method with the specified name and caches it /// /// The name of the method /// The matched method public PyObject GetMethod(string methodName) { if (!_pythonMethods.TryGetValue(methodName, out var method)) { method = _instance.GetMethod(methodName); _pythonMethods = AddToDictionary(_pythonMethods, methodName, method); } return method; } /// /// Invokes the specified method with the specified arguments /// /// The name of the method /// The arguments to call the method with /// The returned valued converted to the given type public T InvokeMethod(string methodName, params object[] args) { using var _ = Py.GIL(); var method = GetMethod(methodName); return method.Invoke(args); } /// /// Invokes the specified method with the specified arguments /// /// The name of the method /// The arguments to call the method with public PyObject InvokeMethod(string methodName, params object[] args) { using var _ = Py.GIL(); var method = GetMethod(methodName); return method.Invoke(args); } private string GetPropertyName(string propertyName, bool isEvent = false) { if (!_pythonPropertyNames.TryGetValue(propertyName, out var pythonPropertyName)) { var snakeCasedPropertyName = propertyName.ToSnakeCase(); // If the object is actually a C# object (e.g. a child class of a C# class), // we check which property was defined in the Python class (if any), either the snake-cased or the original name. if (!isEvent && _underlyingClrObject != null) { var underlyingClrObjectType = _underlyingClrObject.GetType(); var property = underlyingClrObjectType.GetProperty(propertyName); if (property != null) { var clrPropertyValue = property.GetValue(_underlyingClrObject); var pyObjectSnakeCasePropertyValue = _instance.GetAttr(snakeCasedPropertyName); if (!pyObjectSnakeCasePropertyValue.TryConvert(out object pyObjectSnakeCasePropertyClrValue, true) || !ReferenceEquals(clrPropertyValue, pyObjectSnakeCasePropertyClrValue)) { pythonPropertyName = snakeCasedPropertyName; } else { pythonPropertyName = propertyName; } } } if (pythonPropertyName == null) { pythonPropertyName = snakeCasedPropertyName; if (!_instance.HasAttr(pythonPropertyName)) { pythonPropertyName = propertyName; } } _pythonPropertyNames = AddToDictionary(_pythonPropertyNames, propertyName, pythonPropertyName); } return pythonPropertyName; } /// /// Adds a key-value pair to the dictionary by copying the original one first and returning a new dictionary /// containing the new key-value pair along with the original ones. /// We do this in order to avoid the overhead of using locks or concurrent dictionaries and still be thread-safe. /// private static Dictionary AddToDictionary(Dictionary dictionary, string key, T value) { return new Dictionary(dictionary) { [key] = value }; } } }