using System;
using System.Linq;
using System.Collections.Generic;
using System.Dynamic;
namespace Python.Runtime
{
public class PyScopeException : Exception
{
public PyScopeException(string message)
: base(message)
{
}
}
///
/// Classes/methods have this attribute must be used with GIL obtained.
///
public class PyGILAttribute : Attribute
{
}
[PyGIL]
public class PyScope : DynamicObject, IPyDisposable
{
public readonly string Name;
///
/// the python Module object the scope associated with.
///
internal readonly IntPtr obj;
///
/// the variable dict of the scope.
///
internal readonly IntPtr variables;
private bool _isDisposed;
private bool _finalized = false;
///
/// The Manager this scope associated with.
/// It provides scopes this scope can import.
///
internal readonly PyScopeManager Manager;
///
/// event which will be triggered after the scope disposed.
///
public event Action OnDispose;
///
/// Constructor
///
///
/// Create a scope based on a Python Module.
///
internal PyScope(IntPtr ptr, PyScopeManager manager)
{
if (!Runtime.PyType_IsSubtype(Runtime.PyObject_TYPE(ptr), Runtime.PyModuleType))
{
throw new PyScopeException("object is not a module");
}
Manager = manager ?? PyScopeManager.Global;
obj = ptr;
//Refcount of the variables not increase
variables = Runtime.PyModule_GetDict(obj);
Runtime.CheckExceptionOccurred();
Runtime.PyDict_SetItemString(
variables, "__builtins__",
Runtime.PyEval_GetBuiltins()
);
this.Name = this.Get("__name__");
}
///
/// return the variable dict of the scope.
///
///
public PyDict Variables()
{
Runtime.XIncref(variables);
return new PyDict(variables);
}
///
/// Create a scope, and import all from this scope
///
///
public PyScope NewScope()
{
var scope = Manager.Create();
scope.ImportAll(this);
return scope;
}
///
/// Import method
///
///
/// Import a scope or a module of given name,
/// scope will be looked up first.
///
public dynamic Import(string name, string asname = null)
{
Check();
if (String.IsNullOrEmpty(asname))
{
asname = name;
}
PyScope scope;
Manager.TryGet(name, out scope);
if (scope != null)
{
Import(scope, asname);
return scope;
}
else
{
PyObject module = PythonEngine.ImportModule(name);
Import(module, asname);
return module;
}
}
///
/// Import method
///
///
/// Import a scope as a variable of given name.
///
public void Import(PyScope scope, string asname)
{
this.Set(asname, scope.obj);
}
///
/// Import Method
///
///
/// The 'import .. as ..' statement in Python.
/// Import a module as a variable into the scope.
///
public void Import(PyObject module, string asname = null)
{
if (String.IsNullOrEmpty(asname))
{
asname = module.GetAttr("__name__").As();
}
Set(asname, module);
}
///
/// ImportAll Method
///
///
/// The 'import * from ..' statement in Python.
/// Import all content of a scope/module of given name into the scope, scope will be looked up first.
///
public void ImportAll(string name)
{
PyScope scope;
Manager.TryGet(name, out scope);
if (scope != null)
{
ImportAll(scope);
return;
}
else
{
PyObject module = PythonEngine.ImportModule(name);
ImportAll(module);
}
}
///
/// ImportAll Method
///
///
/// Import all variables of the scope into this scope.
///
public void ImportAll(PyScope scope)
{
int result = Runtime.PyDict_Update(variables, scope.variables);
if (result < 0)
{
throw new PythonException();
}
}
///
/// ImportAll Method
///
///
/// Import all variables of the module into this scope.
///
public void ImportAll(PyObject module)
{
if (Runtime.PyObject_Type(module.obj) != Runtime.PyModuleType)
{
throw new PyScopeException("object is not a module");
}
var module_dict = Runtime.PyModule_GetDict(module.obj);
int result = Runtime.PyDict_Update(variables, module_dict);
if (result < 0)
{
throw new PythonException();
}
}
///
/// ImportAll Method
///
///
/// Import all variables in the dictionary into this scope.
///
public void ImportAll(PyDict dict)
{
int result = Runtime.PyDict_Update(variables, dict.obj);
if (result < 0)
{
throw new PythonException();
}
}
///
/// Execute method
///
///
/// Execute a Python ast and return the result as a PyObject.
/// The ast can be either an expression or stmts.
///
public PyObject Execute(PyObject script, PyDict locals = null)
{
Check();
IntPtr _locals = locals == null ? variables : locals.obj;
IntPtr ptr = Runtime.PyEval_EvalCode(script.Handle, variables, _locals);
Runtime.CheckExceptionOccurred();
if (ptr == Runtime.PyNone)
{
Runtime.XDecref(ptr);
return null;
}
return new PyObject(ptr);
}
///
/// Execute method
///
///
/// Execute a Python ast and return the result as a PyObject,
/// and convert the result to a Managed Object of given type.
/// The ast can be either an expression or stmts.
///
public T Execute(PyObject script, PyDict locals = null)
{
Check();
PyObject pyObj = Execute(script, locals);
if (pyObj == null)
{
return default(T);
}
var obj = pyObj.As();
return obj;
}
///
/// Eval method
///
///
/// Evaluate a Python expression and return the result as a PyObject
/// or null if an exception is raised.
///
public PyObject Eval(string code, PyDict locals = null)
{
Check();
IntPtr _locals = locals == null ? variables : locals.obj;
var flag = (IntPtr)Runtime.Py_eval_input;
IntPtr ptr = Runtime.PyRun_String(
code, flag, variables, _locals
);
Runtime.CheckExceptionOccurred();
return new PyObject(ptr);
}
///
/// Evaluate a Python expression
///
///
/// Evaluate a Python expression
/// and convert the result to a Managed Object of given type.
///
public T Eval(string code, PyDict locals = null)
{
Check();
PyObject pyObj = Eval(code, locals);
var obj = pyObj.As();
return obj;
}
///
/// Exec Method
///
///
/// Exec a Python script and save its local variables in the current local variable dict.
///
public void Exec(string code, PyDict locals = null)
{
Check();
IntPtr _locals = locals == null ? variables : locals.obj;
Exec(code, variables, _locals);
}
private void Exec(string code, IntPtr _globals, IntPtr _locals)
{
var flag = (IntPtr)Runtime.Py_file_input;
IntPtr ptr = Runtime.PyRun_String(
code, flag, _globals, _locals
);
Runtime.CheckExceptionOccurred();
if (ptr != Runtime.PyNone)
{
throw new PythonException();
}
Runtime.XDecref(ptr);
}
///
/// Set Variable Method
///
///
/// Add a new variable to the variables dict if it not exist
/// or update its value if the variable exists.
///
public void Set(string name, object value)
{
IntPtr _value = Converter.ToPython(value, value?.GetType());
Set(name, _value);
Runtime.XDecref(_value);
}
private void Set(string name, IntPtr value)
{
Check();
using (var pyKey = new PyString(name))
{
int r = Runtime.PyObject_SetItem(variables, pyKey.obj, value);
if (r < 0)
{
throw new PythonException();
}
}
}
///
/// Remove Method
///
///
/// Remove a variable from the variables dict.
///
public void Remove(string name)
{
Check();
using (var pyKey = new PyString(name))
{
int r = Runtime.PyObject_DelItem(variables, pyKey.obj);
if (r < 0)
{
throw new PythonException();
}
}
}
///
/// Contains Method
///
///
/// Returns true if the variable exists in the scope.
///
public bool Contains(string name)
{
Check();
using (var pyKey = new PyString(name))
{
return Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0;
}
}
///
/// Get Method
///
///
/// Returns the value of the variable of given name.
/// If the variable does not exist, throw an Exception.
///
public PyObject Get(string name)
{
PyObject scope;
var state = TryGet(name, out scope);
if (!state)
{
throw new PyScopeException($"The scope of name '{Name}' has no attribute '{name}'");
}
return scope;
}
///
/// TryGet Method
///
///
/// Returns the value of the variable, local variable first.
/// If the variable does not exist, return null.
///
public bool TryGet(string name, out PyObject value)
{
Check();
using (var pyKey = new PyString(name))
{
if (Runtime.PyMapping_HasKey(variables, pyKey.obj) != 0)
{
IntPtr op = Runtime.PyObject_GetItem(variables, pyKey.obj);
if (op == IntPtr.Zero)
{
throw new PythonException();
}
if (op == Runtime.PyNone)
{
Runtime.XDecref(op);
value = null;
return true;
}
value = new PyObject(op);
return true;
}
else
{
value = null;
return false;
}
}
}
///
/// Get Method
///
///
/// Obtain the value of the variable of given name,
/// and convert the result to a Managed Object of given type.
/// If the variable does not exist, throw an Exception.
///
public T Get(string name)
{
Check();
PyObject pyObj = Get(name);
if (pyObj == null)
{
return default(T);
}
return pyObj.As();
}
///
/// TryGet Method
///
///
/// Obtain the value of the variable of given name,
/// and convert the result to a Managed Object of given type.
/// If the variable does not exist, return false.
///
public bool TryGet(string name, out T value)
{
Check();
PyObject pyObj;
var result = TryGet(name, out pyObj);
if (!result)
{
value = default(T);
return false;
}
if (pyObj == null)
{
if (typeof(T).IsValueType)
{
throw new PyScopeException($"The value of the attribute '{name}' is None which cannot be convert to '{typeof(T).ToString()}'");
}
else
{
value = default(T);
return true;
}
}
value = pyObj.As();
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = this.Get(binder.Name);
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
this.Set(binder.Name, value);
return true;
}
private void Check()
{
if (_isDisposed)
{
throw new PyScopeException($"The scope of name '{Name}' object has been disposed");
}
}
public void Dispose()
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
Runtime.XDecref(obj);
this.OnDispose?.Invoke(this);
}
public IntPtr[] GetTrackedHandles()
{
return new IntPtr[] { obj };
}
~PyScope()
{
if (_finalized || _isDisposed)
{
return;
}
_finalized = true;
Finalizer.Instance.AddFinalizedObject(this);
}
}
public class PyScopeManager
{
public static PyScopeManager Global;
private Dictionary NamedScopes = new Dictionary();
internal static void Reset()
{
Global = new PyScopeManager();
}
internal PyScope NewScope(string name)
{
if (name == null)
{
name = "";
}
var module = Runtime.PyModule_New(name);
if (module == IntPtr.Zero)
{
throw new PythonException();
}
return new PyScope(module, this);
}
///
/// Create Method
///
///
/// Create an anonymous scope.
///
[PyGIL]
public PyScope Create()
{
var scope = this.NewScope(null);
return scope;
}
///
/// Create Method
///
///
/// Create an named scope of given name.
///
[PyGIL]
public PyScope Create(string name)
{
if (String.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(name));
}
if (name != null && Contains(name))
{
throw new PyScopeException($"A scope of name '{name}' does already exist");
}
var scope = this.NewScope(name);
scope.OnDispose += Remove;
NamedScopes[name] = scope;
return scope;
}
///
/// Contains Method
///
///
/// return true if the scope exists in this manager.
///
public bool Contains(string name)
{
return NamedScopes.ContainsKey(name);
}
///
/// Get Method
///
///
/// Find the scope in this manager.
/// If the scope not exist, an Exception will be thrown.
///
public PyScope Get(string name)
{
if (String.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(name));
}
if (NamedScopes.ContainsKey(name))
{
return NamedScopes[name];
}
throw new PyScopeException($"There is no scope named '{name}' registered in this manager");
}
///
/// Get Method
///
///
/// Try to find the scope in this manager.
///
public bool TryGet(string name, out PyScope scope)
{
return NamedScopes.TryGetValue(name, out scope);
}
///
/// Remove Method
///
///
/// remove the scope from this manager.
///
public void Remove(PyScope scope)
{
NamedScopes.Remove(scope.Name);
}
[PyGIL]
public void Clear()
{
var scopes = NamedScopes.Values.ToList();
foreach (var scope in scopes)
{
scope.Dispose();
}
}
}
}