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
This project was vibe coded using [Claude](https://claude.ai) by Anthropic.
This project was coded with assistance of AI.
## License

224
main.py
View File

@ -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
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):
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)
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/) <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(
["basedpyright", "--project", str(config)],
)
config.unlink()
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:
)
)
try:
result = subprocess.run(
["basedpyright", "--project", str(config)],
)
config.unlink()
finally:
config.unlink(missing_ok=True)
if result.returncode != 0:
failed = True