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) <noreply@anthropic.com>
This commit is contained in:
Joseph HENRY 2026-03-31 16:16:34 +02:00
parent 80d5311e33
commit 9e4bcdd283
2 changed files with 129 additions and 111 deletions

View File

@ -131,7 +131,7 @@ uv run poe check
## Disclaimer ## Disclaimer
This project was vibe coded using [Claude](https://claude.ai) by Anthropic. This project was coded with assistance of AI.
## License ## License

224
main.py
View File

@ -11,7 +11,7 @@ import sys
import tomllib import tomllib
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import TypeAlias from typing import cast
from blender_downloader import get_blender_executable from blender_downloader import get_blender_executable
from generate_stubs import write_stubs from generate_stubs import write_stubs
@ -22,80 +22,41 @@ INTROSPECT_SCRIPT = SCRIPT_DIR / "introspect.py"
OVERRIDES_DIR = SCRIPT_DIR / "overrides" OVERRIDES_DIR = SCRIPT_DIR / "overrides"
TomlValue: TypeAlias = str | int | bool | list["TomlValue"] | dict[str, "TomlValue"] def _toml_quote(value: str) -> str:
TomlDict: TypeAlias = dict[str, "TomlValue"] """Quote and escape a TOML string value."""
return json.dumps(value)
def _serialize_toml_value(value: TomlValue) -> str: def _toml_string_array(values: list[str]) -> str:
"""Serialize a single TOML value.""" """Serialize a list of strings as a TOML array."""
if isinstance(value, bool): return "[" + ", ".join(_toml_quote(v) for v in values) + "]"
return "true" if value else "false"
if isinstance(value, str):
return f'"{value}"' def _as_object_list(value: object) -> list[object]:
if isinstance(value, int): """Return value as list[object] when possible, otherwise an empty list."""
return str(value)
if isinstance(value, list): if isinstance(value, list):
if value and isinstance(value[0], dict): return cast(list[object], value)
items: list[str] = [] return []
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)
def _serialize_toml(data: TomlDict, prefix: str = "") -> str: def _as_object_dict(value: object) -> dict[object, object]:
"""Serialize a dict to TOML format.""" """Return value as dict[object, object] when possible, otherwise an empty dict."""
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): if isinstance(value, dict):
has_values = any(not isinstance(v, dict) for v in value.values()) return cast(dict[object, object], value)
has_tables = any(isinstance(v, dict) for v in value.values()) return {}
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 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.""" """Load [project] metadata from this project's pyproject.toml."""
with (SCRIPT_DIR / "pyproject.toml").open("rb") as f: 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 return data
@ -108,46 +69,91 @@ def build_generated_pyproject(
"""Build a pyproject.toml for the generated stubs, inheriting metadata from the project.""" """Build a pyproject.toml for the generated stubs, inheriting metadata from the project."""
meta = load_project_metadata() meta = load_project_metadata()
classifiers_raw = meta.get("classifiers", []) classifiers: list[str] = []
classifiers: list[TomlValue] = [] for classifier_item in _as_object_list(meta.get("classifiers", [])):
if isinstance(classifiers_raw, list): if isinstance(classifier_item, str):
classifiers = [c for c in classifiers_raw if isinstance(c, str)] classifiers.append(classifier_item)
classifiers.append("Typing :: Stubs Only") classifiers.append("Typing :: Stubs Only")
project: TomlDict = { keywords: list[str] = []
"name": str(meta.get("name", "blender-python-stubs")), for keyword_item in _as_object_list(meta.get("keywords", [])):
"version": package_version, if isinstance(keyword_item, str):
"description": f"Type stubs for Blender {blender_version} Python API", keywords.append(keyword_item)
"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,
}
urls = meta.get("urls") authors: list[dict[str, str]] = []
if isinstance(urls, dict): for raw_author in _as_object_list(meta.get("authors", [])):
project["urls"] = urls 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 = { urls_dict: dict[str, str] = {}
"requires": ["hatchling"], for raw_key, raw_value in _as_object_dict(meta.get("urls")).items():
"build-backend": "hatchling.build", if isinstance(raw_key, str) and isinstance(raw_value, str):
} urls_dict[raw_key] = raw_value
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}
generated: TomlDict = { license_value = meta.get("license", "GPL-2.0-or-later")
"build-system": build_system, if isinstance(license_value, dict):
"project": project, license_dict = cast(dict[object, object], license_value)
"tool": tool, 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 = """\ README_TEMPLATE = """\
@ -185,9 +191,19 @@ obj.location.x = 1.0
bpy.data.objects.new("Cube", bpy.data.meshes.new("Mesh")) 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). 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/) <img src="https://upload.wikimedia.org/wikipedia/commons/0/0c/Blender_logo_no_text.svg" alt="blender" width="20"/>
""" """
@ -383,11 +399,12 @@ def typecheck_stubs(versions: list[str] | None = None) -> None:
) )
) )
try:
result = subprocess.run( result = subprocess.run(
["basedpyright", "--project", str(config)], ["basedpyright", "--project", str(config)],
) )
finally:
config.unlink() config.unlink(missing_ok=True)
if result.returncode != 0: if result.returncode != 0:
failed = True failed = True
@ -459,11 +476,12 @@ def conformance_check(versions: list[str] | None = None) -> None:
) )
) )
try:
result = subprocess.run( result = subprocess.run(
["basedpyright", "--project", str(config)], ["basedpyright", "--project", str(config)],
) )
finally:
config.unlink() config.unlink(missing_ok=True)
if result.returncode != 0: if result.returncode != 0:
failed = True failed = True