From 8af5cdf6a41e7a44f6a0116d59af9621b42c64e1 Mon Sep 17 00:00:00 2001 From: Amadeus Date: Tue, 6 Oct 2026 15:01:23 -0300 Subject: [PATCH 1/2] bug: generate a valid class name when the project name starts with a digit convert_to_class_name only removed non-alphanumeric characters, so a project called '2024 Momentum' got 'class 2024Momentum', which does not compile. Prefix the name with an underscore when it is empty or starts with a digit. Co-Authored-By: Claude Opus 5.5 --- lean/components/util/name_extraction.py | 8 +++++++- tests/commands/test_create_project.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lean/components/util/name_extraction.py b/lean/components/util/name_extraction.py index 4c058e86d..2ccfee31c 100644 --- a/lean/components/util/name_extraction.py +++ b/lean/components/util/name_extraction.py @@ -31,4 +31,10 @@ def convert_to_class_name(file_path: Path): :return: returns a valid class name """ from re import sub - return sub(f"[^a-zA-Z0-9]", "", "".join(map(_capitalize, file_path.name.split(" ")))) + class_name = sub(f"[^a-zA-Z0-9]", "", "".join(map(_capitalize, file_path.name.split(" ")))) + + # An identifier cannot be empty or start with a digit + if class_name == "" or class_name[0].isdigit(): + class_name = "_" + class_name + + return class_name diff --git a/tests/commands/test_create_project.py b/tests/commands/test_create_project.py index bc27c93fa..a1c42f8af 100644 --- a/tests/commands/test_create_project.py +++ b/tests/commands/test_create_project.py @@ -157,6 +157,22 @@ def test_create_project_preserves_capitals_in_class_name() -> None: assert "class MyFIRSTProject(QCAlgorithm)" in file.read() +@pytest.mark.parametrize("name,class_name", [("2024 momentum", "_2024Momentum"), + ("123", "_123")]) +def test_create_project_creates_valid_class_name_when_name_starts_with_digit(name: str, class_name: str) -> None: + create_fake_lean_cli_directory() + + result = CliRunner().invoke(lean, ["create-project", "--language", "python", name]) + + assert result.exit_code == 0 + + with open(Path.cwd() / name / "main.py") as file: + content = file.read() + + assert f"class {class_name}(QCAlgorithm)" in content + compile(content, "main.py", "exec") + + def test_create_project_aborts_when_path_invalid() -> None: create_fake_lean_cli_directory() From e57b0efac37b8517f2796020714be6c7feb97497 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Mon, 12 Oct 2026 14:24:38 -0400 Subject: [PATCH 2/2] bug: generate a valid class name when the project name maps to a keyword Project names such as `none`, `true` or `_class` produced `class None`, `class True` or `class class`, which are syntax errors in Python and C#. Prefix the class name with an underscore when it starts with a lowercase letter or is a Python keyword. --- lean/components/util/name_extraction.py | 6 ++++-- tests/commands/test_create_project.py | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lean/components/util/name_extraction.py b/lean/components/util/name_extraction.py index 2ccfee31c..67dd2c186 100644 --- a/lean/components/util/name_extraction.py +++ b/lean/components/util/name_extraction.py @@ -31,10 +31,12 @@ def convert_to_class_name(file_path: Path): :return: returns a valid class name """ from re import sub + from keyword import iskeyword class_name = sub(f"[^a-zA-Z0-9]", "", "".join(map(_capitalize, file_path.name.split(" ")))) - # An identifier cannot be empty or start with a digit - if class_name == "" or class_name[0].isdigit(): + # An identifier cannot be empty, start with a digit or be a keyword. + # Keywords are lowercase in Python and C# except for None/True/False, so a lowercase start is enough to flag them + if class_name == "" or not class_name[0].isupper() or iskeyword(class_name): class_name = "_" + class_name return class_name diff --git a/tests/commands/test_create_project.py b/tests/commands/test_create_project.py index a1c42f8af..9d6ea5d5e 100644 --- a/tests/commands/test_create_project.py +++ b/tests/commands/test_create_project.py @@ -158,8 +158,12 @@ def test_create_project_preserves_capitals_in_class_name() -> None: @pytest.mark.parametrize("name,class_name", [("2024 momentum", "_2024Momentum"), - ("123", "_123")]) -def test_create_project_creates_valid_class_name_when_name_starts_with_digit(name: str, class_name: str) -> None: + ("123", "_123"), + ("none", "_None"), + ("true", "_True"), + ("_class", "_class"), + ("__import__", "_import")]) +def test_create_project_creates_valid_class_name_when_name_is_not_an_identifier(name: str, class_name: str) -> None: create_fake_lean_cli_directory() result = CliRunner().invoke(lean, ["create-project", "--language", "python", name])