From ae593524f68e71edb52048ce1547d16266bffa40 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Tue, 7 Feb 2023 00:21:25 +0000 Subject: [PATCH 1/8] Refactor operators to use module naming convention --- ...{ScopeConfiguration.cs => CreateModule.cs} | 18 +++++------ ...lection.cs => ModuleResourceCollection.cs} | 6 ++-- src/Bonsai.Scripting.Python/Eval.cs | 16 +++++----- .../{GetVariable.cs => Get.cs} | 32 +++++++++---------- .../ModuleNameConverter.cs | 13 ++++++++ .../{ScopeResources.cs => ModuleResources.cs} | 16 +++++----- .../ScopeNameConverter.cs | 18 ----------- .../{SetVariable.cs => Set.cs} | 26 +++++++-------- 8 files changed, 70 insertions(+), 75 deletions(-) rename src/Bonsai.Scripting.Python/Configuration/{ScopeConfiguration.cs => CreateModule.cs} (65%) rename src/Bonsai.Scripting.Python/Configuration/{ScopeConfigurationCollection.cs => ModuleResourceCollection.cs} (67%) rename src/Bonsai.Scripting.Python/{GetVariable.cs => Get.cs} (60%) create mode 100644 src/Bonsai.Scripting.Python/ModuleNameConverter.cs rename src/Bonsai.Scripting.Python/{ScopeResources.cs => ModuleResources.cs} (70%) delete mode 100644 src/Bonsai.Scripting.Python/ScopeNameConverter.cs rename src/Bonsai.Scripting.Python/{SetVariable.cs => Set.cs} (67%) diff --git a/src/Bonsai.Scripting.Python/Configuration/ScopeConfiguration.cs b/src/Bonsai.Scripting.Python/Configuration/CreateModule.cs similarity index 65% rename from src/Bonsai.Scripting.Python/Configuration/ScopeConfiguration.cs rename to src/Bonsai.Scripting.Python/Configuration/CreateModule.cs index b7bf5f2..b12d147 100644 --- a/src/Bonsai.Scripting.Python/Configuration/ScopeConfiguration.cs +++ b/src/Bonsai.Scripting.Python/Configuration/CreateModule.cs @@ -6,26 +6,26 @@ namespace Bonsai.Scripting.Python.Configuration { /// - /// Provides configuration and loading functionality for Python global scopes. + /// Provides configuration and loading functionality for top-level modules. /// - public class ScopeConfiguration : ResourceConfiguration + public class CreateModule : ResourceConfiguration { /// - /// Gets or sets the name of the Python script file to run on scope initialization. + /// Gets or sets the path to the Python script file to run on module initialization. /// [FileNameFilter("Python Files (*.py)|*.py|All Files|*.*")] - [Description("The name of the Python script file to run on scope initialization.")] + [Description("The path to the Python script file to run on module initialization.")] [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] [TypeConverter(typeof(ResourceFileNameConverter))] - public string Script { get; set; } + public string ScriptPath { get; set; } /// public override PyModule CreateResource(ResourceManager resourceManager) { var scope = Py.CreateScope(Name); - if (!string.IsNullOrEmpty(Script)) + if (!string.IsNullOrEmpty(ScriptPath)) { - var code = File.ReadAllText(Script); + var code = File.ReadAllText(ScriptPath); scope.Exec(code); } return scope; @@ -34,9 +34,9 @@ public override PyModule CreateResource(ResourceManager resourceManager) /// public override string ToString() { - var script = Script; + var script = ScriptPath; var baseName = Name; - if (string.IsNullOrEmpty(baseName)) baseName = nameof(ScopeConfiguration); + if (string.IsNullOrEmpty(baseName)) baseName = nameof(CreateModule); if (string.IsNullOrEmpty(script)) return baseName; else return $"{baseName} [{script}]"; } diff --git a/src/Bonsai.Scripting.Python/Configuration/ScopeConfigurationCollection.cs b/src/Bonsai.Scripting.Python/Configuration/ModuleResourceCollection.cs similarity index 67% rename from src/Bonsai.Scripting.Python/Configuration/ScopeConfigurationCollection.cs rename to src/Bonsai.Scripting.Python/Configuration/ModuleResourceCollection.cs index e1ccb21..2561ffc 100644 --- a/src/Bonsai.Scripting.Python/Configuration/ScopeConfigurationCollection.cs +++ b/src/Bonsai.Scripting.Python/Configuration/ModuleResourceCollection.cs @@ -3,16 +3,16 @@ namespace Bonsai.Scripting.Python.Configuration { /// - /// Represents a collection of scope configuration objects. + /// Represents a collection of module resource configuration objects. /// - public class ScopeConfigurationCollection : KeyedCollection + public class ModuleResourceCollection : KeyedCollection { /// /// Returns the key for the specified configuration object. /// /// The configuration object from which to extract the key. /// The key for the specified configuration object. - protected override string GetKeyForItem(ScopeConfiguration item) + protected override string GetKeyForItem(CreateModule item) { return item.Name; } diff --git a/src/Bonsai.Scripting.Python/Eval.cs b/src/Bonsai.Scripting.Python/Eval.cs index 5cd09d6..4393ae8 100644 --- a/src/Bonsai.Scripting.Python/Eval.cs +++ b/src/Bonsai.Scripting.Python/Eval.cs @@ -8,17 +8,17 @@ namespace Bonsai.Scripting.Python { /// /// Represents an operator that evaluates a Python expression in the specified - /// runtime scope. + /// top-level module. /// [Description("Evaluates a Python expression in the specified runtime scope.")] public class Eval : Combinator { /// - /// Gets or sets the name of the runtime scope on which to evaluate the Python expression. + /// Gets or sets the name of the top-level module on which to evaluate the Python expression. /// - [TypeConverter(typeof(ScopeNameConverter))] - [Description("The name of the runtime scope on which to evaluate the Python expression.")] - public string ScopeName { get; set; } + [TypeConverter(typeof(ModuleNameConverter))] + [Description("The name of the top-level module on which to evaluate the Python expression.")] + public string ModuleName { get; set; } /// /// Gets or sets the Python expression to evaluate. @@ -27,7 +27,7 @@ public class Eval : Combinator public string Expression { get; set; } /// - /// 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. /// /// @@ -44,12 +44,12 @@ public override IObservable Process(IObservable sour { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var scope = runtime.Resources.Load(ScopeName); + var module = runtime.Resources.Load(ModuleName); return source.Select(_ => { using (Py.GIL()) { - return scope.Eval(Expression); + return module.Eval(Expression); } }); }); diff --git a/src/Bonsai.Scripting.Python/GetVariable.cs b/src/Bonsai.Scripting.Python/Get.cs similarity index 60% rename from src/Bonsai.Scripting.Python/GetVariable.cs rename to src/Bonsai.Scripting.Python/Get.cs index b54cf35..495aedd 100644 --- a/src/Bonsai.Scripting.Python/GetVariable.cs +++ b/src/Bonsai.Scripting.Python/Get.cs @@ -7,27 +7,27 @@ namespace Bonsai.Scripting.Python { /// - /// Represents an operator that gets the value of a Python runtime variable in the - /// specified scope. + /// Represents an operator that gets the value of a variable in the specified + /// Python module. /// - [Description("Gets the value of a Python runtime variable in the specified scope.")] - public class GetVariable : Source + [Description("Gets the value of a variable in the specified Python module.")] + public class Get : Source { /// - /// Gets or sets the name of the Python runtime scope containing the variable. + /// Gets or sets the name of the Python module containing the variable. /// - [TypeConverter(typeof(ScopeNameConverter))] - [Description("The name of the Python runtime scope containing the variable.")] - public string ScopeName { get; set; } + [TypeConverter(typeof(ModuleNameConverter))] + [Description("The name of the Python module containing the variable.")] + public string ModuleName { get; set; } /// - /// Gets or sets the name of the scope variable to get the value of. + /// Gets or sets the name of the variable to get the value of. /// - [Description("The name of the scope variable to get the value of.")] + [Description("The name of the variable to get the value of.")] public string VariableName { get; set; } /// - /// Gets the value of a Python runtime variable in the specified scope and + /// Gets the value of a variable in the specified Python module and /// surfaces it through an observable sequence. /// /// @@ -38,13 +38,13 @@ public override IObservable Generate() { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var scope = runtime.Resources.Load(ScopeName); - return Observable.Return(scope.Get(VariableName)); + var module = runtime.Resources.Load(ModuleName); + return Observable.Return(module.Get(VariableName)); }); } /// - /// Gets the value of a Python runtime variable in the specified scope + /// Gets the value of a variable in the specified Python module /// whenever an observable sequence emits a notification. /// /// @@ -61,12 +61,12 @@ public IObservable Generate(IObservable source) { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var scope = runtime.Resources.Load(ScopeName); + var module = runtime.Resources.Load(ModuleName); return source.Select(_ => { using (Py.GIL()) { - return scope.Get(VariableName); + return module.Get(VariableName); } }); }); diff --git a/src/Bonsai.Scripting.Python/ModuleNameConverter.cs b/src/Bonsai.Scripting.Python/ModuleNameConverter.cs new file mode 100644 index 0000000..5456965 --- /dev/null +++ b/src/Bonsai.Scripting.Python/ModuleNameConverter.cs @@ -0,0 +1,13 @@ +using Bonsai.Resources; +using Python.Runtime; + +namespace Bonsai.Scripting.Python +{ + class ModuleNameConverter : ResourceNameConverter + { + public ModuleNameConverter() + : base(typeof(PyModule)) + { + } + } +} diff --git a/src/Bonsai.Scripting.Python/ScopeResources.cs b/src/Bonsai.Scripting.Python/ModuleResources.cs similarity index 70% rename from src/Bonsai.Scripting.Python/ScopeResources.cs rename to src/Bonsai.Scripting.Python/ModuleResources.cs index 477a6ff..fbda376 100644 --- a/src/Bonsai.Scripting.Python/ScopeResources.cs +++ b/src/Bonsai.Scripting.Python/ModuleResources.cs @@ -9,24 +9,24 @@ namespace Bonsai.Scripting.Python { /// - /// Represents an operator that creates a collection of scope resources to + /// Represents an operator that creates a collection of module resources to /// be loaded into the runtime resource manager. /// - [DefaultProperty(nameof(Scopes))] - [Description("Creates a collection of scope resources to be loaded into the runtime resource manager.")] - public class ScopeResources : ResourceLoader + [DefaultProperty(nameof(Resources))] + [Description("Creates a collection of module resources to be loaded into the runtime resource manager.")] + public class ModuleResources : ResourceLoader { /// - /// Gets the collection of scope resources to be loaded into the runtime resource manager. + /// Gets the collection of module resources to be loaded into the runtime resource manager. /// [Editor("Bonsai.Resources.Design.ResourceCollectionEditor, Bonsai.System.Design", DesignTypes.UITypeEditor)] - [Description("The collection of scope resources to be loaded into the runtime resource manager.")] - public ScopeConfigurationCollection Scopes { get; } = new ScopeConfigurationCollection(); + [Description("The collection of module resources to be loaded into the runtime resource manager.")] + public ModuleResourceCollection Resources { get; } = new ModuleResourceCollection(); /// protected override IEnumerable GetResources() { - return Scopes; + return Resources; } /// diff --git a/src/Bonsai.Scripting.Python/ScopeNameConverter.cs b/src/Bonsai.Scripting.Python/ScopeNameConverter.cs deleted file mode 100644 index fa7a095..0000000 --- a/src/Bonsai.Scripting.Python/ScopeNameConverter.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Bonsai.Resources; -using Python.Runtime; - -namespace Bonsai.Scripting.Python -{ - class ScopeNameConverter : ResourceNameConverter - { - public ScopeNameConverter() - : base(typeof(PyModule)) - { - } - - protected override bool IsResourceSupported(IResourceConfiguration resource) - { - return base.IsResourceSupported(resource); - } - } -} diff --git a/src/Bonsai.Scripting.Python/SetVariable.cs b/src/Bonsai.Scripting.Python/Set.cs similarity index 67% rename from src/Bonsai.Scripting.Python/SetVariable.cs rename to src/Bonsai.Scripting.Python/Set.cs index 269cc47..2cca9a4 100644 --- a/src/Bonsai.Scripting.Python/SetVariable.cs +++ b/src/Bonsai.Scripting.Python/Set.cs @@ -8,27 +8,27 @@ namespace Bonsai.Scripting.Python { /// /// Represents an operator that adds or updates a Python runtime variable in the - /// specified scope. + /// specified top-level module. /// - [Description("Adds or updates a Python runtime variable in the specified scope.")] - public class SetVariable : Sink + [Description("Adds or updates a Python runtime variable in the specified top-level module.")] + public class Set : Sink { /// - /// Gets or sets the name of the Python runtime scope containing the variable. + /// Gets or sets the name of the Python top-level module containing the variable. /// - [TypeConverter(typeof(ScopeNameConverter))] - [Description("The name of the Python runtime scope containing the variable.")] - public string ScopeName { get; set; } + [TypeConverter(typeof(ModuleNameConverter))] + [Description("The name of the Python top-level module containing the variable.")] + public string ModuleName { get; set; } /// - /// Gets or sets the name of the scope variable to add or update the value of. + /// Gets or sets the name of the variable to add or update the value of. /// - [Description("The name of the scope variable to add or update the value of.")] + [Description("The name of the variable to add or update the value of.")] public string VariableName { get; set; } /// - /// Adds or updates a Python runtime variable in the specified scope with - /// the values from an observable sequence. + /// Adds or updates a Python runtime variable in the specified top-level module + /// with the values from an observable sequence. /// /// /// The type of the values in the sequence. @@ -45,12 +45,12 @@ public override IObservable Process(IObservable sourc { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var scope = runtime.Resources.Load(ScopeName); + var module = runtime.Resources.Load(ModuleName); return source.Do(value => { using (Py.GIL()) { - scope.Set(VariableName, value); + module.Set(VariableName, value); } }); }); From 2904938eea0fa346998c0b95dc3c07a187556bcd Mon Sep 17 00:00:00 2001 From: glopesdev Date: Tue, 7 Feb 2023 00:21:30 +0000 Subject: [PATCH 2/8] Add support for the exec operator --- src/Bonsai.Scripting.Python/Exec.cs | 59 +++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/Bonsai.Scripting.Python/Exec.cs diff --git a/src/Bonsai.Scripting.Python/Exec.cs b/src/Bonsai.Scripting.Python/Exec.cs new file mode 100644 index 0000000..049366c --- /dev/null +++ b/src/Bonsai.Scripting.Python/Exec.cs @@ -0,0 +1,59 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Python.Runtime; + +namespace Bonsai.Scripting.Python +{ + /// + /// Represents an operator that executes a Python script in the specified + /// top-level module. + /// + [Description("Executes a Python script in the specified top-level module.")] + public class Exec : Combinator + { + /// + /// Gets or sets the name of the top-level module on which to execute the Python script. + /// + [TypeConverter(typeof(ModuleNameConverter))] + [Description("The name of the top-level module on which to execute the Python script.")] + public string ModuleName { get; set; } + + /// + /// Gets or sets the Python script to evaluate. + /// + [Description("The Python script to evaluate.")] + [Editor(DesignTypes.MultilineStringEditor, DesignTypes.UITypeEditor)] + public string Script { get; set; } + + /// + /// Executes a Python script in the specified top-level module whenever an + /// observable sequence emits a notification. + /// + /// + /// The type of the elements in the sequence. + /// + /// + /// The sequence of notifications used to trigger execution of the Python script. + /// + /// + /// A sequence of objects representing the top-level + /// module where each Python script was executed. + /// + public override IObservable Process(IObservable source) + { + return RuntimeManager.RuntimeSource.SelectMany(runtime => + { + var module = runtime.Resources.Load(ModuleName); + return source.Select(_ => + { + using (Py.GIL()) + { + return module.Exec(Script); + } + }); + }); + } + } +} From 481ebbbd96a2d2071697a24eeeb2ba2101ed1e3c Mon Sep 17 00:00:00 2001 From: glopesdev Date: Tue, 7 Feb 2023 01:41:25 +0000 Subject: [PATCH 3/8] Add operator default properties --- src/Bonsai.Scripting.Python/Eval.cs | 1 + src/Bonsai.Scripting.Python/Exec.cs | 1 + src/Bonsai.Scripting.Python/Get.cs | 1 + src/Bonsai.Scripting.Python/Set.cs | 1 + 4 files changed, 4 insertions(+) diff --git a/src/Bonsai.Scripting.Python/Eval.cs b/src/Bonsai.Scripting.Python/Eval.cs index 4393ae8..cba33ec 100644 --- a/src/Bonsai.Scripting.Python/Eval.cs +++ b/src/Bonsai.Scripting.Python/Eval.cs @@ -10,6 +10,7 @@ namespace Bonsai.Scripting.Python /// Represents an operator that evaluates a Python expression in the specified /// top-level module. /// + [DefaultProperty(nameof(Expression))] [Description("Evaluates a Python expression in the specified runtime scope.")] public class Eval : Combinator { diff --git a/src/Bonsai.Scripting.Python/Exec.cs b/src/Bonsai.Scripting.Python/Exec.cs index 049366c..cc49719 100644 --- a/src/Bonsai.Scripting.Python/Exec.cs +++ b/src/Bonsai.Scripting.Python/Exec.cs @@ -10,6 +10,7 @@ namespace Bonsai.Scripting.Python /// Represents an operator that executes a Python script in the specified /// top-level module. /// + [DefaultProperty(nameof(Script))] [Description("Executes a Python script in the specified top-level module.")] public class Exec : Combinator { diff --git a/src/Bonsai.Scripting.Python/Get.cs b/src/Bonsai.Scripting.Python/Get.cs index 495aedd..7726774 100644 --- a/src/Bonsai.Scripting.Python/Get.cs +++ b/src/Bonsai.Scripting.Python/Get.cs @@ -10,6 +10,7 @@ namespace Bonsai.Scripting.Python /// Represents an operator that gets the value of a variable in the specified /// Python module. /// + [DefaultProperty(nameof(VariableName))] [Description("Gets the value of a variable in the specified Python module.")] public class Get : Source { diff --git a/src/Bonsai.Scripting.Python/Set.cs b/src/Bonsai.Scripting.Python/Set.cs index 2cca9a4..05b445c 100644 --- a/src/Bonsai.Scripting.Python/Set.cs +++ b/src/Bonsai.Scripting.Python/Set.cs @@ -10,6 +10,7 @@ namespace Bonsai.Scripting.Python /// Represents an operator that adds or updates a Python runtime variable in the /// specified top-level module. /// + [DefaultProperty(nameof(VariableName))] [Description("Adds or updates a Python runtime variable in the specified top-level module.")] public class Set : Sink { From f162fed22d837467ae85b8a999f09fd772e31384 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Tue, 7 Feb 2023 02:59:47 +0000 Subject: [PATCH 4/8] Add support for using dynamic modules --- src/Bonsai.Scripting.Python/Eval.cs | 23 ++++++++++++++++++++++- src/Bonsai.Scripting.Python/Exec.cs | 24 +++++++++++++++++++++++- src/Bonsai.Scripting.Python/Get.cs | 26 +++++++++++++++++++++++++- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/Bonsai.Scripting.Python/Eval.cs b/src/Bonsai.Scripting.Python/Eval.cs index cba33ec..6f78b36 100644 --- a/src/Bonsai.Scripting.Python/Eval.cs +++ b/src/Bonsai.Scripting.Python/Eval.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; using System.Reactive.Linq; @@ -55,5 +55,26 @@ public override IObservable Process(IObservable sour }); }); } + + /// + /// Evaluates a Python expression in an observable sequence of modules. + /// + /// + /// The sequence of modules in which to evaluate the Python expression. + /// + /// + /// A sequence of handles representing the result + /// of evaluating the Python expression. + /// + public IObservable Process(IObservable source) + { + return source.Select(module => + { + using (Py.GIL()) + { + return module.Eval(Expression); + } + }); + } } } diff --git a/src/Bonsai.Scripting.Python/Exec.cs b/src/Bonsai.Scripting.Python/Exec.cs index cc49719..85548eb 100644 --- a/src/Bonsai.Scripting.Python/Exec.cs +++ b/src/Bonsai.Scripting.Python/Exec.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; using System.Reactive.Linq; @@ -56,5 +56,27 @@ public override IObservable Process(IObservable sour }); }); } + + /// + /// Executes a Python script in an observable sequence of modules. + /// + /// + /// The sequence of modules in which to execute the Python script. + /// + /// + /// An observable sequence that is identical to the + /// sequence but where there is an additional side effect of executing the + /// Python script in each of the objects. + /// + public IObservable Process(IObservable source) + { + return source.Select(module => + { + using (Py.GIL()) + { + return module.Exec(Script); + } + }); + } } } diff --git a/src/Bonsai.Scripting.Python/Get.cs b/src/Bonsai.Scripting.Python/Get.cs index 7726774..58199ee 100644 --- a/src/Bonsai.Scripting.Python/Get.cs +++ b/src/Bonsai.Scripting.Python/Get.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; using System.Reactive.Linq; @@ -72,5 +72,29 @@ public IObservable Generate(IObservable source) }); }); } + + /// + /// Gets the value of the specified variable in each of the Python modules + /// in an observable sequence. + /// + /// + /// The sequence of modules from which to get the value of the specified + /// variable. + /// + /// + /// A sequence of handles representing the value + /// of the specified variable for each of the modules in the + /// sequence. + /// + public IObservable Process(IObservable source) + { + return source.Select(module => + { + using (Py.GIL()) + { + return module.Get(VariableName); + } + }); + } } } From a3e8297db44f2f14bead75091e65f65fbc5143d2 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Tue, 7 Feb 2023 03:34:34 +0000 Subject: [PATCH 5/8] Refactor to avoid using resource pattern --- .../Bonsai.Scripting.Python.csproj | 1 - .../Configuration/CreateModule.cs | 44 --------- .../Configuration/ModuleResourceCollection.cs | 20 ---- src/Bonsai.Scripting.Python/CreateModule.cs | 69 ++++++++++++++ src/Bonsai.Scripting.Python/Eval.cs | 4 +- src/Bonsai.Scripting.Python/Exec.cs | 4 +- src/Bonsai.Scripting.Python/Get.cs | 6 +- .../ModuleNameConverter.cs | 92 ++++++++++++++++++- .../ModuleResources.cs | 51 ---------- src/Bonsai.Scripting.Python/RuntimeManager.cs | 25 ++--- src/Bonsai.Scripting.Python/RuntimeModule.cs | 36 ++++++++ src/Bonsai.Scripting.Python/Set.cs | 2 +- 12 files changed, 207 insertions(+), 147 deletions(-) delete mode 100644 src/Bonsai.Scripting.Python/Configuration/CreateModule.cs delete mode 100644 src/Bonsai.Scripting.Python/Configuration/ModuleResourceCollection.cs create mode 100644 src/Bonsai.Scripting.Python/CreateModule.cs delete mode 100644 src/Bonsai.Scripting.Python/ModuleResources.cs create mode 100644 src/Bonsai.Scripting.Python/RuntimeModule.cs diff --git a/src/Bonsai.Scripting.Python/Bonsai.Scripting.Python.csproj b/src/Bonsai.Scripting.Python/Bonsai.Scripting.Python.csproj index 165d699..af96fbf 100644 --- a/src/Bonsai.Scripting.Python/Bonsai.Scripting.Python.csproj +++ b/src/Bonsai.Scripting.Python/Bonsai.Scripting.Python.csproj @@ -11,7 +11,6 @@ - diff --git a/src/Bonsai.Scripting.Python/Configuration/CreateModule.cs b/src/Bonsai.Scripting.Python/Configuration/CreateModule.cs deleted file mode 100644 index b12d147..0000000 --- a/src/Bonsai.Scripting.Python/Configuration/CreateModule.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.ComponentModel; -using System.IO; -using Bonsai.Resources; -using Python.Runtime; - -namespace Bonsai.Scripting.Python.Configuration -{ - /// - /// Provides configuration and loading functionality for top-level modules. - /// - public class CreateModule : ResourceConfiguration - { - /// - /// Gets or sets the path to the Python script file to run on module initialization. - /// - [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)] - [TypeConverter(typeof(ResourceFileNameConverter))] - public string ScriptPath { get; set; } - - /// - public override PyModule CreateResource(ResourceManager resourceManager) - { - var scope = Py.CreateScope(Name); - if (!string.IsNullOrEmpty(ScriptPath)) - { - var code = File.ReadAllText(ScriptPath); - scope.Exec(code); - } - return scope; - } - - /// - public override string ToString() - { - var script = ScriptPath; - var baseName = Name; - if (string.IsNullOrEmpty(baseName)) baseName = nameof(CreateModule); - if (string.IsNullOrEmpty(script)) return baseName; - else return $"{baseName} [{script}]"; - } - } -} diff --git a/src/Bonsai.Scripting.Python/Configuration/ModuleResourceCollection.cs b/src/Bonsai.Scripting.Python/Configuration/ModuleResourceCollection.cs deleted file mode 100644 index 2561ffc..0000000 --- a/src/Bonsai.Scripting.Python/Configuration/ModuleResourceCollection.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.ObjectModel; - -namespace Bonsai.Scripting.Python.Configuration -{ - /// - /// Represents a collection of module resource configuration objects. - /// - public class ModuleResourceCollection : KeyedCollection - { - /// - /// Returns the key for the specified configuration object. - /// - /// The configuration object from which to extract the key. - /// The key for the specified configuration object. - protected override string GetKeyForItem(CreateModule item) - { - return item.Name; - } - } -} diff --git a/src/Bonsai.Scripting.Python/CreateModule.cs b/src/Bonsai.Scripting.Python/CreateModule.cs new file mode 100644 index 0000000..3bcdcbf --- /dev/null +++ b/src/Bonsai.Scripting.Python/CreateModule.cs @@ -0,0 +1,69 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Reactive.Linq; +using Python.Runtime; + +namespace Bonsai.Scripting.Python +{ + /// + /// Represents an operator that creates a top-level module in the Python runtime. + /// + public class CreateModule : Source + { + /// + /// Gets or sets the name of the top-level module. + /// + [Description("The name of the top-level module.")] + public string Name { get; set; } + + /// + /// Gets or sets the path to the Python script file to run on module initialization. + /// + [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; } + + /// + /// Generates an observable sequence that contains the created top-level module. + /// + /// + /// A sequence containing a single instance of the class + /// representing the created top-level module. + /// + public override IObservable Generate() + { + return Generate(RuntimeManager.RuntimeSource); + } + + /// + /// Generates an observable sequence that contains the created top-level modules. + /// + /// + /// An observable sequence of the in which to create + /// the top-level module. + /// + /// + /// A sequence of objects representing all the created + /// top-level modules. + /// + public IObservable Generate(IObservable source) + { + return source.SelectMany(runtime => Observable.Create(observer => + { + using (Py.GIL()) + { + var module = new RuntimeModule(runtime, Name); + if (!string.IsNullOrEmpty(ScriptPath)) + { + var code = File.ReadAllText(ScriptPath); + module.Scope.Exec(code); + } + observer.OnNext(module.Scope); + return module; + } + })); + } + } +} diff --git a/src/Bonsai.Scripting.Python/Eval.cs b/src/Bonsai.Scripting.Python/Eval.cs index 6f78b36..3816ee2 100644 --- a/src/Bonsai.Scripting.Python/Eval.cs +++ b/src/Bonsai.Scripting.Python/Eval.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; using System.Reactive.Linq; @@ -45,7 +45,7 @@ public override IObservable Process(IObservable sour { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var module = runtime.Resources.Load(ModuleName); + var module = runtime.Modules[ModuleName].Scope; return source.Select(_ => { using (Py.GIL()) diff --git a/src/Bonsai.Scripting.Python/Exec.cs b/src/Bonsai.Scripting.Python/Exec.cs index 85548eb..c6cf9b3 100644 --- a/src/Bonsai.Scripting.Python/Exec.cs +++ b/src/Bonsai.Scripting.Python/Exec.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; using System.Reactive.Linq; @@ -46,7 +46,7 @@ public override IObservable Process(IObservable sour { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var module = runtime.Resources.Load(ModuleName); + var module = runtime.Modules[ModuleName].Scope; return source.Select(_ => { using (Py.GIL()) diff --git a/src/Bonsai.Scripting.Python/Get.cs b/src/Bonsai.Scripting.Python/Get.cs index 58199ee..6f88ccb 100644 --- a/src/Bonsai.Scripting.Python/Get.cs +++ b/src/Bonsai.Scripting.Python/Get.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; using System.Reactive.Linq; @@ -39,7 +39,7 @@ public override IObservable Generate() { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var module = runtime.Resources.Load(ModuleName); + var module = runtime.Modules[ModuleName].Scope; return Observable.Return(module.Get(VariableName)); }); } @@ -62,7 +62,7 @@ public IObservable Generate(IObservable source) { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var module = runtime.Resources.Load(ModuleName); + var module = runtime.Modules[ModuleName].Scope; return source.Select(_ => { using (Py.GIL()) diff --git a/src/Bonsai.Scripting.Python/ModuleNameConverter.cs b/src/Bonsai.Scripting.Python/ModuleNameConverter.cs index 5456965..34c98da 100644 --- a/src/Bonsai.Scripting.Python/ModuleNameConverter.cs +++ b/src/Bonsai.Scripting.Python/ModuleNameConverter.cs @@ -1,13 +1,95 @@ -using Bonsai.Resources; -using Python.Runtime; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using Bonsai.Expressions; namespace Bonsai.Scripting.Python { - class ModuleNameConverter : ResourceNameConverter + class ModuleNameConverter : StringConverter { - public ModuleNameConverter() - : base(typeof(PyModule)) + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { + return context != null; + } + + static bool IsGroup(IWorkflowExpressionBuilder builder) + { + return builder is IncludeWorkflowBuilder || builder is GroupWorkflowBuilder; + } + + static IEnumerable SelectContextElements(ExpressionBuilderGraph source) + { + foreach (var node in source) + { + var element = ExpressionBuilder.Unwrap(node.Value); + yield return element; + + var workflowBuilder = element as IWorkflowExpressionBuilder; + if (IsGroup(workflowBuilder)) + { + var workflow = workflowBuilder.Workflow; + if (workflow == null) continue; + foreach (var groupElement in SelectContextElements(workflow)) + { + yield return groupElement; + } + } + } + } + + static bool GetCallContext(ExpressionBuilderGraph source, ExpressionBuilderGraph target, Stack context) + { + context.Push(source); + if (source == target) + { + return true; + } + + foreach (var element in SelectContextElements(source)) + { + var groupBuilder = element as IWorkflowExpressionBuilder; + if (IsGroup(groupBuilder) && groupBuilder.Workflow == target) + { + return true; + } + + if (element is WorkflowExpressionBuilder workflowBuilder) + { + if (GetCallContext(workflowBuilder.Workflow, target, context)) + { + return true; + } + } + } + + context.Pop(); + return false; + } + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + if (context != null) + { + var workflowBuilder = (WorkflowBuilder)context.GetService(typeof(WorkflowBuilder)); + var builderGraph = (ExpressionBuilderGraph)context.GetService(typeof(ExpressionBuilderGraph)); + if (workflowBuilder != null && builderGraph != null) + { + var callContext = new Stack(); + if (GetCallContext(workflowBuilder.Workflow, builderGraph, callContext)) + { + var names = (from level in callContext + from element in SelectContextElements(level) + let module = ExpressionBuilder.GetWorkflowElement(element) as CreateModule + where module != null + select module.Name) + .Distinct() + .ToList(); + return new StandardValuesCollection(names); + } + } + } + + return base.GetStandardValues(context); } } } diff --git a/src/Bonsai.Scripting.Python/ModuleResources.cs b/src/Bonsai.Scripting.Python/ModuleResources.cs deleted file mode 100644 index fbda376..0000000 --- a/src/Bonsai.Scripting.Python/ModuleResources.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using Bonsai.Resources; -using Bonsai.Scripting.Python.Configuration; - -namespace Bonsai.Scripting.Python -{ - /// - /// Represents an operator that creates a collection of module resources to - /// be loaded into the runtime resource manager. - /// - [DefaultProperty(nameof(Resources))] - [Description("Creates a collection of module resources to be loaded into the runtime resource manager.")] - public class ModuleResources : ResourceLoader - { - /// - /// Gets the collection of module resources to be loaded into the runtime resource manager. - /// - [Editor("Bonsai.Resources.Design.ResourceCollectionEditor, Bonsai.System.Design", DesignTypes.UITypeEditor)] - [Description("The collection of module resources to be loaded into the runtime resource manager.")] - public ModuleResourceCollection Resources { get; } = new ModuleResourceCollection(); - - /// - protected override IEnumerable GetResources() - { - return Resources; - } - - /// - /// Bundles a set of resources to be loaded into the runtime resource manager. - /// - /// - /// A sequence containing the object into which - /// the resources will be loaded. - /// - /// - /// A sequence of objects which - /// can be used to load additional resources into the resource manager. - /// - public IObservable Process(IObservable source) - { - return source.Select(runtime => - { - return new ResourceConfigurationCollection(runtime.Resources, GetResources()); - }); - } - } -} diff --git a/src/Bonsai.Scripting.Python/RuntimeManager.cs b/src/Bonsai.Scripting.Python/RuntimeManager.cs index 04cdc79..261d578 100644 --- a/src/Bonsai.Scripting.Python/RuntimeManager.cs +++ b/src/Bonsai.Scripting.Python/RuntimeManager.cs @@ -1,8 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; using System.Reactive.Concurrency; using System.Reactive.Linq; -using Bonsai.Resources; using Python.Runtime; namespace Bonsai.Scripting.Python @@ -15,23 +15,20 @@ namespace Bonsai.Scripting.Python public class RuntimeManager : IDisposable { readonly EventLoopScheduler runtimeScheduler; - readonly ResourceManager runtimeResources; + readonly Dictionary runtimeModules; readonly IObserver runtimeObserver; IntPtr threadState; internal RuntimeManager(string path, IObserver observer) { runtimeScheduler = new EventLoopScheduler(); - runtimeResources = new ResourceManager(); + runtimeModules = new Dictionary(); runtimeObserver = observer; Schedule(() => { Initialize(path); threadState = PythonEngine.BeginAllowThreads(); - using (Py.GIL()) - { - observer.OnNext(this); - } + observer.OnNext(this); }); } @@ -39,7 +36,7 @@ internal RuntimeManager(string path, IObserver observer) () => SubjectManager.ReserveSubject(), disposable => disposable.Subject); - internal ResourceManager Resources => runtimeResources; + internal Dictionary Modules => runtimeModules; internal void Schedule(Action action) { @@ -76,15 +73,6 @@ static void Initialize(string path) } } - void DisposeInternal() - { - using (Py.GIL()) - { - Resources.Dispose(); - } - PythonEngine.EndAllowThreads(threadState); - } - /// /// Shutdown the thread and release all resources associated with the Python runtime. /// All remaining work scheduled after shutdown is abandoned. @@ -95,8 +83,9 @@ public void Dispose() { if (PythonEngine.IsInitialized) { - DisposeInternal(); + PythonEngine.EndAllowThreads(threadState); PythonEngine.Shutdown(); + runtimeModules.Clear(); } runtimeScheduler.Dispose(); }); diff --git a/src/Bonsai.Scripting.Python/RuntimeModule.cs b/src/Bonsai.Scripting.Python/RuntimeModule.cs new file mode 100644 index 0000000..7b10e83 --- /dev/null +++ b/src/Bonsai.Scripting.Python/RuntimeModule.cs @@ -0,0 +1,36 @@ +using System; +using Python.Runtime; + +namespace Bonsai.Scripting.Python +{ + class RuntimeModule : IDisposable + { + public RuntimeModule(RuntimeManager owner, string name) + { + Owner = owner ?? throw new ArgumentNullException(nameof(owner)); + Name = name ?? string.Empty; + Scope = Py.CreateScope(Name); + owner.Modules.Add(Name, this); + ShutdownHandler = Scope.Dispose; + PythonEngine.AddShutdownHandler(ShutdownHandler); + } + + public string Name { get; } + + public PyModule Scope { get; } + + RuntimeManager Owner { get; } + + PythonEngine.ShutdownHandler ShutdownHandler { get; } + + public void Dispose() + { + using (Py.GIL()) + { + PythonEngine.RemoveShutdownHandler(ShutdownHandler); + Owner.Modules.Remove(Name); + Scope.Dispose(); + } + } + } +} diff --git a/src/Bonsai.Scripting.Python/Set.cs b/src/Bonsai.Scripting.Python/Set.cs index 05b445c..29feaff 100644 --- a/src/Bonsai.Scripting.Python/Set.cs +++ b/src/Bonsai.Scripting.Python/Set.cs @@ -46,7 +46,7 @@ public override IObservable Process(IObservable sourc { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var module = runtime.Resources.Load(ModuleName); + var module = runtime.Modules[ModuleName].Scope; return source.Do(value => { using (Py.GIL()) From 51d8bc3b0f1cf75b00bea430d80187c87fb3410b Mon Sep 17 00:00:00 2001 From: glopesdev Date: Tue, 7 Feb 2023 09:11:14 +0000 Subject: [PATCH 6/8] Avoid shared module dictionary --- src/Bonsai.Scripting.Python/CreateModule.cs | 27 +----- src/Bonsai.Scripting.Python/DynamicModule.cs | 38 ++++++++ src/Bonsai.Scripting.Python/Eval.cs | 12 +-- src/Bonsai.Scripting.Python/Exec.cs | 12 +-- src/Bonsai.Scripting.Python/Get.cs | 15 ++- .../ModuleNameConverter.cs | 95 ------------------- src/Bonsai.Scripting.Python/RuntimeManager.cs | 8 +- src/Bonsai.Scripting.Python/RuntimeModule.cs | 36 ------- src/Bonsai.Scripting.Python/Set.cs | 12 +-- 9 files changed, 70 insertions(+), 185 deletions(-) create mode 100644 src/Bonsai.Scripting.Python/DynamicModule.cs delete mode 100644 src/Bonsai.Scripting.Python/ModuleNameConverter.cs delete mode 100644 src/Bonsai.Scripting.Python/RuntimeModule.cs diff --git a/src/Bonsai.Scripting.Python/CreateModule.cs b/src/Bonsai.Scripting.Python/CreateModule.cs index 3bcdcbf..3d5dd47 100644 --- a/src/Bonsai.Scripting.Python/CreateModule.cs +++ b/src/Bonsai.Scripting.Python/CreateModule.cs @@ -34,36 +34,19 @@ public class CreateModule : Source /// public override IObservable Generate() { - return Generate(RuntimeManager.RuntimeSource); - } - - /// - /// Generates an observable sequence that contains the created top-level modules. - /// - /// - /// An observable sequence of the in which to create - /// the top-level module. - /// - /// - /// A sequence of objects representing all the created - /// top-level modules. - /// - public IObservable Generate(IObservable source) - { - return source.SelectMany(runtime => Observable.Create(observer => + return RuntimeManager.RuntimeSource.SelectMany(runtime => { using (Py.GIL()) { - var module = new RuntimeModule(runtime, Name); + var module = new DynamicModule(Name ?? string.Empty); if (!string.IsNullOrEmpty(ScriptPath)) { var code = File.ReadAllText(ScriptPath); - module.Scope.Exec(code); + module.Exec(code); } - observer.OnNext(module.Scope); - return module; + return Observable.Return(module); } - })); + }); } } } diff --git a/src/Bonsai.Scripting.Python/DynamicModule.cs b/src/Bonsai.Scripting.Python/DynamicModule.cs new file mode 100644 index 0000000..fa56edc --- /dev/null +++ b/src/Bonsai.Scripting.Python/DynamicModule.cs @@ -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); + } + } +} diff --git a/src/Bonsai.Scripting.Python/Eval.cs b/src/Bonsai.Scripting.Python/Eval.cs index 3816ee2..c52a8cc 100644 --- a/src/Bonsai.Scripting.Python/Eval.cs +++ b/src/Bonsai.Scripting.Python/Eval.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Linq; using System.Reactive.Linq; +using System.Xml.Serialization; using Python.Runtime; namespace Bonsai.Scripting.Python @@ -15,11 +16,11 @@ namespace Bonsai.Scripting.Python public class Eval : Combinator { /// - /// Gets or sets the name of the top-level module on which to evaluate the Python expression. + /// Gets or sets the top-level module on which to evaluate the Python expression. /// - [TypeConverter(typeof(ModuleNameConverter))] - [Description("The name of the top-level module on which to evaluate the Python expression.")] - public string ModuleName { get; set; } + [XmlIgnore] + [Description("The top-level module on which to evaluate the Python expression.")] + public PyModule Module { get; set; } /// /// Gets or sets the Python expression to evaluate. @@ -45,12 +46,11 @@ public override IObservable Process(IObservable sour { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var module = runtime.Modules[ModuleName].Scope; return source.Select(_ => { using (Py.GIL()) { - return module.Eval(Expression); + return Module.Eval(Expression); } }); }); diff --git a/src/Bonsai.Scripting.Python/Exec.cs b/src/Bonsai.Scripting.Python/Exec.cs index c6cf9b3..77855c4 100644 --- a/src/Bonsai.Scripting.Python/Exec.cs +++ b/src/Bonsai.Scripting.Python/Exec.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Linq; using System.Reactive.Linq; +using System.Xml.Serialization; using Python.Runtime; namespace Bonsai.Scripting.Python @@ -15,11 +16,11 @@ namespace Bonsai.Scripting.Python public class Exec : Combinator { /// - /// Gets or sets the name of the top-level module on which to execute the Python script. + /// Gets or sets the top-level module on which to execute the Python script. /// - [TypeConverter(typeof(ModuleNameConverter))] - [Description("The name of the top-level module on which to execute the Python script.")] - public string ModuleName { get; set; } + [XmlIgnore] + [Description("The top-level module on which to execute the Python script.")] + public PyModule Module { get; set; } /// /// Gets or sets the Python script to evaluate. @@ -46,12 +47,11 @@ public override IObservable Process(IObservable sour { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var module = runtime.Modules[ModuleName].Scope; return source.Select(_ => { using (Py.GIL()) { - return module.Exec(Script); + return Module.Exec(Script); } }); }); diff --git a/src/Bonsai.Scripting.Python/Get.cs b/src/Bonsai.Scripting.Python/Get.cs index 6f88ccb..2d43335 100644 --- a/src/Bonsai.Scripting.Python/Get.cs +++ b/src/Bonsai.Scripting.Python/Get.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Linq; using System.Reactive.Linq; +using System.Xml.Serialization; using Python.Runtime; namespace Bonsai.Scripting.Python @@ -15,11 +16,11 @@ namespace Bonsai.Scripting.Python public class Get : Source { /// - /// Gets or sets the name of the Python module containing the variable. + /// Gets or sets the Python module containing the variable. /// - [TypeConverter(typeof(ModuleNameConverter))] - [Description("The name of the Python module containing the variable.")] - public string ModuleName { get; set; } + [XmlIgnore] + [Description("The Python module containing the variable.")] + public PyModule Module { get; set; } /// /// Gets or sets the name of the variable to get the value of. @@ -39,8 +40,7 @@ public override IObservable Generate() { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var module = runtime.Modules[ModuleName].Scope; - return Observable.Return(module.Get(VariableName)); + return Observable.Return(Module.Get(VariableName)); }); } @@ -62,12 +62,11 @@ public IObservable Generate(IObservable source) { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var module = runtime.Modules[ModuleName].Scope; return source.Select(_ => { using (Py.GIL()) { - return module.Get(VariableName); + return Module.Get(VariableName); } }); }); diff --git a/src/Bonsai.Scripting.Python/ModuleNameConverter.cs b/src/Bonsai.Scripting.Python/ModuleNameConverter.cs deleted file mode 100644 index 34c98da..0000000 --- a/src/Bonsai.Scripting.Python/ModuleNameConverter.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using Bonsai.Expressions; - -namespace Bonsai.Scripting.Python -{ - class ModuleNameConverter : StringConverter - { - public override bool GetStandardValuesSupported(ITypeDescriptorContext context) - { - return context != null; - } - - static bool IsGroup(IWorkflowExpressionBuilder builder) - { - return builder is IncludeWorkflowBuilder || builder is GroupWorkflowBuilder; - } - - static IEnumerable SelectContextElements(ExpressionBuilderGraph source) - { - foreach (var node in source) - { - var element = ExpressionBuilder.Unwrap(node.Value); - yield return element; - - var workflowBuilder = element as IWorkflowExpressionBuilder; - if (IsGroup(workflowBuilder)) - { - var workflow = workflowBuilder.Workflow; - if (workflow == null) continue; - foreach (var groupElement in SelectContextElements(workflow)) - { - yield return groupElement; - } - } - } - } - - static bool GetCallContext(ExpressionBuilderGraph source, ExpressionBuilderGraph target, Stack context) - { - context.Push(source); - if (source == target) - { - return true; - } - - foreach (var element in SelectContextElements(source)) - { - var groupBuilder = element as IWorkflowExpressionBuilder; - if (IsGroup(groupBuilder) && groupBuilder.Workflow == target) - { - return true; - } - - if (element is WorkflowExpressionBuilder workflowBuilder) - { - if (GetCallContext(workflowBuilder.Workflow, target, context)) - { - return true; - } - } - } - - context.Pop(); - return false; - } - - public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) - { - if (context != null) - { - var workflowBuilder = (WorkflowBuilder)context.GetService(typeof(WorkflowBuilder)); - var builderGraph = (ExpressionBuilderGraph)context.GetService(typeof(ExpressionBuilderGraph)); - if (workflowBuilder != null && builderGraph != null) - { - var callContext = new Stack(); - if (GetCallContext(workflowBuilder.Workflow, builderGraph, callContext)) - { - var names = (from level in callContext - from element in SelectContextElements(level) - let module = ExpressionBuilder.GetWorkflowElement(element) as CreateModule - where module != null - select module.Name) - .Distinct() - .ToList(); - return new StandardValuesCollection(names); - } - } - } - - return base.GetStandardValues(context); - } - } -} diff --git a/src/Bonsai.Scripting.Python/RuntimeManager.cs b/src/Bonsai.Scripting.Python/RuntimeManager.cs index 261d578..8b355c3 100644 --- a/src/Bonsai.Scripting.Python/RuntimeManager.cs +++ b/src/Bonsai.Scripting.Python/RuntimeManager.cs @@ -15,14 +15,12 @@ namespace Bonsai.Scripting.Python public class RuntimeManager : IDisposable { readonly EventLoopScheduler runtimeScheduler; - readonly Dictionary runtimeModules; readonly IObserver runtimeObserver; IntPtr threadState; internal RuntimeManager(string path, IObserver observer) { runtimeScheduler = new EventLoopScheduler(); - runtimeModules = new Dictionary(); runtimeObserver = observer; Schedule(() => { @@ -34,9 +32,8 @@ internal RuntimeManager(string path, IObserver observer) internal static IObservable RuntimeSource { get; } = Observable.Using( () => SubjectManager.ReserveSubject(), - disposable => disposable.Subject); - - internal Dictionary Modules => runtimeModules; + disposable => disposable.Subject) + .Take(1); internal void Schedule(Action action) { @@ -85,7 +82,6 @@ public void Dispose() { PythonEngine.EndAllowThreads(threadState); PythonEngine.Shutdown(); - runtimeModules.Clear(); } runtimeScheduler.Dispose(); }); diff --git a/src/Bonsai.Scripting.Python/RuntimeModule.cs b/src/Bonsai.Scripting.Python/RuntimeModule.cs deleted file mode 100644 index 7b10e83..0000000 --- a/src/Bonsai.Scripting.Python/RuntimeModule.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using Python.Runtime; - -namespace Bonsai.Scripting.Python -{ - class RuntimeModule : IDisposable - { - public RuntimeModule(RuntimeManager owner, string name) - { - Owner = owner ?? throw new ArgumentNullException(nameof(owner)); - Name = name ?? string.Empty; - Scope = Py.CreateScope(Name); - owner.Modules.Add(Name, this); - ShutdownHandler = Scope.Dispose; - PythonEngine.AddShutdownHandler(ShutdownHandler); - } - - public string Name { get; } - - public PyModule Scope { get; } - - RuntimeManager Owner { get; } - - PythonEngine.ShutdownHandler ShutdownHandler { get; } - - public void Dispose() - { - using (Py.GIL()) - { - PythonEngine.RemoveShutdownHandler(ShutdownHandler); - Owner.Modules.Remove(Name); - Scope.Dispose(); - } - } - } -} diff --git a/src/Bonsai.Scripting.Python/Set.cs b/src/Bonsai.Scripting.Python/Set.cs index 29feaff..67a1000 100644 --- a/src/Bonsai.Scripting.Python/Set.cs +++ b/src/Bonsai.Scripting.Python/Set.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Linq; using System.Reactive.Linq; +using System.Xml.Serialization; using Python.Runtime; namespace Bonsai.Scripting.Python @@ -15,11 +16,11 @@ namespace Bonsai.Scripting.Python public class Set : Sink { /// - /// Gets or sets the name of the Python top-level module containing the variable. + /// Gets or sets the Python top-level module containing the variable. /// - [TypeConverter(typeof(ModuleNameConverter))] - [Description("The name of the Python top-level module containing the variable.")] - public string ModuleName { get; set; } + [XmlIgnore] + [Description("The Python top-level module containing the variable.")] + public PyModule Module { get; set; } /// /// Gets or sets the name of the variable to add or update the value of. @@ -46,12 +47,11 @@ public override IObservable Process(IObservable sourc { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - var module = runtime.Modules[ModuleName].Scope; return source.Do(value => { using (Py.GIL()) { - module.Set(VariableName, value); + Module.Set(VariableName, value); } }); }); From 48967c87e17dea0352042d5bef958013302a7864 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Tue, 7 Feb 2023 09:44:03 +0000 Subject: [PATCH 7/8] Add default main module --- src/Bonsai.Scripting.Python/Eval.cs | 24 ++++++++++++++- src/Bonsai.Scripting.Python/Exec.cs | 24 ++++++++++++++- src/Bonsai.Scripting.Python/Get.cs | 29 +++++++++++++++++-- src/Bonsai.Scripting.Python/RuntimeManager.cs | 10 +++++++ src/Bonsai.Scripting.Python/Set.cs | 3 +- 5 files changed, 85 insertions(+), 5 deletions(-) diff --git a/src/Bonsai.Scripting.Python/Eval.cs b/src/Bonsai.Scripting.Python/Eval.cs index c52a8cc..2795680 100644 --- a/src/Bonsai.Scripting.Python/Eval.cs +++ b/src/Bonsai.Scripting.Python/Eval.cs @@ -50,7 +50,8 @@ public override IObservable Process(IObservable sour { using (Py.GIL()) { - return Module.Eval(Expression); + var module = Module ?? runtime.MainModule; + return module.Eval(Expression); } }); }); @@ -76,5 +77,26 @@ public IObservable Process(IObservable source) } }); } + + /// + /// Evaluates an expression in the main module of the Python runtime. + /// + /// + /// A sequence containing the Python runtime in which to evaluate the expression. + /// + /// + /// A sequence of handles representing the result + /// of evaluating the Python expression. + /// + public IObservable Process(IObservable source) + { + return source.Select(runtime => + { + using (Py.GIL()) + { + return runtime.MainModule.Eval(Expression); + } + }); + } } } diff --git a/src/Bonsai.Scripting.Python/Exec.cs b/src/Bonsai.Scripting.Python/Exec.cs index 77855c4..21b77a1 100644 --- a/src/Bonsai.Scripting.Python/Exec.cs +++ b/src/Bonsai.Scripting.Python/Exec.cs @@ -51,7 +51,8 @@ public override IObservable Process(IObservable sour { using (Py.GIL()) { - return Module.Exec(Script); + var module = Module ?? runtime.MainModule; + return module.Exec(Script); } }); }); @@ -78,5 +79,26 @@ public IObservable Process(IObservable source) } }); } + + /// + /// Executes a script in the main module of the Python runtime. + /// + /// + /// A sequence containing the Python runtime in which to execute the script. + /// + /// + /// A sequence containing the object representing + /// the top-level module where the Python script was executed. + /// + public IObservable Process(IObservable source) + { + return source.Select(runtime => + { + using (Py.GIL()) + { + return runtime.MainModule.Exec(Script); + } + }); + } } } diff --git a/src/Bonsai.Scripting.Python/Get.cs b/src/Bonsai.Scripting.Python/Get.cs index 2d43335..bb34183 100644 --- a/src/Bonsai.Scripting.Python/Get.cs +++ b/src/Bonsai.Scripting.Python/Get.cs @@ -40,7 +40,8 @@ public override IObservable Generate() { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - return Observable.Return(Module.Get(VariableName)); + var module = Module ?? runtime.MainModule; + return Observable.Return(module.Get(VariableName)); }); } @@ -66,7 +67,8 @@ public IObservable Generate(IObservable source) { using (Py.GIL()) { - return Module.Get(VariableName); + var module = Module ?? runtime.MainModule; + return module.Get(VariableName); } }); }); @@ -95,5 +97,28 @@ public IObservable Process(IObservable source) } }); } + + /// + /// Gets the value of the specified variable in the main module of the + /// Python runtime. + /// + /// + /// A sequence containing the Python runtime from which to get the + /// value of the specified variable. + /// + /// + /// A sequence of handles representing the value + /// of the specified variable in the main module of the Python runtime. + /// + public IObservable Process(IObservable source) + { + return source.Select(runtime => + { + using (Py.GIL()) + { + return runtime.MainModule.Get(VariableName); + } + }); + } } } diff --git a/src/Bonsai.Scripting.Python/RuntimeManager.cs b/src/Bonsai.Scripting.Python/RuntimeManager.cs index 8b355c3..332ce30 100644 --- a/src/Bonsai.Scripting.Python/RuntimeManager.cs +++ b/src/Bonsai.Scripting.Python/RuntimeManager.cs @@ -26,10 +26,16 @@ internal RuntimeManager(string path, IObserver observer) { Initialize(path); threadState = PythonEngine.BeginAllowThreads(); + using (Py.GIL()) + { + MainModule = Py.CreateScope(); + } observer.OnNext(this); }); } + internal PyModule MainModule { get; private set; } + internal static IObservable RuntimeSource { get; } = Observable.Using( () => SubjectManager.ReserveSubject(), disposable => disposable.Subject) @@ -80,6 +86,10 @@ public void Dispose() { if (PythonEngine.IsInitialized) { + using (Py.GIL()) + { + MainModule.Dispose(); + } PythonEngine.EndAllowThreads(threadState); PythonEngine.Shutdown(); } diff --git a/src/Bonsai.Scripting.Python/Set.cs b/src/Bonsai.Scripting.Python/Set.cs index 67a1000..b98ae69 100644 --- a/src/Bonsai.Scripting.Python/Set.cs +++ b/src/Bonsai.Scripting.Python/Set.cs @@ -51,7 +51,8 @@ public override IObservable Process(IObservable sourc { using (Py.GIL()) { - Module.Set(VariableName, value); + var module = Module ?? runtime.MainModule; + module.Set(VariableName, value); } }); }); From f542b2201a74e8ff446ab535fc59ce2dc68528b0 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Tue, 7 Feb 2023 09:53:14 +0000 Subject: [PATCH 8/8] Allow running script on main module creation --- src/Bonsai.Scripting.Python/CreateModule.cs | 12 ++--------- src/Bonsai.Scripting.Python/CreateRuntime.cs | 10 ++++++++- src/Bonsai.Scripting.Python/RuntimeManager.cs | 21 +++++++++++++++---- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/Bonsai.Scripting.Python/CreateModule.cs b/src/Bonsai.Scripting.Python/CreateModule.cs index 3d5dd47..091e5b0 100644 --- a/src/Bonsai.Scripting.Python/CreateModule.cs +++ b/src/Bonsai.Scripting.Python/CreateModule.cs @@ -36,16 +36,8 @@ public override IObservable Generate() { return RuntimeManager.RuntimeSource.SelectMany(runtime => { - using (Py.GIL()) - { - var module = new DynamicModule(Name ?? string.Empty); - if (!string.IsNullOrEmpty(ScriptPath)) - { - var code = File.ReadAllText(ScriptPath); - module.Exec(code); - } - return Observable.Return(module); - } + var module = RuntimeManager.CreateModule(Name ?? string.Empty, ScriptPath); + return Observable.Return(module); }); } } diff --git a/src/Bonsai.Scripting.Python/CreateRuntime.cs b/src/Bonsai.Scripting.Python/CreateRuntime.cs index f1027fb..79f8203 100644 --- a/src/Bonsai.Scripting.Python/CreateRuntime.cs +++ b/src/Bonsai.Scripting.Python/CreateRuntime.cs @@ -24,6 +24,14 @@ public class CreateRuntime : Source [Editor("Bonsai.Design.FolderNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] public string PythonHome { get; set; } + /// + /// Gets or sets the path to the Python script file to run on runtime initialization. + /// + [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; } + /// /// Creates an observable sequence that initializes a Python runtime object which /// can be used to import modules, evaluate expressions, and pass data to and @@ -39,7 +47,7 @@ public override IObservable 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, diff --git a/src/Bonsai.Scripting.Python/RuntimeManager.cs b/src/Bonsai.Scripting.Python/RuntimeManager.cs index 332ce30..d1e1257 100644 --- a/src/Bonsai.Scripting.Python/RuntimeManager.cs +++ b/src/Bonsai.Scripting.Python/RuntimeManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Reactive.Concurrency; using System.Reactive.Linq; @@ -18,17 +17,17 @@ public class RuntimeManager : IDisposable readonly IObserver runtimeObserver; IntPtr threadState; - internal RuntimeManager(string path, IObserver observer) + internal RuntimeManager(string pythonHome, string scriptPath, IObserver observer) { runtimeScheduler = new EventLoopScheduler(); runtimeObserver = observer; Schedule(() => { - Initialize(path); + Initialize(pythonHome); threadState = PythonEngine.BeginAllowThreads(); using (Py.GIL()) { - MainModule = Py.CreateScope(); + MainModule = CreateModule(scriptPath: scriptPath); } observer.OnNext(this); }); @@ -41,6 +40,20 @@ internal RuntimeManager(string path, IObserver observer) disposable => disposable.Subject) .Take(1); + internal static DynamicModule CreateModule(string name = "", string scriptPath = "") + { + using (Py.GIL()) + { + var module = new DynamicModule(name); + if (!string.IsNullOrEmpty(scriptPath)) + { + var code = File.ReadAllText(scriptPath); + module.Exec(code); + } + return module; + } + } + internal void Schedule(Action action) { runtimeScheduler.Schedule(() =>