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
|
## 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
224
main.py
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user