CSnakes

repository·main·Indexed 22 days ago

https://github.com/tonybaloney/csnakes

A .NET Source Generator and Runtime for embedding Python code and libraries directly into C# applications. It leverages the Python C-API for high-performance, in-process execution and uses Python type hinting to automatically generate C# method signatures. Supports .NET 8 and 9, Python 3.9 through 3.13 (including free-threading mode), and is compatible with C-extensions like NumPy across Windows, macOS, and Linux.

Tokens
34.5K
Snippets
111
Records
151
Agent score
77%

What's inside CSnakes

  1. Overview of CSnakes Advanced Features

    main

    CSnakes provides several advanced capabilities for complex Python integration scenarios beyond standard source-generated bindings. These include:

    • Large Integer Handling: Using C# System.Numerics.BigInteger to interface with Python's arbitrary-precision integers.
    • Free-Threading Mode: Leveraging Python 3.13+ to remove Global Interpreter Lock (GIL) limitations for true parallelism.
    • Manual Python Integration: Using the CSnakes runtime API directly to bypass the source generator for maximum control.
    • Hot Reload Support: Modifying Python code during development without application restarts.
    • Signal Handler Configuration: Managing how Python signal handlers interact with .NET hosting frameworks.
    • Native AOT Support: Compiling applications with Native AOT for faster startup and self-contained deployment.
  2. Overview of CSnakes

    main
    CSnakes is a .NET Source Generator and Runtime designed to embed Python code and libraries directly into a C#.NET solution. Unlike microservice-based approaches, CSnakes operates at a low level using Python's C-API, allowing for high-performance, in-process invocation of Python code without the overhead of REST or HTTP. It leverages Python type hinting to automatically generate clean, readable C# method signatures with native .NET types.
  3. Available CSnakes Sample Projects

    main

    CSnakes provides several sample projects covering different .NET application types and integration scenarios:

    • Basic Console Application: Simplest implementation showing basic Python function calls, string manipulation, math, and error handling.
    • AOT Console Application: Demonstrates compatibility with Native AOT compilation. Requires source generator usage and <PublishAot>true</PublishAot> in the .csproj.
    • Web Application: An ASP.NET Core app integrating Python for data processing, REST API endpoints, and dependency injection.
    • F# Sample: Demonstrates usage within an F# functional programming context with type-safe integration.
    • Aspire Distributed Application: Shows orchestration of multiple microservices using .NET Aspire with shared Python modules.
  4. Discover Python files for Source Generation

    main

    CSnakes provides two ways to discover .py and .pyi files in your .NET project for generating C# bindings:

    1. Automatic Discovery (Default): CSnakes recursively finds all Python files in your project. This is controlled by the EnableDefaultPythonItems property, which defaults to true.
    2. Manual Discovery: If you need granular control, you can disable automatic discovery and explicitly specify files using the AdditionalFiles item group. When using manual discovery, you must include the SourceItemType="Python" attribute.
    <!-- Automatic (Default) -->
    <PropertyGroup>
      <EnableDefaultPythonItems>true</EnableDefaultPythonItems>
    </PropertyGroup>
    
    <!-- Manual -->
    <PropertyGroup>
      <EnableDefaultPythonItems>false</EnableDefaultPythonItems>
    </PropertyGroup>
    <ItemGroup>
      <AdditionalFiles Include="math_utils.py" SourceItemType="Python">
        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      </AdditionalFiles>
    </ItemGroup>
  5. Requirements for Python type annotations in CSnakes

    main

    You do not need to type annotate all your Python code. However, to leverage the Source Generator for automatic C# code generation (including signatures and type conversions), you must provide type hints for the specific functions you intend to call from C#.

    Only Python files explicitly marked in the C# project as CSharp Analyzer Additional Files will be processed by the Source Generator.

  6. Use the IPyBuffer interface for Python Buffer Protocol and NumPy arrays

    main

    CSnakes supports the Python Buffer Protocol for bytes and bytearray types, as well as NumPy ndarrays. To access these in C#, use the CSnakes.Runtime.Python.IPyBuffer interface. This interface allows you to read or write raw data from Python objects using .NET Span types.

    In Python, you should use the Buffer type hint (from collections.abc in Python 3.12+ or typing_extensions for older versions) to indicate a function returns an object supporting the Buffer Protocol.

    Warning: Span is writable. Modifying the buffer in C# will reflect changes in the Python object. To prevent accidental modification, use the As[T]ReadOnly methods to obtain a read-only view.

    try:
        from collections.abc import Buffer
    except ImportError:
        from typing_extensions import Buffer
    
    import numpy as np
    
    def example_array() -> Buffer:
        return np.array([True, False, True, False, False], dtype=np.bool_)
  7. Work with complex types (Lists, Dictionaries, Tuples)

    main

    CSnakes maps common Python collection types to usable C# types:

    • Lists: Python list[T] maps to C# arrays or collections (e.g., new[] { 1, 2, 3 }).
    • Dictionaries: Python dict can be accessed in C# using indexers (e.g., user["name"]).
    • Tuples: Python tuple maps to C# ValueTuple. Elements are accessed via .Item1, .Item2, etc., or via C# tuple deconstruction.
    • Optional Parameters: Python default arguments are preserved in the generated C# method signatures.
    // Tuples
    var (firstName, lastName) = mathModule.ParseName("John Doe");
    
    // Dictionaries
    var user = mathModule.CreateUser("Alice", 30);
    Console.WriteLine($"Name: {user["name"]}");
    
    // Optional Parameters
    var greeting = mathModule.Greet("Alice"); // Uses default prefix/suffix
  8. Understand the CSnakes Type System and PyObject

    main

    CSnakes integrates Python and C# type systems using automatic and manual conversion.

    • PyObject: Represents any Python object in C#. It allows for attribute access, method invocation, and function calls. It manages memory via a SafeHandle, meaning Python objects are automatically reference-counted and released by the .NET Garbage Collector when the PyObject is disposed.
    • Source Generation: The CSnakes source generator automatically creates marshalling calls between C# and Python based on the Python function's type signatures.
    • Manual Marshalling: You can manually convert types using:
      • PyObject.From<T>(T object): Converts a C# type to a PyObject.
      • T PyObject.As<T>(PyObject x): Converts a PyObject to a C# type.
    // Manual conversion examples
    PyObject pyObj = PyObject.From(42); // C# long to PyObject
    long csharpValue = pyObj.As<long>(); // PyObject to C# long
  9. Manage PyObject memory and lifecycle

    main

    Because PyObject represents a Python object, it implements IDisposable. You must ensure objects are disposed of to prevent memory leaks in the Python runtime.

    Best Practice: Always use the using statement to ensure automatic disposal.

    // Recommended: automatic disposal
    using PyObject pyObj = PyObject.From("Hello, World!");
    
    // Manual disposal (if not using 'using')
    PyObject pyObj2 = PyObject.From(42);
    try {
        // ...
    } finally {
        pyObj2.Dispose();
    }
    
    // Cloning
    PyObject cloned = original.Clone();
  10. Handling Python classes and custom types in C#

    main

    CSnakes does not support source generation for custom types, including dataclasses and namedtuple instances. When a Python function returns a class instance, it is returned to C# as a PyObject rather than a specific CLR type.

    You can interact with these objects using PyObject methods like GetAttr and Call. While ToString() provides the string representation, you cannot directly cast the PyObject to a specific .NET class type.

    Example Python function:

    def create_person(name: str, age: int) -> Person:
        return Person(name, age)

    Resulting C# signature and usage:

    // The return type is PyObject
    var person = module.CreatePerson("Alice", 42);
    
    // Use GetAttr to access properties
    var name = person.GetAttr("name");
    var age = person.GetAttr("age");
    var person = module.CreatePerson("Alice", 42);
    var name = person.GetAttr("name");
    var age = person.GetAttr("age");
  11. Call Python functions from C#

    main

    CSnakes uses source generation to create C# wrapper methods for your Python functions based on their type annotations.

    Key behaviors:

    • Naming: Python function names are converted from snake_case to PascalCase (e.g., hello_world becomes HelloWorld).
    • Type Mapping: Python types are automatically mapped to C# equivalents (e.g., list[float] becomes IReadOnlyList<double>).
    • Module Access: Modules are accessed via generated methods on the IPythonEnvironment instance (e.g., env.Demo() for demo.py).
    // Python: def hello_world(name: str) -> str:
    // C#: 
    var greeting = module.HelloWorld("Alice");
    
    // Python: def add_numbers(a: int, b: int) -> int:
    // C#:
    var sum = module.AddNumbers(5, 3);
    
    // Python: def calculate_average(numbers: list[float]) -> float:
    // C#:
    var numbers = new[] { 1.5, 2.5, 3.5, 4.5 };
    var average = module.CalculateAverage(numbers);
  12. When to use manual Python integration

    main

    While the CSnakes Source Generator automates boilerplate for invoking Python functions and converting types, you may choose manual integration for:

    • Fine-grained control: Managing Python object handling directly.
    • Dynamic code: Working with Python code that cannot be statically analyzed.
    • Custom conversions: Implementing type conversions not supported by the Source Generator.
    • Debugging: Investigating complex interop scenarios.
    • Custom abstractions: Building your own layers over the CSnakes runtime.

    Important Limitations:

    • Native AOT: Dynamic conversion is not supported in Native AOT environments.
    • Performance: Manual integration is typically slower than using the Source Generator.