From 9e4bcdd283b278de99e23b0d2a33962db906391b Mon Sep 17 00:00:00 2001 From: Joseph HENRY Date: Tue, 31 Mar 2026 16:16:34 +0200 Subject: [PATCH] Add disclaimer, license, and footer to generated package README Update disclaimer text in both repo and generated READMEs. Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 2 +- main.py | 238 +++++++++++++++++++++++++++++------------------------- 2 files changed, 129 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index 9c99a95..1a208c6 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ uv run poe check ## Disclaimer -This project was vibe coded using [Claude](https://claude.ai) by Anthropic. +This project was coded with assistance of AI. ## License diff --git a/main.py b/main.py index 9ea65cb..f13074e 100644 --- a/main.py +++ b/main.py @@ -11,7 +11,7 @@ import sys import tomllib from dataclasses import dataclass from pathlib import Path -from typing import TypeAlias +from typing import cast from blender_downloader import get_blender_executable from generate_stubs import write_stubs @@ -22,80 +22,41 @@ INTROSPECT_SCRIPT = SCRIPT_DIR / "introspect.py" OVERRIDES_DIR = SCRIPT_DIR / "overrides" -TomlValue: TypeAlias = str | int | bool | list["TomlValue"] | dict[str, "TomlValue"] -TomlDict: TypeAlias = dict[str, "TomlValue"] +def _toml_quote(value: str) -> str: + """Quote and escape a TOML string value.""" + return json.dumps(value) -def _serialize_toml_value(value: TomlValue) -> str: - """Serialize a single TOML value.""" - if isinstance(value, bool): - return "true" if value else "false" - if isinstance(value, str): - return f'"{value}"' - if isinstance(value, int): - return str(value) +def _toml_string_array(values: list[str]) -> str: + """Serialize a list of strings as a TOML array.""" + return "[" + ", ".join(_toml_quote(v) for v in values) + "]" + + +def _as_object_list(value: object) -> list[object]: + """Return value as list[object] when possible, otherwise an empty list.""" if isinstance(value, list): - if value and isinstance(value[0], dict): - items: list[str] = [] - for item in value: - if isinstance(item, dict): - pairs = ", ".join( - f"{k} = {_serialize_toml_value(v)}" for k, v in item.items() - ) - items.append(f"{{ {pairs} }}") - joined = ",\n ".join(items) - return f"[\n {joined},\n]" - str_items = [_serialize_toml_value(v) for v in value] - if len(str_items) <= 3: - return f"[{', '.join(str_items)}]" - joined = ",\n ".join(str_items) - return f"[\n {joined},\n]" - return str(value) + return cast(list[object], value) + return [] -def _serialize_toml(data: TomlDict, prefix: str = "") -> str: - """Serialize a dict to TOML format.""" - lines: list[str] = [] - tables: list[tuple[str, TomlDict]] = [] - - for key, value in data.items(): - full_key = f"{prefix}.{key}" if prefix else key - if isinstance(value, dict): - has_values = any(not isinstance(v, dict) for v in value.values()) - has_tables = any(isinstance(v, dict) for v in value.values()) - - if has_values: - tables.append((full_key, value)) - elif has_tables: - lines.append(_serialize_toml(value, full_key)) - else: - quoted_key = f'"{key}"' if "." in key else key - lines.append(f"{quoted_key} = {_serialize_toml_value(value)}") - - result_parts: list[str] = [] - if lines: - if prefix: - result_parts.append(f"[{prefix}]") - result_parts.extend(lines) - - for table_key, table_value in tables: - result_parts.append("") - result_parts.append(f"[{table_key}]") - for k, v in table_value.items(): - if isinstance(v, dict): - result_parts.append("") - result_parts.append(_serialize_toml(v, f"{table_key}.{k}")) - else: - quoted_k = f'"{k}"' if "." in k else k - result_parts.append(f"{quoted_k} = {_serialize_toml_value(v)}") - - return "\n".join(result_parts) +def _as_object_dict(value: object) -> dict[object, object]: + """Return value as dict[object, object] when possible, otherwise an empty dict.""" + if isinstance(value, dict): + return cast(dict[object, object], value) + return {} -def load_project_metadata() -> TomlDict: +def _as_str(value: object, default: str) -> str: + """Return a string value with fallback.""" + if isinstance(value, str): + return value + return default + + +def load_project_metadata() -> dict[str, object]: """Load [project] metadata from this project's pyproject.toml.""" with (SCRIPT_DIR / "pyproject.toml").open("rb") as f: - data: TomlDict = tomllib.load(f)["project"] + data: dict[str, object] = tomllib.load(f)["project"] return data @@ -108,46 +69,91 @@ def build_generated_pyproject( """Build a pyproject.toml for the generated stubs, inheriting metadata from the project.""" meta = load_project_metadata() - classifiers_raw = meta.get("classifiers", []) - classifiers: list[TomlValue] = [] - if isinstance(classifiers_raw, list): - classifiers = [c for c in classifiers_raw if isinstance(c, str)] + classifiers: list[str] = [] + for classifier_item in _as_object_list(meta.get("classifiers", [])): + if isinstance(classifier_item, str): + classifiers.append(classifier_item) classifiers.append("Typing :: Stubs Only") - project: TomlDict = { - "name": str(meta.get("name", "blender-python-stubs")), - "version": package_version, - "description": f"Type stubs for Blender {blender_version} Python API", - "readme": "README.md", - "requires-python": f">={python_version}", - "license": str(meta.get("license", "GPL-2.0-or-later")), - "keywords": meta.get("keywords", []), - "authors": meta.get("authors", []), - "classifiers": classifiers, - } + keywords: list[str] = [] + for keyword_item in _as_object_list(meta.get("keywords", [])): + if isinstance(keyword_item, str): + keywords.append(keyword_item) - urls = meta.get("urls") - if isinstance(urls, dict): - project["urls"] = urls + authors: list[dict[str, str]] = [] + for raw_author in _as_object_list(meta.get("authors", [])): + author = _as_object_dict(raw_author) + author_item: dict[str, str] = {} + name = author.get("name") + email = author.get("email") + if isinstance(name, str): + author_item["name"] = name + if isinstance(email, str): + author_item["email"] = email + if author_item: + authors.append(author_item) - build_system: TomlDict = { - "requires": ["hatchling"], - "build-backend": "hatchling.build", - } - pkg_list: list[TomlValue] = [p for p in sorted(packages)] - wheel: TomlDict = {"packages": pkg_list} - targets: TomlDict = {"wheel": wheel} - build: TomlDict = {"targets": targets} - hatch: TomlDict = {"build": build} - tool: TomlDict = {"hatch": hatch} + urls_dict: dict[str, str] = {} + for raw_key, raw_value in _as_object_dict(meta.get("urls")).items(): + if isinstance(raw_key, str) and isinstance(raw_value, str): + urls_dict[raw_key] = raw_value - generated: TomlDict = { - "build-system": build_system, - "project": project, - "tool": tool, - } + license_value = meta.get("license", "GPL-2.0-or-later") + if isinstance(license_value, dict): + license_dict = cast(dict[object, object], license_value) + license_text = _as_str(license_dict.get("text"), "GPL-2.0-or-later") + else: + license_text = str(license_value) - return _serialize_toml(generated) + project_name = _as_str( + meta.get("name", "blender-python-stubs"), "blender-python-stubs" + ) + + lines: list[str] = [ + "[build-system]", + 'requires = ["hatchling"]', + 'build-backend = "hatchling.build"', + "", + "[project]", + f"name = {_toml_quote(project_name)}", + f"version = {_toml_quote(package_version)}", + f'description = {_toml_quote(f"Type stubs for Blender {blender_version} Python API")}', + 'readme = "README.md"', + f'requires-python = {_toml_quote(f">={python_version}")}', + f"license = {_toml_quote(license_text)}", + f"keywords = {_toml_string_array(keywords)}", + ] + + if authors: + lines.append("authors = [") + for author in authors: + fields: list[str] = [] + if "name" in author: + fields.append(f'name = {_toml_quote(author["name"])}') + if "email" in author: + fields.append(f'email = {_toml_quote(author["email"])}') + lines.append(f" {{ {', '.join(fields)} }},") + lines.append("]") + else: + lines.append("authors = []") + + lines.append(f"classifiers = {_toml_string_array(classifiers)}") + + if urls_dict: + lines.append("") + lines.append("[project.urls]") + for key in sorted(urls_dict): + lines.append(f"{_toml_quote(key)} = {_toml_quote(urls_dict[key])}") + + lines.extend( + [ + "", + "[tool.hatch.build.targets.wheel]", + f"packages = {_toml_string_array(sorted(packages))}", + ] + ) + + return "\n".join(lines) README_TEMPLATE = """\ @@ -185,9 +191,19 @@ obj.location.x = 1.0 bpy.data.objects.new("Cube", bpy.data.meshes.new("Mesh")) ``` +## Disclaimer + +This project was coded with assistance of AI. + +## License + +[MIT](LICENSE) + --- Generated by [blender-python-stubs](https://git.autourdeminuit.com/autour_de_minuit/blender-python-stubs). + +Made with ❤️ at [Autour de Minuit (ADV)](https://blog.autourdeminuit.com/) blender """ @@ -383,11 +399,12 @@ def typecheck_stubs(versions: list[str] | None = None) -> None: ) ) - result = subprocess.run( - ["basedpyright", "--project", str(config)], - ) - - config.unlink() + try: + result = subprocess.run( + ["basedpyright", "--project", str(config)], + ) + finally: + config.unlink(missing_ok=True) if result.returncode != 0: failed = True @@ -459,11 +476,12 @@ def conformance_check(versions: list[str] | None = None) -> None: ) ) - result = subprocess.run( - ["basedpyright", "--project", str(config)], - ) - - config.unlink() + try: + result = subprocess.run( + ["basedpyright", "--project", str(config)], + ) + finally: + config.unlink(missing_ok=True) if result.returncode != 0: failed = True