{"record":{"id":"6e429d3c19022d2c","repo":"apache/superset","slug":"package-name-cannot-start-with-python-keyword-fi","errorCode":null,"errorMessage":"Package name cannot start with Python keyword '{first_part}'","messagePattern":"Package name cannot start with Python keyword '(.+?)'","errorType":"exception","errorClass":"ExtensionNameError","httpStatus":null,"severity":"error","filePath":"superset-extensions-cli/src/superset_extensions_cli/utils.py","lineNumber":199,"sourceCode":"    \"\"\"Convert display name directly to kebab-case (e.g., 'Hello World' -> 'hello-world').\"\"\"\n    normalized = _normalize_for_identifiers(name)\n    return _normalized_to_kebab(normalized)\n\n\ndef validate_python_package_name(name: str) -> None:\n    \"\"\"\n    Validate Python package name (snake_case format).\n\n    Raises:\n        ExtensionNameError: If name is invalid\n    \"\"\"\n    # Check if it starts with a number (invalid for Python identifiers)\n    if name[0].isdigit():\n        raise ExtensionNameError(f\"Package name '{name}' cannot start with a number\")\n\n    # Check if the first part (before any underscore) is a Python keyword\n    if (first_part := name.split(\"_\")[0]) in PYTHON_KEYWORDS:\n        raise ExtensionNameError(\n            f\"Package name cannot start with Python keyword '{first_part}'\"\n        )\n\n    # Check if it's a valid Python identifier\n    if not name.replace(\"_\", \"a\").isalnum():\n        raise ExtensionNameError(f\"'{name}' is not a valid Python package name\")\n\n\ndef validate_npm_package_name(name: str) -> None:\n    \"\"\"\n    Validate npm package name (kebab-case format).\n\n    Raises:\n        ExtensionNameError: If name is invalid\n    \"\"\"\n    if name.lower() in NPM_RESERVED:\n        raise ExtensionNameError(f\"'{name}' is a reserved npm package name\")\n","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset-extensions-cli/src/superset_extensions_cli/utils.py#L181-L217","documentation":"validate_python_package_name rejects snake_case package names whose first underscore-separated token is a Python keyword ('class_x', 'import_utils'), since the generated module would produce a keyword identifier and fail to import. The offending keyword is named in the message.","triggerScenarios":"Scaffolding an extension whose normalized name begins with a keyword: 'class-dashboard', 'import-export', 'for-finance', 'None-player', etc.","commonSituations":"Names mirroring Python/domain vocabulary ('import', 'class', 'type', 'none', 'true'); automation passing arbitrary strings; kebab-case names whose first token normalizes to a keyword.","solutions":["Rename so the first token is not a keyword: 'classif-dashboard' instead of 'class-dashboard'.","Move the keyword later in the name: 'data-import' rather than 'import-data'.","Check the keyword list (keyword.kwlist) when naming extensions programmatically."],"exampleFix":"# before\n$ superset-extension new import-export\n# after\n$ superset-extension new data-import","handlingStrategy":"validation","validationCode":"import keyword\n\ndef avoids_leading_keyword(name: str) -> bool:\n    return name.split('_')[0] not in keyword.kwlist","typeGuard":null,"tryCatchPattern":"try:\n    validate_python_package_name(name)\nexcept ExtensionNameError as e:\n    print(f'Rename the extension: {e}')\n    sys.exit(2)","preventionTips":["Avoid Python keywords (class, import, for, none, true, ...) as the first name token.","When automating, screen candidates against keyword.kwlist first."],"tags":["cli","validation","scaffolding","python-keywords"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}