Skip to content
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
1 change: 0 additions & 1 deletion src/Bonsai.Scripting.Python/Bonsai.Scripting.Python.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

<ItemGroup>
<PackageReference Include="Bonsai.Core" Version="2.7.0" />
<PackageReference Include="Bonsai.System" Version="2.7.0" />
<PackageReference Include="pythonnet" Version="3.0.1" />
</ItemGroup>

Expand Down
44 changes: 0 additions & 44 deletions src/Bonsai.Scripting.Python/Configuration/ScopeConfiguration.cs

This file was deleted.

This file was deleted.

44 changes: 44 additions & 0 deletions src/Bonsai.Scripting.Python/CreateModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Reactive.Linq;
using Python.Runtime;

namespace Bonsai.Scripting.Python
{
/// <summary>
/// Represents an operator that creates a top-level module in the Python runtime.
/// </summary>
public class CreateModule : Source<PyModule>
{
/// <summary>
/// Gets or sets the name of the top-level module.
/// </summary>
[Description("The name of the top-level module.")]
public string Name { get; set; }

/// <summary>
/// Gets or sets the path to the Python script file to run on module initialization.
/// </summary>
[FileNameFilter("Python Files (*.py)|*.py|All Files|*.*")]
[Description("The path to the Python script file to run on module initialization.")]
[Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)]
public string ScriptPath { get; set; }

/// <summary>
/// Generates an observable sequence that contains the created top-level module.
/// </summary>
/// <returns>
/// A sequence containing a single instance of the <see cref="PyModule"/> class
/// representing the created top-level module.
/// </returns>
public override IObservable<PyModule> Generate()
{
return RuntimeManager.RuntimeSource.SelectMany(runtime =>
{
var module = RuntimeManager.CreateModule(Name ?? string.Empty, ScriptPath);
return Observable.Return(module);
});
}
}
}
10 changes: 9 additions & 1 deletion src/Bonsai.Scripting.Python/CreateRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public class CreateRuntime : Source<RuntimeManager>
[Editor("Bonsai.Design.FolderNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)]
public string PythonHome { get; set; }

/// <summary>
/// Gets or sets the path to the Python script file to run on runtime initialization.
/// </summary>
[FileNameFilter("Python Files (*.py)|*.py|All Files|*.*")]
[Description("The path to the Python script file to run on runtime initialization.")]
[Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)]
public string ScriptPath { get; set; }

/// <summary>
/// Creates an observable sequence that initializes a Python runtime object which
/// can be used to import modules, evaluate expressions, and pass data to and
Expand All @@ -39,7 +47,7 @@ public override IObservable<RuntimeManager> Generate()
{
var disposable = SubjectManager.ReserveSubject();
var subscription = disposable.Subject.SubscribeSafe(observer);
var runtime = new RuntimeManager(PythonHome, disposable.Subject);
var runtime = new RuntimeManager(PythonHome, ScriptPath, disposable.Subject);
return new CompositeDisposable
{
subscription,
Expand Down
38 changes: 38 additions & 0 deletions src/Bonsai.Scripting.Python/DynamicModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Threading;
using Python.Runtime;

namespace Bonsai.Scripting.Python
{
class DynamicModule : PyModule
{
PythonEngine.ShutdownHandler shutdown;

internal DynamicModule(string name)
: base(name ?? throw new ArgumentNullException(name))
{
shutdown = () =>
{
if (Interlocked.Exchange(ref shutdown, null) != null)
{
Dispose();
}
};
PythonEngine.AddShutdownHandler(shutdown);
}

protected override void Dispose(bool disposing)
{
var handler = Interlocked.Exchange(ref shutdown, null);
if (handler != null)
{
PythonEngine.RemoveShutdownHandler(handler);
using (Py.GIL())
{
base.Dispose(disposing);
}
}
else base.Dispose(disposing);
}
}
}
60 changes: 52 additions & 8 deletions src/Bonsai.Scripting.Python/Eval.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Xml.Serialization;
using Python.Runtime;

namespace Bonsai.Scripting.Python
{
/// <summary>
/// Represents an operator that evaluates a Python expression in the specified
/// runtime scope.
/// top-level module.
/// </summary>
[DefaultProperty(nameof(Expression))]
[Description("Evaluates a Python expression in the specified runtime scope.")]
public class Eval : Combinator<PyObject>
{
/// <summary>
/// Gets or sets the name of the runtime scope on which to evaluate the Python expression.
/// Gets or sets the top-level module on which to evaluate the Python expression.
/// </summary>
[TypeConverter(typeof(ScopeNameConverter))]
[Description("The name of the runtime scope on which to evaluate the Python expression.")]
public string ScopeName { get; set; }
[XmlIgnore]
[Description("The top-level module on which to evaluate the Python expression.")]
public PyModule Module { get; set; }

/// <summary>
/// Gets or sets the Python expression to evaluate.
Expand All @@ -27,7 +29,7 @@ public class Eval : Combinator<PyObject>
public string Expression { get; set; }

/// <summary>
/// Evaluates a Python expression in the specified runtime scope whenever an
/// Evaluates a Python expression in the specified top-level module whenever an
/// observable sequence emits a notification.
/// </summary>
/// <typeparam name="TSource">
Expand All @@ -44,15 +46,57 @@ public override IObservable<PyObject> Process<TSource>(IObservable<TSource> sour
{
return RuntimeManager.RuntimeSource.SelectMany(runtime =>
{
var scope = runtime.Resources.Load<PyModule>(ScopeName);
return source.Select(_ =>
{
using (Py.GIL())
{
return scope.Eval(Expression);
var module = Module ?? runtime.MainModule;
return module.Eval(Expression);
}
});
});
}

/// <summary>
/// Evaluates a Python expression in an observable sequence of modules.
/// </summary>
/// <param name="source">
/// The sequence of modules in which to evaluate the Python expression.
/// </param>
/// <returns>
/// A sequence of <see cref="PyObject"/> handles representing the result
/// of evaluating the Python expression.
/// </returns>
public IObservable<PyObject> Process(IObservable<PyModule> source)
{
return source.Select(module =>
{
using (Py.GIL())
{
return module.Eval(Expression);
}
});
}

/// <summary>
/// Evaluates an expression in the main module of the Python runtime.
/// </summary>
/// <param name="source">
/// A sequence containing the Python runtime in which to evaluate the expression.
/// </param>
/// <returns>
/// A sequence of <see cref="PyObject"/> handles representing the result
/// of evaluating the Python expression.
/// </returns>
public IObservable<PyObject> Process(IObservable<RuntimeManager> source)
{
return source.Select(runtime =>
{
using (Py.GIL())
{
return runtime.MainModule.Eval(Expression);
}
});
}
}
}
104 changes: 104 additions & 0 deletions src/Bonsai.Scripting.Python/Exec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Xml.Serialization;
using Python.Runtime;

namespace Bonsai.Scripting.Python
{
/// <summary>
/// Represents an operator that executes a Python script in the specified
/// top-level module.
/// </summary>
[DefaultProperty(nameof(Script))]
[Description("Executes a Python script in the specified top-level module.")]
public class Exec : Combinator<PyModule>
{
/// <summary>
/// Gets or sets the top-level module on which to execute the Python script.
/// </summary>
[XmlIgnore]
[Description("The top-level module on which to execute the Python script.")]
public PyModule Module { get; set; }

/// <summary>
/// Gets or sets the Python script to evaluate.
/// </summary>
[Description("The Python script to evaluate.")]
[Editor(DesignTypes.MultilineStringEditor, DesignTypes.UITypeEditor)]
public string Script { get; set; }

/// <summary>
/// Executes a Python script in the specified top-level module whenever an
/// observable sequence emits a notification.
/// </summary>
/// <typeparam name="TSource">
/// The type of the elements in the <paramref name="source"/> sequence.
/// </typeparam>
/// <param name="source">
/// The sequence of notifications used to trigger execution of the Python script.
/// </param>
/// <returns>
/// A sequence of <see cref="PyModule"/> objects representing the top-level
/// module where each Python script was executed.
/// </returns>
public override IObservable<PyModule> Process<TSource>(IObservable<TSource> source)
{
return RuntimeManager.RuntimeSource.SelectMany(runtime =>
{
return source.Select(_ =>
{
using (Py.GIL())
{
var module = Module ?? runtime.MainModule;
return module.Exec(Script);
}
});
});
}

/// <summary>
/// Executes a Python script in an observable sequence of modules.
/// </summary>
/// <param name="source">
/// The sequence of modules in which to execute the Python script.
/// </param>
/// <returns>
/// An observable sequence that is identical to the <paramref name="source"/>
/// sequence but where there is an additional side effect of executing the
/// Python script in each of the <see cref="PyModule"/> objects.
/// </returns>
public IObservable<PyModule> Process(IObservable<PyModule> source)
{
return source.Select(module =>
{
using (Py.GIL())
{
return module.Exec(Script);
}
});
}

/// <summary>
/// Executes a script in the main module of the Python runtime.
/// </summary>
/// <param name="source">
/// A sequence containing the Python runtime in which to execute the script.
/// </param>
/// <returns>
/// A sequence containing the <see cref="PyModule"/> object representing
/// the top-level module where the Python script was executed.
/// </returns>
public IObservable<PyModule> Process(IObservable<RuntimeManager> source)
{
return source.Select(runtime =>
{
using (Py.GIL())
{
return runtime.MainModule.Exec(Script);
}
});
}
}
}
Loading