IronPython - "AttributeError: object has no attribute" with custom classes - c#

This seems like there is just a simple error that I cannot figure out. I am using IronPython in a C# WPF application and am getting the following error when trying to run a function from a custom C# class:AttributeError: 'MyScriptingFunctions' object has no attribute 'Procedure'.
The python script I'm running is very simple and has two lines. Line 1 executes fine, the error occurs on line 2.
txt.Text = "some text"
MyFunc.Procedure(5)
MyScriptingFunctions.cs:
class MyScriptingFunctions
{
public MyScriptingFunctions() {}
public void Procedure(int num)
{
Console.WriteLine("Executing procedure " + num);
}
}
Here is how I setup the IronPython engine:
private void btnRunScript_Click(object sender, RoutedEventArgs e)
{
MyScriptingFunctions scriptFuncs = new MyScriptingFunctions();
ScriptEngine engine = Python.CreateEngine();
ScriptScope scope = engine.CreateScope();
ScriptRuntime runtime = engine.Runtime;
runtime.LoadAssembly(typeof(String).Assembly);
runtime.LoadAssembly(typeof(Uri).Assembly);
//Set Variable for the python script to use
scope.SetVariable("txt", fullReadResultsTextBox);
scope.SetVariable("MyFunc", scriptFuncs);
string code = this.scriptTextBox.Text;
try
{
ScriptSource source = engine.CreateScriptSourceFromString(code, SourceCodeKind.Statements);
source.Execute(scope);
}
catch (Exception ex)
{
ExceptionOperations eo;
eo = engine.GetService<ExceptionOperations>();
string error = eo.FormatException(ex);
MessageBox.Show(error, "There was an Error");
return;
}
}
I am simply setting two variables:txt which is of type System.Windows.Controls.TextBox, and MyFunc which is an object of my custom class MyScriptingFunctions.
What am I doing wrong and why does the python script execute the TextBox methods correctly and not my custom class's methods?

The only think I can see that might be an issue, or just a copy-paste error, is that the MyScriptingFunctions in not public. That shouldn't be an issue because you're passing an instance in, not trying to import the class, but it's worth a try. Otherwise everything looks fine.

Related

Implementing "Intellisense" for IronPython

In my C# application I have a text editor that allows the user to enter IronPython scripts. I have implemented a set of C# classes that are made available to the python environment.
I would now like to implement an "intellisense" type of system where the user enters a variable name then a dot and it prompts the user for a list of available methods and properties.
For example here is an IronPython script:
foo = MyClass()
foo.
At this point the cursor is just after the dot. MyClass example in C#:
public class MyClass {
public void Func1() ...
public void Func2() ...
}
Now I would like to give the user a pop up list showing Func1(), Func2(), etc.
What I need to do is take the variable name "foo" and get the class MyClass.
Note that I can't execute the IronPython code to do this because it performs actions in the user interface.
This is how far I have been able to get:
ScriptSource Source = engine.CreateScriptSourceFromString(pycode, SourceCodeKind.File);
SourceUnit su = HostingHelpers.GetSourceUnit(Source);
Microsoft.Scripting.Runtime.CompilerContext Ctxt = new Microsoft.Scripting.Runtime.CompilerContext(su, new IronPython.Compiler.PythonCompilerOptions(), ErrorSink.Null);
IronPython.Compiler.Parser Parser = IronPython.Compiler.Parser.CreateParser(Ctxt, new IronPython.PythonOptions());
IronPython.Compiler.Ast.PythonAst ast = Parser.ParseFile(false);
if (ast.Body is IronPython.Compiler.Ast.SuiteStatement)
{
IronPython.Compiler.Ast.SuiteStatement ss = ast.Body as IronPython.Compiler.Ast.SuiteStatement;
foreach (IronPython.Compiler.Ast.Statement s in ss.Statements)
{
if (s is IronPython.Compiler.Ast.ExpressionStatement)
{
IronPython.Compiler.Ast.ExpressionStatement es = s as IronPython.Compiler.Ast.ExpressionStatement;
}
}
}
I can see that the last line of the script foo. is an ExpressionStatement and I can drill down from there to get the NameExpression of 'foo' but I can't see how to get the type of the variable.
Is there a better way to do this? Is it even possible?
Thanks!

How to raise an OracleException for test purposes

