[BugFix] openbb-cli: Make Codegen Names That Start With a Digit or Are Keywords Valid (#7800)

* [BugFix] `openbb-cli`: Prefix Codegen Names That Start With a Digit or Are Keywords

_safe_segment returned names such as "24hr" or "class" unchanged, so the
router generator emitted `async def 24hr(` and unimportable module names.
Prefix them with an underscore (Fixes #7799).

* Suffix keyword segments with an underscore instead of prefixing them
This commit is contained in:
Younes Beriane
2026-10-24 06:07:36 +00:00
committed by GitHub
parent 78020ee4b8
commit 86e27341b0
2 changed files with 52 additions and 3 deletions

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
import keyword
import re
from dataclasses import dataclass, field
from typing import TYPE_CHECKING
@@ -56,9 +57,18 @@ class GeneratedRouters:
def _safe_segment(name: str) -> str:
"""Lower-case + replace non-alphanumerics with underscores."""
cleaned = re.sub(r"[^0-9a-zA-Z]+", "_", name).strip("_")
return cleaned.lower() or "ns"
"""Lower-case + replace non-alphanumerics with underscores.
A result that starts with a digit gets a leading underscore and a Python
keyword gets a trailing one (as pydantic_gen does for field names), so it
stays a valid function and module identifier.
"""
cleaned = (re.sub(r"[^0-9a-zA-Z]+", "_", name).strip("_") or "ns").lower()
if cleaned[0].isdigit():
cleaned = f"_{cleaned}"
elif keyword.iskeyword(cleaned):
cleaned = f"{cleaned}_"
return cleaned
def _module_name_for(path: str) -> str:

View File

@@ -407,3 +407,42 @@ def test_generate_routers_command_that_is_also_a_namespace_lives_in_its_own_rout
assert "async def count(" in by_module["alerts_active"].source
for r in out.routers:
ast.parse(r.source)
def test_safe_segment_prefixes_digits_and_suffixes_keywords():
assert rg._safe_segment("24hr") == "_24hr"
assert rg._safe_segment("class") == "class_"
assert rg._safe_segment("Return") == "return_"
def test_generate_routers_digit_and_keyword_commands_are_valid_python():
tree = nt.build_namespace_tree(
{
"ticker.24hr": {"providers": ["fmp"], "description": "24h stats"},
"ticker.class": {"providers": ["fmp"], "description": "Class"},
"24hr": {"providers": ["fmp"], "description": "Root 24h"},
}
)
fetchers = {
key: _Fetcher(
module_name=key.replace(".", "_"),
fetcher_class="F",
model_name="M",
)
for key in ("ticker.24hr", "ticker.class", "24hr")
}
out = rg.generate_routers(
tree,
package_name="openbb_codegen",
provider_name="fmp",
fetchers_by_command=fetchers,
post_commands_by_command={},
)
assert out.routers
for router in out.routers:
ast.parse(router.source)
for block in out.root_commands:
ast.parse(block)
generated = "".join(r.source for r in out.routers) + "".join(out.root_commands)
assert "async def _24hr(" in generated
assert "async def class_(" in generated