mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 04:31:13 +00:00
[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:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user