< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Languages.PyDelegateBuilder
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Languages/PyDelegateBuilder.cs
Tag: Kestrun/Kestrun@9d3a582b2d63930269564a7591aa77ef297cadeb
Line coverage
11%
Covered lines: 7
Uncovered lines: 54
Coverable lines: 61
Total lines: 122
Line coverage: 11.4%
Branch coverage
41%
Covered branches: 5
Total branches: 12
Branch coverage: 41.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Implemented()100%11100%
ConfigurePythonRuntimePath(...)100%210%
.cctor()100%11100%
EnsurePythonEngine()0%2040%
Build(...)62.5%54810.2%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Languages/PyDelegateBuilder.cs

#LineLine coverage
 1using Kestrun.Models;
 2using Python.Runtime;
 3using Serilog.Events;
 4
 5namespace Kestrun.Languages;
 6// ---------------------------------------------------------------------------
 7//  Python delegate builder  –  now takes optional imports / references
 8// ---------------------------------------------------------------------------
 9
 10internal static class PyDelegateBuilder
 11{
 212    public static bool Implemented { get; set; }
 013    public static void ConfigurePythonRuntimePath(string path) => Runtime.PythonDLL = path;
 14    // ---------------------------------------------------------------------------
 15    //  helpers at class level
 16    // ---------------------------------------------------------------------------
 17
 18#if NET9_0_OR_GREATER
 119    private static readonly Lock _pyGate = new();
 20#else
 21    private static readonly object _pyGate = new();
 22#endif
 23    private static bool _pyInit;
 24
 25    private static void EnsurePythonEngine()
 26    {
 027        if (_pyInit)
 28        {
 029            return;
 30        }
 31
 32        lock (_pyGate)
 33        {
 034            if (_pyInit)
 35            {
 036                return;          // double-check
 37            }
 38
 39            // If you need a specific DLL, set Runtime.PythonDLL
 40            // or expose it via the PYTHONNET_PYDLL environment variable.
 41            // Runtime.PythonDLL = @"C:\Python312\python312.dll";
 42
 043            PythonEngine.Initialize();        // load CPython once
 044            _ = PythonEngine.BeginAllowThreads(); // let other threads run
 045            _pyInit = true;
 046        }
 047    }
 48    internal static RequestDelegate Build(string code, Serilog.ILogger logger)
 49    {
 150        if (logger.IsEnabled(LogEventLevel.Debug))
 51        {
 152            logger.Debug("Building Python delegate, script l   ength={Length}", code?.Length);
 53        }
 54
 155        if (string.IsNullOrWhiteSpace(code))
 56        {
 057            throw new ArgumentException("Python script code cannot be empty.", nameof(code));
 58        }
 59
 160        if (!Implemented)
 61        {
 162            throw new NotImplementedException("JavaScript scripting is not yet supported in Kestrun.");
 63        }
 64
 065        EnsurePythonEngine();                 // one-time init
 66
 67        // ---------- compile the script once ----------
 068        using var gil = Py.GIL();           // we are on the caller's thread
 069        using var scope = Py.CreateScope();
 70
 71        /*  Expect the user script to contain:
 72
 73                def handle(ctx, res):
 74                    # ctx -> ASP.NET HttpContext (proxied)
 75                    # res -> KestrunResponse    (proxied)
 76                    ...
 77
 78            Scope.Exec compiles & executes that code once per route.
 79        */
 080        _ = scope.Exec(code);
 081        dynamic pyHandle = scope.Get("handle");
 82
 83        // ---------- return a RequestDelegate ----------
 084        return async context =>
 085        {
 086            try
 087            {
 088                using var _ = Py.GIL();       // enter GIL for *this* request
 089                var krRequest = await KestrunRequest.NewRequest(context);
 090                var krResponse = new KestrunResponse(krRequest);
 091
 092                // Call the Python handler (Python → .NET marshal is automatic)
 093                pyHandle(context, krResponse, context);
 094
 095                // redirect?
 096                if (!string.IsNullOrEmpty(krResponse.RedirectUrl))
 097                {
 098                    context.Response.Redirect(krResponse.RedirectUrl);
 099                    return;                   // finally-block will CompleteAsync
 0100                }
 0101
 0102                // normal response
 0103                await krResponse.ApplyTo(context.Response).ConfigureAwait(false);
 0104            }
 0105            catch (Exception ex)
 0106            {
 0107                // optional logging
 0108                logger.Error($"Python route error: {ex}");
 0109                context.Response.StatusCode = 500;
 0110                context.Response.ContentType = "text/plain; charset=utf-8";
 0111                await context.Response.WriteAsync(
 0112                    "Python script failed while processing the request.").ConfigureAwait(false);
 0113            }
 0114            finally
 0115            {
 0116                // Always flush & close so the client doesn’t hang
 0117                try { await context.Response.CompleteAsync().ConfigureAwait(false); }
 0118                catch (ObjectDisposedException) { /* client disconnected */ }
 0119            }
 0120        };
 0121    }
 122}