From 992f5bf860aa0bbde9a4db2ef0a3a28c13d2ee76 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Wed, 12 Aug 2026 12:16:54 -0400 Subject: [PATCH 1/3] Restore Python builtin Exception in AlgorithmImports from System import * shadows the builtin Exception with System.Exception, so except Exception clauses in Python algorithms silently miss Python exceptions (TypeError, KeyError, ...). Rebind the builtin after the star imports and keep the CLR type reachable as System.Exception. --- Common/AlgorithmImports.py | 7 ++ Tests/Python/AlgorithmImportsTests.cs | 111 ++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 Tests/Python/AlgorithmImportsTests.cs diff --git a/Common/AlgorithmImports.py b/Common/AlgorithmImports.py index 35cda517a1ca..62c31f1cff54 100644 --- a/Common/AlgorithmImports.py +++ b/Common/AlgorithmImports.py @@ -29,6 +29,7 @@ if file.endswith(".dll") and file.startswith("QuantConnect."): AddReference(file.replace(".dll", "")) +import System from System import * from System.Drawing import * @@ -99,5 +100,11 @@ import math import json +# "from System import *" shadows Python's builtin Exception with System.Exception, whose +# except clauses do not catch Python exceptions (TypeError, KeyError, ...). +# Restore the builtin; the CLR type remains available as System.Exception. +import builtins +Exception = builtins.Exception + QCAlgorithmFramework = QCAlgorithm QCAlgorithmFrameworkBridge = QCAlgorithm diff --git a/Tests/Python/AlgorithmImportsTests.cs b/Tests/Python/AlgorithmImportsTests.cs new file mode 100644 index 000000000000..5d431b1e22b3 --- /dev/null +++ b/Tests/Python/AlgorithmImportsTests.cs @@ -0,0 +1,111 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +using System; +using Python.Runtime; +using NUnit.Framework; + +namespace QuantConnect.Tests.Python +{ + [TestFixture] + public class AlgorithmImportsTests + { + private PyObject _module; + + [OneTimeSetUp] + public void Setup() + { + using (Py.GIL()) + { + _module = PyModule.FromString("AlgorithmImportsTests", @" +from AlgorithmImports import * +import builtins +import AlgorithmImports + +def exception_is_the_python_builtin(): + return Exception is builtins.Exception + +def except_clause_catches_python_exceptions(): + try: + raise TypeError('expected') + except Exception: + return True + except BaseException: + return False + +def except_clause_catches_clr_exceptions(action): + try: + action() + except Exception: + return True + except BaseException: + return False + +def shadowed_builtin_names(): + return [name for name, value in vars(AlgorithmImports).items() + if not name.startswith('_') and getattr(builtins, name, value) is not value] +"); + } + } + + [OneTimeTearDown] + public void TearDown() + { + using (Py.GIL()) + { + _module.Dispose(); + } + } + + [Test] + public void ExceptionIsThePythonBuiltinException() + { + using (Py.GIL()) + { + Assert.IsTrue(_module.GetAttr("exception_is_the_python_builtin").Invoke().As()); + } + } + + [Test] + public void ExceptClauseCatchesPythonExceptions() + { + using (Py.GIL()) + { + Assert.IsTrue(_module.GetAttr("except_clause_catches_python_exceptions").Invoke().As()); + } + } + + [Test] + public void ExceptClauseCatchesClrExceptions() + { + using (Py.GIL()) + { + Action action = () => throw new ArgumentException("thrown from C#"); + Assert.IsTrue(_module.GetAttr("except_clause_catches_clr_exceptions").Invoke(action.ToPython()).As()); + } + } + + [Test] + public void StarImportsDoNotShadowPythonBuiltins() + { + using (Py.GIL()) + { + var shadowed = _module.GetAttr("shadowed_builtin_names").Invoke().As(); + Assert.IsEmpty(shadowed, $"Python builtins shadowed by AlgorithmImports: {string.Join(", ", shadowed)}"); + } + } + } +} From aee2c92fcc139dae95a5b15089ac7504fa645936 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Thu, 13 Aug 2026 10:31:46 -0400 Subject: [PATCH 2/3] Restore builtin Exception with a from-import to avoid exporting builtins --- Common/AlgorithmImports.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Common/AlgorithmImports.py b/Common/AlgorithmImports.py index 62c31f1cff54..2ba22ee076dd 100644 --- a/Common/AlgorithmImports.py +++ b/Common/AlgorithmImports.py @@ -103,8 +103,7 @@ # "from System import *" shadows Python's builtin Exception with System.Exception, whose # except clauses do not catch Python exceptions (TypeError, KeyError, ...). # Restore the builtin; the CLR type remains available as System.Exception. -import builtins -Exception = builtins.Exception +from builtins import Exception QCAlgorithmFramework = QCAlgorithm QCAlgorithmFrameworkBridge = QCAlgorithm From d0a40d27374b77075676d2044a6899440a5d2e30 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Thu, 13 Aug 2026 10:43:47 -0400 Subject: [PATCH 3/3] Drop the System module import; users can import it explicitly when needed --- Common/AlgorithmImports.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Common/AlgorithmImports.py b/Common/AlgorithmImports.py index 2ba22ee076dd..6d67d7dfdb9f 100644 --- a/Common/AlgorithmImports.py +++ b/Common/AlgorithmImports.py @@ -29,7 +29,6 @@ if file.endswith(".dll") and file.startswith("QuantConnect."): AddReference(file.replace(".dll", "")) -import System from System import * from System.Drawing import * @@ -102,7 +101,7 @@ # "from System import *" shadows Python's builtin Exception with System.Exception, whose # except clauses do not catch Python exceptions (TypeError, KeyError, ...). -# Restore the builtin; the CLR type remains available as System.Exception. +# Restore the builtin; "import System" gives explicit access to the CLR type if needed. from builtins import Exception QCAlgorithmFramework = QCAlgorithm