Files
HappyOnigiri-ComfyUI-Meld/check_scripts/common/api_usage_check.py
T
HappyOnigiri 4d45ea6e25 refactor: rename py/ backend package to meld/ and extract SQL to repository
- Rename py/ directory to meld/ to eliminate PyPI 'py' package shadowing,
  which required conftest.py/path.py workarounds in the test suite
- Remove meld/path.py (pytest stub no longer needed after rename)
- Empty meld/__init__.py (removed redundant 'from . import path as path')
- Replace 87-line conftest.py monkeypatch with minimal Package.setup() shim
- Extract create_schema(cursor) from schema.py as single DDL source of truth,
  called by both init_db() and test helpers
- Add permanent_delete, soft_delete, soft_delete_to_trash, restore_image,
  restore_image_with_rename, and collect_deleted_ancestors to repository.py
  to eliminate duplicated inline SQL across router.py and importer/service.py
- Update all import paths, pyproject.toml, Makefile, CI config, and
  check_scripts to reference meld/ instead of py/
2026-03-30 19:21:28 +09:00

75 lines
2.5 KiB
Python

import ast
import os
from .abstract_check import ASTChecker
class ApiUsageChecker(ASTChecker):
def get_target_files(self) -> list[str]:
# Target specific file: py/image_manager/api.py
target = os.path.join("meld", "image_manager", "api.py")
if os.path.exists(target):
return [target]
return []
def _get_name(self, node: ast.AST) -> str | None:
"""Helper to get the full name of an attribute or name node."""
if isinstance(node, ast.Name):
return node.id
elif isinstance(node, ast.Attribute):
base = self._get_name(node.value)
if base:
return f"{base}.{node.attr}"
return None
def visit_Call(self, node: ast.Call) -> None:
# Detect .get("...")
if isinstance(node.func, ast.Attribute) and node.func.attr == "get" and len(node.args) >= 1:
receiver = self._get_name(node.func.value)
# Filter out known false positives
if receiver:
parts = receiver.split(".")
if any("routes" in part for part in parts):
return
if receiver in ("os.environ", "environ"):
return
if receiver.startswith("request."):
return
if receiver == "db_settings":
return
arg0 = node.args[0]
value = None
if isinstance(arg0, ast.Constant) and isinstance(arg0.value, str):
value = arg0.value
if value is not None:
self.add_error(node.lineno, f"Forbidden .get() with string literal: {value!r}")
self.generic_visit(node)
def visit_Subscript(self, node: ast.Subscript) -> None:
# Detect ["..."]
receiver = self._get_name(node.value)
# Filter out known false positives
if receiver and receiver.startswith("request."):
return
index_node = node.slice
# Handle Python versions < 3.9 where slice is wrapped in ast.Index
if isinstance(index_node, ast.Index):
index_node_val = index_node.value # type: ignore[attr-defined]
else:
index_node_val = index_node
value = None
if isinstance(index_node_val, ast.Constant) and isinstance(index_node_val.value, str):
value = index_node_val.value
if value is not None:
self.add_error(node.lineno, f"Forbidden subscript access with string literal: {value!r}")
self.generic_visit(node)