PythonSharp 1.0.3
dotnet add package PythonSharp --version 1.0.3
NuGet\Install-Package PythonSharp -Version 1.0.3
<PackageReference Include="PythonSharp" Version="1.0.3" />
<PackageVersion Include="PythonSharp" Version="1.0.3" />
<PackageReference Include="PythonSharp" />
paket add PythonSharp --version 1.0.3
#r "nuget: PythonSharp, 1.0.3"
#:package PythonSharp@1.0.3
#addin nuget:?package=PythonSharp&version=1.0.3
#tool nuget:?package=PythonSharp&version=1.0.3
<img src="https://github.com/jchristn/PythonSharp/blob/main/Assets/logo.png?raw=true" data-canonical-src="https://github.com/jchristn/PythonSharp/blob/main/Assets/logo.png?raw=true" width="128" height="128" />
PythonSharp
PythonSharp is a simple library for invoking Python scripts from C# and retrieving their return value.
Why not just use pythonnet? Pythonnet is a fantastic, useful, and well-written library, managed and maintained by an excellent community. However, it is challenged in being able to support multiple concurrent virtual environments. Further, pythonnet provides near seamless integration with the .NET CLR, whereas PythonSharp is a wrapper library that invokes Python scripts using the shell. To summarize, you should use pythonnet in most cases, but consider PythonSharp when you need to support multiple virtual environments concurrently.
Help, Feedback, Contribute
If you have any issues or feedback, please file an issue here in Github. We'd love to have you help by contributing code for new features, optimization to the existing codebase, ideas for future releases, or fixes!
New in v1.0.x
- Initial release
Example Project
Refer to the Test project for exercising the library. Create one instance of the PythonRunner class for each separate invocation.
using PythonSharp;
// Create the environment object.
PythonEnvironment env = new PythonEnvironment();
// Set the base executable and pip command.
env.PythonExecutable = "C:\\Program Files\\Python312\\python.exe";
env.PipCommand = "C:\\Program Files\\Python312\\Scripts\\pip.exe";
// Set up the virtual environment, or skip. If it does not exist, PythonSharp will create it.
env.VirtualEnvironmentPath = "./myscripts/venv/";
// Define path to the directory containing your script.
env.ScriptPath = "./myscripts/";
// Specify the requirements.txt file, or skip.
env.RequirementsFile = "./myscripts/requirements.txt";
// Create the script runner.
PythonRunner runner = new PythonRunner(env);
PythonResult result = runner.RunScript("script.py", "{ \"hello\": \"world\" }");
// {
// "GUID": "[GUID]", // Unique identifier for the execution
// "Success": true, // Success or failure
// "CommandResult": 0, // Result of the shell command to run the script
// "ConsoleOutput": "[Console messages here]", // Console output encountered during execution
// "ErrorOutput": "[Error messages here]", // Error output encountered during execution
// "Data": "{ \"hello\": \"world\" }"; // Data returned from the script
// "Exception": null // Exception, if any
// }
How Does it Work?
PythonSharp uses the Shelli library to invoke shell commands. Under the hood, shell commands are issued when necessary to create virtual environments, install requirements (inside or outside of a virtual environment), enter and exit virtual environments, and to invoke scripts.
User scripts should be implemented with a def process(req): method. The input req is a dictionary, and the output of this method must also be a dictionary.
Internally, a Guid is assigned for each invocation of a script. This serves as an identifier for temporary files that are created during execution:
- A wrapper script, which encapsulates the user script
- An output file, containing a JSON representation of the dictionary response from the user script
The wrapper script is lightweight, and only serves to deserialize the input data supplied to runner.RunScript into a dictionary, and, to serialize the output data from the user function into a JSON object. The user script is imported into the wrapper script, and saved as a temporary file called [Guid].py. This file is deleted after execution.
The result value is stored in a file called [Guid].resp on the filesystem, and PythonSharp will read this file to obtain the results and then delete it.
The wrapper script, with a simple def process(req): integrated within, looks as follows when calling runner.RunScript("script.py", "{ \"hello\": \"world\" }");:
import json
# Begin user code
def process(req):
print("Hello from user code!")
return req
# End user code
_req_obj = json.loads('{ "hello": "world" }')
_resp_obj = process(_req_obj)
_out_file = open('9f786ef-7690-4024-8ec0-e5d699246527.resp', 'w')
_out_file.write(json.dumps(_resp_obj))
User Script Requirements
- User scripts must implement a method
def process(req):as the entrypoint for their code - The input variable
reqto this method is a dictionary (deserialized from the JSON string supplied when invokingrunner.RunScript) - The
def process(req):method should return a dictionary
Testing
PythonSharp uses Touchstone to define its test cases once and execute them through multiple runners. The test projects live under src:
- Test.Shared — the single source of truth. All test suites (
PythonSharpTestSuites) are defined here as runner-agnostic Touchstone descriptors coveringPythonEnvironment,PythonResult,LoggingSettings, andPythonRunner, in both positive and negative cases. - Test.Automated — a console host that executes the shared suites through the Touchstone CLI runner.
- Test.Xunit — executes the shared suites via
dotnet testusing the Touchstone xUnit adapter. - Test.Nunit — executes the shared suites via
dotnet testusing the Touchstone NUnit adapter.
The Test project remains an interactive console for manually exercising the library against your own scripts.
Integration suites that require a real Python interpreter are automatically skipped when one cannot be located. Python and pip are auto-detected from PATH; override them with the PYTHONSHARP_TEST_PYTHON and PYTHONSHARP_TEST_PIP environment variables (or --python / --pip on the console runner).
# Run the console (CLI) runner
dotnet run --project src/Test.Automated
# Run through xUnit or NUnit
dotnet test src/Test.Xunit
dotnet test src/Test.Nunit
Slower or environment-sensitive suites are opt-in: pass --run-venv-execution to execute a script from within a freshly-created virtual environment, or --run-requirements to exercise pip-based requirements installation (the corresponding PYTHONSHARP_TEST_RUN_VENV_EXECUTION and PYTHONSHARP_TEST_RUN_REQUIREMENTS environment variables enable them for the dotnet test runners).
Version History
Refer to CHANGELOG.md for version history.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Shelli (>= 2.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Dependency maintenance: updated Shelli to 2.0.1 and removed the unused RestWrapper dependency.