I have a function that executes some SQL commands, and I've created a logger that I write in the file the command that was executed and the number of rows that were affected, but I also need to write down the command that may have raised an OracleException, so I've done this piece of code:
public string ExecuteCommand(List<string> comandos)
{
var excepção = string.Empty;
var executar = new OracleCommand
{
Connection = Updater.OraConnection,
CommandType = CommandType.Text
};
try
{
Logg("Inicio da execução de comandos");
foreach (var variable in comandos)
{
excepção = variable;
executar.CommandText = variable;
throw new OracleException(0, "comando", "stuff", "adasds");
var Afectados = executar.ExecuteNonQuery();
Logg(variable);
Logg("Linhas afectadas: " + Afectados);
}
}
catch (OracleException)
{
Logg("Erros:");
Logg(excepção);
return excepção;
}
return excepção;
}
I've tried to search everywhere but I cant fint any suitable or even focused answers, so Im kinda lost for why cant I raise an oracleException as I did like this: throw new OracleException(0, "comando", "stuff", "adasds");
It just says that Cannot access constructor here due to its protection level.
Any help would be aprecciated
If you just want to simulate the exception being thrown and do not care about interrogating the object then.
private OracleResilienceManager CreateSut()
{
return new OracleResilienceManager(_resilienceSettings);
}
throw System.Runtime.Serialization.CreateSafeUninitializedProtectedType<OracleException>();
In my case I was testing a retry policy and wanted to test the retry logic when this exception is thrown. Had no need to access the object itself.
public class OracleException : Exception
{
}
The class needs to have its scope set to public or internal. The constructor cannot be accessed as its a private class.

Delaying variable declaration in XAML

