Search code examples
c#python.net

Python.net issue working with modules on Ubuntu 22.04 but works on Windows 11


I have a very simple C# app using Python.net 3.0.3 on net8.0 that is attempting to call out to a Python module in the bin/Debug/net8.0 directory, which returns the product of two numbers. On Windows it works, on Ubuntu I receive a Python.Runtime.PythonException with message No module named 'script'. The files on both machines are the same.

Code:

namespace PythonLoader
{
    using System;
    using System.Runtime.InteropServices;
    using GetSomeInput;
    using Python.Runtime;

    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                string script = Inputty.GetString("Module name: ", "script", false);

                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    Runtime.PythonDLL = @"C:\Python312\python312.dll";
                }
                else
                {
                    // see https://stackoverflow.com/questions/20582270/distribution-independent-libpython-path
                    Runtime.PythonDLL = @"/usr/lib/x86_64-linux-gnu/libpython3.10.so";
                }

                PythonEngine.Initialize();
                PythonEngine.BeginAllowThreads();

                Console.WriteLine("Using Python DLL      : " + Runtime.PythonDLL);
                Console.WriteLine("Using Python version  : " + PythonEngine.Version);
                Console.WriteLine("Build information     : " + PythonEngine.BuildInfo);
                Console.WriteLine("Compiler              : " + PythonEngine.Compiler);
                Console.WriteLine("Max supported version : " + PythonEngine.MaxSupportedVersion);
                Console.WriteLine("Min supported version : " + PythonEngine.MinSupportedVersion);
                Console.WriteLine("Platform              : " + PythonEngine.Platform);
                Console.WriteLine("Program name          : " + PythonEngine.ProgramName);
                Console.WriteLine("Python home           : " + PythonEngine.PythonHome);
                Console.WriteLine("Python path           : " + PythonEngine.PythonPath);

                using (Py.GIL())
                {
                    dynamic app = Py.Import(script);
                    Console.WriteLine(app.multiply(2, 4));
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    }
}

File script.py

def multiply(a, b):
  return a * b

File __init__.py

from script import multiply

Both of these files are copied to the output directory (e.g. bin/Debug/net8.0.

Works on Windows:

C:\Code\Misc\PythonLoader\PythonLoader\bin\Debug\net8.0>pythonloader
Module name:  [script]
Using Python DLL      : C:\Python312\python312.dll
Using Python version  : 3.12.2 (tags/v3.12.2:6abddd9, Feb  6 2024, 21:26:36) [MSC v.1937 64 bit (AMD64)]
Build information     : tags/v3.12.2:6abddd9, Feb  6 2024, 21:26:36
Compiler              : [MSC v.1937 64 bit (AMD64)]
Max supported version : 3.12.2147483647.2147483647
Min supported version : 3.7
Platform              : win32
Program name          : python
Python home           :
Python path           : C:\Python312\python312.zip;C:\Python312\DLLs;C:\Python312\Lib;C:\Code\Misc\PythonLoader\PythonLoader\bin\Debug\net8.0
8

Fails on Ubuntu:

joel@ubuntu:~/Code/pythontest/bin/Debug/net8.0$ ./pythontest
Python filename:  [script.py] 
Using Python DLL      : /usr/lib/x86_64-linux-gnu/libpython3.10.so
Using Python version  : 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]
Build information     : main, Nov 20 2023, 15:14:05
Compiler              : [GCC 11.4.0]
Max supported version : 3.12.2147483647.2147483647
Min supported version : 3.7
Platform              : linux
Program name          : python3
Python home           : 
Python path           : /usr/lib/python310.zip:/usr/lib/python3.10:/usr/lib/python3.10/lib-dynload:/home/joel/Code/pythontest/bin/Debug/net8.0
Python.Runtime.PythonException: No module named 'script'
   at Python.Runtime.PythonException.ThrowLastAsClrException()
   at Python.Runtime.NewReferenceExtensions.BorrowOrThrow(NewReference& reference)
   at Python.Runtime.PyModule.Import(String name)
   at Python.Runtime.Py.Import(String name)
   at PythonLoader.Program.Main(String[] args) in /home/joel/Code/pythontest/Program.cs:line 59

Directory contents on Windows:

C:\Code\Misc\PythonLoader\PythonLoader\bin\Debug\net8.0>dir
 Volume in drive C is OS
 Volume Serial Number is 541C-D54E

 Directory of C:\Code\Misc\PythonLoader\PythonLoader\bin\Debug\net8.0

05/24/2024  08:34 PM    <DIR>          .
05/24/2024  09:07 AM    <DIR>          ..
01/11/2024  11:37 AM           322,048 GetSomeInput.dll
10/11/2023  12:15 AM           431,616 Python.Runtime.dll
05/24/2024  02:32 PM             7,378 PythonLoader.deps.json
05/24/2024  08:34 PM             6,656 PythonLoader.dll
05/24/2024  08:34 PM           142,848 PythonLoader.exe
05/24/2024  08:34 PM            10,932 PythonLoader.pdb
05/24/2024  09:17 AM               268 PythonLoader.runtimeconfig.json
05/24/2024  09:16 AM                37 script.py
05/24/2024  03:40 PM                29 __init__.py
05/24/2024  09:21 AM    <DIR>          __pycache__
               9 File(s)        921,812 bytes
               3 Dir(s)  48,029,245,440 bytes free

Directory contents on Ubuntu:

joel@ubuntu:~/Code/pythontest/bin/Debug/net8.0$ ls -la
total 860
drwxrwxr-x 2 joel joel   4096 May 24 20:33 .
drwxrwxr-x 3 joel joel   4096 May 24 15:41 ..
-rwxrw-r-- 1 joel joel 322048 Jan 11 10:32 GetSomeInput.dll
-rw-rw-r-- 1 joel joel     31 May 24 15:52 __init__.py
-rwxrw-r-- 1 joel joel 431616 Oct 11  2023 Python.Runtime.dll
-rwxr-xr-x 1 joel joel  72448 May 24 20:33 pythontest
-rw-rw-r-- 1 joel joel   7171 May 24 15:41 pythontest.deps.json
-rw-rw-r-- 1 joel joel   6656 May 24 20:33 pythontest.dll
-rw-rw-r-- 1 joel joel  10964 May 24 20:33 pythontest.pdb
-rw-rw-r-- 1 joel joel    257 May 24 15:41 pythontest.runtimeconfig.json
-rw-rw-r-- 1 joel joel     38 May 24 20:31 script.py

Any help would be much appreciated!

Edit: added more logging for verbosity and clarity.


Solution

  • The problem seems to be caused by the fact that sys.path (search paths used when loading modules) does not contain the current working directory when ran on Linux (maybe an issue of the library).

    This can be determined by running a simple script:

    import sys
    
    print('; '.join(sys.path))
    

    So we need to add current dir to sys.path to let our script load the modules in current directory. To do so we just need to add an empty string (as stated in docs, an empty string means the current directory) in this array.

    To apply this we need to slightly edit the code and create a python scope.

    using (Py.GIL())
    using (var scope = Py.CreateScope()) // Scope creation
    {
        scope.Import("sys");
        // Line below is used just to check sys.path value. It can be removed
        Console.WriteLine(scope.Eval("'; '.join(sys.path)"));
    
        scope.Exec(@"if '' not in sys.path: sys.path.insert(0, '')"); // Add current directory to sys.path if not already there
        dynamic app = scope.Import(script); // Now import should work
        Console.WriteLine(app.multiply(2, 4));
    }