Skip to content

Commit 5921516

Browse files
committed
Split expression and python scripting packages
1 parent 5fa5d3c commit 5921516

31 files changed

+715
-679
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
<PropertyGroup>
4+
<Title>Bonsai - Python Scripting Library</Title>
5+
<Description>Bonsai Scripting Library containing Python scripting infrastructure for Bonsai.</Description>
6+
<PackageTags>Bonsai Rx Scripting Python</PackageTags>
7+
<TargetFramework>net462</TargetFramework>
8+
<Version>2.7.0</Version>
9+
</PropertyGroup>
10+
<ItemGroup>
11+
<PackageReference Include="IronPython" Version="2.7.5" />
12+
<PackageReference Include="IronPython.StdLib" Version="2.7.5" />
13+
</ItemGroup>
14+
<ItemGroup>
15+
<ProjectReference Include="..\Bonsai.Core\Bonsai.Core.csproj" />
16+
</ItemGroup>
17+
</Project>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Bonsai.Scripting.Python
2+
{
3+
interface IScriptingElement : INamedElement
4+
{
5+
string Description { get; }
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using Bonsai;
2+
3+
// General Information about an assembly is controlled through the following
4+
// set of attributes. Change these attribute values to modify the information
5+
// associated with an assembly.
6+
[assembly: XmlNamespacePrefix("clr-namespace:Bonsai.Scripting.Python", "py")]
7+
[assembly: WorkflowNamespaceIcon("Bonsai:ElementIcon.Scripting")]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"profiles": {
3+
"Bonsai": {
4+
"commandName": "Executable",
5+
"executablePath": "$(SolutionDir)Bonsai/bin/$(Configuration)/net472/Bonsai.exe",
6+
"commandLineArgs": "--lib:$(TargetDir)."
7+
}
8+
}
9+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.Linq;
3+
using System.ComponentModel;
4+
using System.Reactive.Linq;
5+
6+
namespace Bonsai.Scripting.Python
7+
{
8+
/// <summary>
9+
/// Represents an operator that uses a Python script to filter the elements
10+
/// of an observable sequence.
11+
/// </summary>
12+
[Obsolete]
13+
[DefaultProperty(nameof(Script))]
14+
[WorkflowElementCategory(ElementCategory.Condition)]
15+
[Description("A Python script used to determine which elements of the input sequence are accepted.")]
16+
public class PythonCondition : Combinator
17+
{
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="PythonCondition"/> class.
20+
/// </summary>
21+
public PythonCondition()
22+
{
23+
Script = "def process(value):\n return True";
24+
}
25+
26+
/// <summary>
27+
/// Gets or sets the script that determines the criteria for the condition.
28+
/// </summary>
29+
[Editor("Bonsai.Scripting.Python.Design.PythonScriptEditor, Bonsai.Scripting.Python.Design", DesignTypes.UITypeEditor)]
30+
[Description("The script that determines the criteria for the condition.")]
31+
public string Script { get; set; }
32+
33+
/// <summary>
34+
/// Uses a Python script to filter the elements of an observable sequence.
35+
/// </summary>
36+
/// <typeparam name="TSource">
37+
/// The type of the elements in the <paramref name="source"/> sequence.
38+
/// </typeparam>
39+
/// <param name="source">
40+
/// The observable sequence to filter.
41+
/// </param>
42+
/// <returns>
43+
/// An observable sequence that contains the elements of the <paramref name="source"/>
44+
/// sequence that satisfy the condition.
45+
/// </returns>
46+
public override IObservable<TSource> Process<TSource>(IObservable<TSource> source)
47+
{
48+
return Observable.Defer(() =>
49+
{
50+
var engine = PythonEngine.Create();
51+
var scope = engine.CreateScope();
52+
engine.Execute(Script, scope);
53+
54+
PythonProcessor<TSource, bool> processor;
55+
if (PythonHelper.TryGetClass(scope, "Condition", out object condition))
56+
{
57+
processor = new PythonProcessor<TSource, bool>(engine.Operations, condition);
58+
}
59+
else processor = new PythonProcessor<TSource, bool>(scope);
60+
61+
if (processor.Load != null)
62+
{
63+
processor.Load();
64+
}
65+
66+
var result = source.Where(processor.Process);
67+
if (processor.Unload != null)
68+
{
69+
result = result.Finally(processor.Unload);
70+
}
71+
72+
return result;
73+
});
74+
}
75+
}
76+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Microsoft.Scripting.Hosting;
2+
using System.IO;
3+
using System.Reflection;
4+
5+
namespace Bonsai.Scripting.Python
6+
{
7+
static class PythonEngine
8+
{
9+
internal static ScriptEngine Create()
10+
{
11+
var engine = IronPython.Hosting.Python.CreateEngine();
12+
var basePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
13+
var libPath = Directory.GetDirectories(Path.Combine(basePath, "../../../"), "IronPython.StdLib.*");
14+
if (libPath.Length == 1)
15+
{
16+
var lib = Path.Combine(libPath[0], $"content/Lib");
17+
var sitePackages = Path.Combine(lib, "site-packages");
18+
engine.SetSearchPaths(new[] { lib, sitePackages });
19+
}
20+
return engine;
21+
}
22+
}
23+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using IronPython.Runtime;
2+
using IronPython.Runtime.Types;
3+
using Microsoft.Scripting.Hosting;
4+
using System;
5+
6+
namespace Bonsai.Scripting.Python
7+
{
8+
static class PythonHelper
9+
{
10+
internal const string ReturnsDecorator = "import clr\ndef returns(type):\n def decorator(func):\n func.__returntype__ = clr.GetClrType(type)\n return func\n return decorator\n\n";
11+
internal const string ReturnTypeAttribute = "__returntype__";
12+
internal const string LoadFunction = "load";
13+
internal const string UnloadFunction = "unload";
14+
internal const string ProcessFunction = "process";
15+
internal const string GenerateFunction = "generate";
16+
17+
internal static bool TryGetClass(ScriptScope scope, string className, out object pythonClass)
18+
{
19+
if (scope.TryGetVariable(className, out object variable))
20+
{
21+
pythonClass = variable as OldClass;
22+
if (pythonClass != null)
23+
{
24+
return true;
25+
}
26+
27+
pythonClass = variable as PythonType;
28+
return pythonClass != null;
29+
}
30+
31+
pythonClass = null;
32+
return false;
33+
}
34+
35+
internal static Type GetOutputType(ObjectOperations op, object pythonClass, string methodName)
36+
{
37+
var function = (PythonFunction)op.GetMember<Method>(pythonClass, methodName).__func__;
38+
if (function.func_dict.TryGetValue(ReturnTypeAttribute, out object returnType))
39+
{
40+
return (Type)returnType;
41+
}
42+
43+
return typeof(object);
44+
}
45+
46+
internal static Type GetOutputType(ScriptScope scope, string functionName)
47+
{
48+
var function = scope.GetVariable<PythonFunction>(functionName);
49+
if (function.func_dict.TryGetValue(ReturnTypeAttribute, out object returnType))
50+
{
51+
return (Type)returnType;
52+
}
53+
else
54+
{
55+
if (scope.TryGetVariable("getOutputType", out Func<Type> getOutputType))
56+
{
57+
return getOutputType();
58+
}
59+
else
60+
{
61+
return typeof(object);
62+
}
63+
}
64+
}
65+
}
66+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Microsoft.Scripting.Hosting;
2+
using System;
3+
4+
namespace Bonsai.Scripting.Python
5+
{
6+
class PythonProcessor<TSource, TResult>
7+
{
8+
internal PythonProcessor(ScriptScope scope)
9+
{
10+
scope.TryGetVariable(PythonHelper.LoadFunction, out Action load);
11+
scope.TryGetVariable(PythonHelper.UnloadFunction, out Action unload);
12+
Process = scope.GetVariable<Func<TSource, TResult>>(PythonHelper.ProcessFunction);
13+
Load = load;
14+
Unload = unload;
15+
}
16+
17+
internal PythonProcessor(ObjectOperations op, object processorClass)
18+
{
19+
var processor = (object)op.CreateInstance(processorClass);
20+
Process = op.GetMember<Func<TSource, TResult>>(processor, PythonHelper.ProcessFunction);
21+
if (op.ContainsMember(processor, PythonHelper.UnloadFunction))
22+
{
23+
Unload = op.GetMember<Action>(processor, PythonHelper.UnloadFunction);
24+
}
25+
if (op.ContainsMember(processor, PythonHelper.LoadFunction))
26+
{
27+
Load = op.GetMember<Action>(processor, PythonHelper.LoadFunction);
28+
}
29+
}
30+
31+
internal Action Load { get; private set; }
32+
33+
internal Action Unload { get; private set; }
34+
35+
internal Func<TSource, TResult> Process { get; private set; }
36+
}
37+
}

0 commit comments

Comments
 (0)