I am trying to finalize a C# / XAML page and am running into trouble. Basically, when I try to compile it is complaining that the name of the variable (Courier_List) does not exist in the current context. I understand by concept why as it has not been declared yet anywhere in the code. However, the reason I have not declared it is because this packaged .dll is meant to work with a management pack (XML file) that contains that definition there (Courier_List) defined as a list and contains the coordinates instructing the visual compiler where to place the list on the form.
I am guessing the solution must be to declare the list variable in the form... but I am not sure how (and if it will work) to just declare the variable and not use it anywhere within the .dll, then when everything is put together it will call the Courier_List from the management pack and not get confused between the two same name variables.
My description may be confusing as it was hard to explain this, so if anyone needs clarification please let me know. I have included the code below:
[assembly: CLSCompliant(true)]
namespace Flexity.RMA
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
class RMATask : CreateWithLinkHandler
{
public RMATask()
{
try
{
// Sealed Class GUID
this.createClassGuid = new Guid("9ebd95da-1b16-b9ea-274d-6b0c16ce1bf3");
this.classToDelegate = new Dictionary<Guid, CreateLinkHelperCallback>()
{
{ ApplicationConstants.WorkItemTypeId, new CreateLinkHelperCallback (this.WorkItemCallback) }
};
}
catch (Exception exc1)
{
MessageBox.Show(exc1.Message, "Exception", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
public void WorkItemCallback(IDataItem RMAForm, IDataItem IncidentForm)
{
try
{
// Note to self: RelatedWorkItems should be in MP XML as alias under TypeProjections
if (RMAForm != null && RMAForm.HasProperty("RelatedWorkItems"))
{
// Perform Linking
RMAForm["RelatedWorkItems"] = IncidentForm;
// Copy Incident Title to RMA Title
RMAForm["Title"] = IncidentForm["Title"];
// Copy Incident Description to RMA Description
RMAForm["Description"] = IncidentForm["Description"];
// Copy Incident ID to RMA Display Name
RMAForm["DisplayName"] = "From " + IncidentForm["Id"];
}
}
catch (Exception exc2)
{
MessageBox.Show(exc2.Message, "Exception", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
public partial class WITemplate: UserControl
{
private readonly RelatedItemsPane _relatedItemsPane;
public WITemplate()
{
InitializeComponent();
var paneConfig = new WorkItemRelatedItemsConfiguration("RelatedWorkItems", "RelatedWorkItemSource",
"RelatedConfigItems", "RelatedKnowledgeArticles",
"FileAttachments");
_relatedItemsPane = new RelatedItemsPane(paneConfig);
tabItemRelItems.Content = _relatedItemsPane;
}
private void Tracking_Button_Click(object sender, RoutedEventArgs e)
{
switch (Courier_List.SelectedValue.ToString())
{
case "UPS":
System.Diagnostics.Process.Start("http://wwwapps.ups.com/ietracking/tracking.cgi?loc=CA_CA^&tracknum^=" + Tracking_Num.Text.ToString());
break;
case "FedEX":
System.Diagnostics.Process.Start("https://www.fedex.com/fedextrack/index.html?tracknumbers^="+Tracking_Num.Text.ToString()+"^&locale=en_CA^&cntry_code=ca_english");
break;
case "UPS SCS":
System.Diagnostics.Process.Start("https://www.upspostsaleslogistics.com/cfw/trackOrder.do?trackNumber^=" + Tracking_Num.Text.ToString());
break;
default:
break;
}
}
}
}
I think you're correct about the obvious solution. Your button click handler doesn't know about the contents of the management pack XML unless you explicitly connect them.
So, you will need to create a variable for use in your Tracking_Button_Click handler for that switch statement. This is a pretty common pattern; if there is a resource somewhere that is needed in C# code you have to have a line or two of code that looks up the resource and assigns it to a variable you declare.
Done correctly, you will not be duplicating data. Rather, the system will set a reference to the existing classes. That in turn will permit you to use the management pack XML in the C# code.
I don't have examples for your management pack, but here are a couple others which follow the pattern. If you want to get at a XAML resource, you do something like:
var myList = (XAMLResourceType) this.TryFindResource("myResourceKey");
You don't need exactly this, but you need something very similar, in the code in your handler. Then your switch statement will work.

WPF Browser throws error

I am calling javascript functions from C# using the WPF variant of the browser control via InvokeScript.
I can call my function once without any problems. But when I call it a second time, it throws the following error :
Unknown name. (Exception from HRESULT: 0x80020006
(DISP_E_UNKNOWNNAME))
The Code I am using is the following :
this.browser.LoadCompleted += (sender, args) =>
{
this.browser.InvokeScript("WriteFromExternal", new object[] { "firstCall" }); // works
this.browser.InvokeScript("WriteFromExternal", new object[] { "secondCall" }); // throws error
};
The javascript function is :
function WriteFromExternal(message) {
document.write("Message : " + message);
}
I can call C# functions from the page via javascript just fine and invoke from C#, just can't invoke a second time. Regardless of what function I call.
I do not understand why it would fail the second time.
Thank you
Edit :
Did the following test (javascript) :
function pageLoaded() {
window.external.tick();
window.external.tick();
window.external.tick();
}
window.onload = pageLoaded;
function WriteFromExternal(message) {
document.write("Message : " + message);
}
And this is the C# side :
private int i = 0;
public void tick()
{
invoke("WriteFromExternal", new object[] { "ticked"+ i++ });
}
public static void invoke(string method, object[] parameters)
{
mainInterface.browser.InvokeScript(method, parameters);
}
And still throws the same error (after the first call), this suggests that it does not matter from where it is called, invoking the function from C# will throw this error if done more than once.
I assume you did the same as me and put your scripts in the body. For some reason when you call document.write from wpf it completely overwrites the document. If instead of using document.write you append a child it works fine. So change your JavaScript function to be:
window.WriteFromExternal = function (message) {
var d = document.createElement("div")
d.innerHTML= "Message : " + message;
document.body.appendChild(d);
}
// call from c#
WriteFromExternal("test")
It's been a while since I did something similar, but from what I remember your code looks correct. However, I do remember using a slightly different pattern in my project. Instead of delegating back to a JS method on the page I would make my ScriptingHost methods return values
EX:
C#:
public string tick()
{
return "some stuff";
}
var msg = window.external.tick();
document.write(msg);
If you have more complex objects than simple strings you can serialize them to JSON and parse them into an object on the JS side.
var jsonObj = JSON.parse(window.external.someMethod());
Not sure if you have the luxury of being able to change your method signatures in your scripting object, but it's at least an alternative approach.
Also, in your current implementation, have you tried to do something other than document.write? Do you get the same error if you display an alert box?

Compiling C# at Runtime

I want to implement C# as the scripting language in my game.
My problem is, that my script will not compile if I want to use classes defined in the game core (exe).
The script looks like this:
using System;
using ConsoleApplication1;
class Script
{
private static void Call()
{
Console.WriteLine("called");
}
public static void Init()
{
Console.WriteLine("Script");
Call();
GameObject myO; // THIS IS WHAT I WANT TO GET WORKED,
//IF THIS IS COMMENTED OUT, IT COMPILES FINE, GAMEOBJECT
// IS DEFINED IN THE "ConsoleApplication1" NAMESPACE.
}
}
The script is compiled like in the MDX sample:
static void Main(string[] args)
{
CodeDomProvider provider = new CSharpCodeProvider();
CompilerParameters cp = new CompilerParameters();
//cp.CompilerOptions = "/target:library";
cp.GenerateExecutable = false;
cp.GenerateInMemory = true;
cp.IncludeDebugInformation = false;
cp.ReferencedAssemblies.Add("ConsoleApplication2.exe");
cp.ReferencedAssemblies.Add("System.dll");
CompilerResults cr = provider.CompileAssemblyFromFile(cp, "script.cs");
if (!cr.Errors.HasErrors)
{
Console.WriteLine("Success");
cr.CompiledAssembly.GetType("Script").GetMethod("Init").Invoke(null, null);
}
Console.ReadLine();
}
Is there any way to call functions or create objects defined in the "ConsoleApplication1" namespace via the script?
This is a daily programming problem. It's not working and you think it should be working. So break it down. Instead of working on a big problem, work on a smaller problem. Just tackle trying to compile the script outside of your program. Once you get that working, then you can try to compile it as a script from inside your program, knowing that you've got the basic problem of references and compiler issues sorted out.
Something like:
csc /reference:ConsoleApplication1.exe script.cs
From the looks of it, it might be as simple as changing the reference from ConsoleApplication2.exe to ConsoleApplication1.exe.

Categories