Search code examples
c#-6.0python.net

Error Calling Python code from .Net Core 6 web api


I am using PythonNet 3.0.1 in my .Net Core 6C# project. Need is to call a .py file and process a csv and read the results back in C#. Have installed Python 3.11.4 on my machine as well as pandas library.

Now when I call the Api Code, it gives an error whenever I try to call the Api method more than once.

I have added a finally block with the following statement: PythonEngine.Shutdown()

The exception logged on console is:

System.NotSupportedException: BinaryFormatter serialization and deserialization are disabled within this application. See https://aka.ms/binaryformatter for more information.

at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph)

at Python.Runtime.RuntimeData.Stash()

at Python.Runtime.Runtime.Shutdown()

at Python.Runtime.PythonEngine.Shutdown()

How do I fix this error and ensure that the Api can be called more than once?

here is the Code I have in the C# class:

string fileLoc = AppDomain.CurrentDomain.BaseDirectory;
         try
        {
            Runtime.PythonDLL = "python311.dll";
            // Initialize the Python engine
            PythonEngine.Initialize();
            using (Py.GIL())
            {
                dynamic module = Py.Import("PythonSampleIronPython");
                dynamic result = module.calculator.xl2determination(fileLoc);
            }
            return new Audit();
        }
        catch (Exception err)
        {
            _logger.LogError("Unable to evaluate model", err);
            throw new Exception("Unable to evaluate model", err);
        }
        finally{
            PythonEngine.Shutdown();
        }

I did not find much help using Pythonnet. So appreciate any help from people using Pythonnet with C#

Here is the Python Code saved in file "PythonSampleIronPython.py".

class calculator: def add(x, y): return x + y def increment(x): x += 1 return x


Solution

  • I got this to work by intializing the PythonEngine on startup in Program.cs:

    Environment.SetEnvironmentVariable("PYTHONNET_PYDLL", "path to python dll");
    PythonEngine.PythonHome = "Path to Python installation";
    PythonEngine.Initialize();
    PythonEngine.BeginAllowThreads();
    

    Then you can test it by using something like this:

    using (Py.GIL())
    {
        PythonEngine.Exec("print('Hello from Python')")
    }
    

    This solution doesn't require you to call PrintEngine.Shutdown() since it's running for the lifecycle of the application.

    Using the github solution provided in the other answer:

    I created a new class NoopFormatter that inherits from IFormatter:

    using System.Runtime.Serialization;
    
    namespace YourNameSpace;
    
    public class NoopFormatter : IFormatter
    {
        public object Deserialize(Stream s) => throw new NotImplementedException();
    
        public void Serialize(Stream s, object o) { }
    
        public SerializationBinder Binder { get; set; }
        public StreamingContext Context { get; set; }
        public ISurrogateSelector SurrogateSelector { get; set; }
    }
    

    Then in my class where I'm using the Python.Runtime I added this line before PythonEngine.Initialize()

    Python.Runtime.RuntimeData.FormatterType = typeof(NoopFormatter);
    

    This allowed PrintEngine.ShutDown() to execute without throwing the BinaryFormatter serialization exception.

    Note: Depending on the use of threading in your application you may need to include PythonEngine.BeginAllowThreads() after you call PythonEngine.Initialize(); This fixed the issue of it locking up on using (Py.GIL()) when hitting the code a second time.