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:
parent
80d5311e33
commit
9e4bcdd283
@ -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
|
||||
|
||||
|
||||
238
main.py
238
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/) <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:
|
||||
)
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user