< 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@0d738bf294e6281b936d031e1979d928007495ff
Line coverage
12%
Covered lines: 8
Uncovered lines: 55
Coverable lines: 63
Total lines: 124
Line coverage: 12.6%
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 08/26/2025 - 14:53:17 Line coverage: 9.8% (6/61) Branch coverage: 25% (3/12) Total lines: 122 Tag: Kestrun/Kestrun@78d1e497d8ba989d121b57aa39aa3c6b22de743109/06/2025 - 18:30:33 Line coverage: 11.4% (7/61) Branch coverage: 41.6% (5/12) Total lines: 122 Tag: Kestrun/Kestrun@aeddbedb8a96e9137aac94c2d5edd011b57ac87111/14/2025 - 12:29:34 Line coverage: 12.9% (8/62) Branch coverage: 41.6% (5/12) Total lines: 124 Tag: Kestrun/Kestrun@5e12b09a6838e68e704cd3dc975331b9e680a62612/15/2025 - 02:23:46 Line coverage: 12.6% (8/63) Branch coverage: 41.6% (5/12) Total lines: 124 Tag: Kestrun/Kestrun@7a3839f4de2254e22daae81ab8dc7cb2f40c8330 08/26/2025 - 14:53:17 Line coverage: 9.8% (6/61) Branch coverage: 25% (3/12) Total lines: 122 Tag: Kestrun/Kestrun@78d1e497d8ba989d121b57aa39aa3c6b22de743109/06/2025 - 18:30:33 Line coverage: 11.4% (7/61) Branch coverage: 41.6% (5/12) Total lines: 122 Tag: Kestrun/Kestrun@aeddbedb8a96e9137aac94c2d5edd011b57ac87111/14/2025 - 12:29:34 Line coverage: 12.9% (8/62) Branch coverage: 41.6% (5/12) Total lines: 124 Tag: Kestrun/Kestrun@5e12b09a6838e68e704cd3dc975331b9e680a62612/15/2025 - 02:23:46 Line coverage: 12.6% (8/63) Branch coverage: 41.6% (5/12) Total lines: 124 Tag: Kestrun/Kestrun@7a3839f4de2254e22daae81ab8dc7cb2f40c8330

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%52812%

File(s)

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

#LineLine coverage
 1using Kestrun.Hosting;
 2using Kestrun.Models;
 3using Python.Runtime;
 4using Serilog.Events;
 5
 6namespace Kestrun.Languages;
 7// ---------------------------------------------------------------------------
 8//  Python delegate builder  –  now takes optional imports / references
 9// ---------------------------------------------------------------------------
 10
 11internal static class PyDelegateBuilder
 12{
 213    public static bool Implemented { get; set; }
 014    public static void ConfigurePythonRuntimePath(string path) => Python.Runtime.Runtime.PythonDLL = path;
 15    // ---------------------------------------------------------------------------
 16    //  helpers at class level
 17    // ---------------------------------------------------------------------------
 18
 19#if NET9_0_OR_GREATER
 20    private static readonly Lock _pyGate = new();
 21#else
 122    private static readonly object _pyGate = new();
 23#endif
 24    private static bool _pyInit;
 25
 26    private static void EnsurePythonEngine()
 27    {
 028        if (_pyInit)
 29        {
 030            return;
 31        }
 32
 033        lock (_pyGate)
 34        {
 035            if (_pyInit)
 36            {
 037                return;          // double-check
 38            }
 39
 40            // If you need a specific DLL, set Runtime.PythonDLL
 41            // or expose it via the PYTHONNET_PYDLL environment variable.
 42            // Runtime.PythonDLL = @"C:\Python312\python312.dll";
 43
 044            PythonEngine.Initialize();        // load CPython once
 045            _ = PythonEngine.BeginAllowThreads(); // let other threads run
 046            _pyInit = true;
 047        }
 048    }
 49    internal static RequestDelegate Build(KestrunHost host, string code)
 50    {
 151        var logger = host.Logger;
 152        if (logger.IsEnabled(LogEventLevel.Debug))
 53        {
 154            logger.Debug("Building Python delegate, script l   ength={Length}", code?.Length);
 55        }
 56
 157        if (string.IsNullOrWhiteSpace(code))
 58        {
 059            throw new ArgumentException("Python script code cannot be empty.", nameof(code));
 60        }
 61
 162        if (!Implemented)
 63        {
 164            throw new NotImplementedException("Python scripting is not yet supported in Kestrun.");
 65        }
 66
 067        EnsurePythonEngine();                 // one-time init
 68
 69        // ---------- compile the script once ----------
 070        using var gil = Py.GIL();           // we are on the caller's thread
 071        using var scope = Py.CreateScope();
 72
 73        /*  Expect the user script to contain:
 74
 75                def handle(ctx, res):
 76                    # ctx -> ASP.NET HttpContext (proxied)
 77                    # res -> KestrunResponse    (proxied)
 78                    ...
 79
 80            Scope.Exec compiles & executes that code once per route.
 81        */
 082        _ = scope.Exec(code);
 083        dynamic pyHandle = scope.Get("handle");
 84
 85        // ---------- return a RequestDelegate ----------
 086        return async context =>
 087        {
 088            try
 089            {
 090                using var _ = Py.GIL();       // enter GIL for *this* request
 091                var krRequest = await KestrunRequest.NewRequest(context);
 092                var krResponse = new KestrunResponse(krRequest);
 093
 094                // Call the Python handler (Python → .NET marshal is automatic)
 095                pyHandle(context, krResponse, context);
 096
 097                // redirect?
 098                if (!string.IsNullOrEmpty(krResponse.RedirectUrl))
 099                {
 0100                    context.Response.Redirect(krResponse.RedirectUrl);
 0101                    return;                   // finally-block will CompleteAsync
 0102                }
 0103
 0104                // normal response
 0105                await krResponse.ApplyTo(context.Response).ConfigureAwait(false);
 0106            }
 0107            catch (Exception ex)
 0108            {
 0109                // optional logging
 0110                logger.Error($"Python route error: {ex}");
 0111                context.Response.StatusCode = 500;
 0112                context.Response.ContentType = "text/plain; charset=utf-8";
 0113                await context.Response.WriteAsync(
 0114                    "Python script failed while processing the request.").ConfigureAwait(false);
 0115            }
 0116            finally
 0117            {
 0118                // Always flush & close so the client doesn’t hang
 0119                try { await context.Response.CompleteAsync().ConfigureAwait(false); }
 0120                catch (ObjectDisposedException) { /* client disconnected */ }
 0121            }
 0122        };
 0123    }
 124}