bzd-python
How to run, test, build, type-check, format, and manage Python dependencies in the bzd monorepo
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
How to run, test, build, type-check, format, and manage Python dependencies in the bzd monorepo
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
How to write, build, test, and contribute Node.js/JavaScript/Vue code in the bzd monorepo
Core software design principles — ALWAYS load this before planning and before writing any implementation code
How to build, test, run, and maintain Bazel targets in the bzd monorepo — always load this before invoking any ./tools/bazel command
How to read, write, and wire BDL (Bzd Description Language) files and their Bazel rules in the bzd monorepo — load this before touching any .bdl file or bdl_* Bazel rule
How to write, build, test, and contribute C++ code in the bzd monorepo
How to build/test esp32/esp32s3/etc targets in the bzd monorepo.
| name | bzd-python |
| description | How to run, test, build, type-check, format, and manage Python dependencies in the bzd monorepo |
| compatibility | opencode |
rules_python (hermetic interpreter — never invoke system python3 or pip directly)--config=mypy)unittest (no pytest)camelCase for functions/variables (repo-wide convention, not standard Python snake_case)| Path | Purpose |
|---|---|
python/bzd/ | Core bzd_python library module (parsers, utils, HTTP, logging, validation, etc.) |
python/MODULE.bazel | Declares the bzd_python Bazel module |
python/defs.bzl | Public API re-exports for custom Python Bazel rules |
python/requirements.in | Direct pip deps for bzd_python library — edit this to add deps |
python/requirements.txt | Locked pip deps for bzd_python (auto-generated, do NOT edit manually) |
tools/python/requirements.in | Direct pip deps for root workspace apps/tools — edit this to add deps |
tools/python/requirements.txt | Locked pip deps for root workspace (auto-generated, do NOT edit manually) |
tools/python/pyproject.toml | Config for mypy, yapf, ruff (tabs, 120-col limit) |
tools/python/BUILD.bazel | Bazel targets for mypy, ruff, yapf, codespell binaries |
| Hub name | requirements.in | Used in BUILD files via |
|---|---|---|
bzd_python_pip | python/requirements.in | load("@bzd_python_pip//:requirements.bzl", "requirement") |
pip | tools/python/requirements.in | load("@pip//:requirements.bzl", "requirement") |
# Run all python tests in the bzd_python library module
./tools/bazel test @bzd_python//...
# Run a specific test target
./tools/bazel test @bzd_python//bzd/utils/tests:dict
./tools/bazel test @bzd_python//bzd/parser/tests:grammar
# Build a py_binary
./tools/bazel build @bzd_python//bzd/apps/map_analyzer:map_analyzer
# Run a py_binary
./tools/bazel run @bzd_python//bzd/apps/map_analyzer:map_analyzer
# Run all Python tests in the root workspace
./tools/bazel test //apps/...
mypy runs as a Bazel build aspect — not a standalone command.
# Run mypy on the entire bzd_python library module
./tools/bazel build --config=mypy @bzd_python//...
# Run mypy on all root workspace targets
./tools/bazel build --config=mypy //...
# Run mypy on a single target
./tools/bazel build --config=mypy @bzd_python//bzd/utils:run
mypy settings (from tools/python/pyproject.toml):
"mypy-ignore" in its BUILD.bazel entryType hints are mandatory on all function signatures.
# Run sanitizer on changed files (formats + lints, includes ruff for Python)
./tools/bazel run //:sanitizer
# Run on ALL files in the repo
./tools/bazel run //:sanitizer -- --all
# Check-only mode (reports issues, no writes)
./tools/bazel run //:sanitizer -- --check --all
Style rules (from tools/python/pyproject.toml):
To exclude a file or directory from sanitizer checks, place a .sanitizerignore file in that directory.
Create the .py source file(s):
python/bzd/<module>/my_lib.pyapps/<app_name>/my_module.pyCreate or update the BUILD.bazel:
load("@rules_python//python:defs.bzl", "py_library")
py_library(
name = "my_lib",
srcs = ["my_lib.py"],
visibility = ["//visibility:public"],
deps = [
"@bzd_python//bzd/utils:run", # example internal dep
],
)
Import paths follow the directory structure from the workspace root:
python/bzd/utils/my_lib.py → from bzd.utils.my_lib import MyClassapps/my_app/my_module.py → from apps.my_app.my_module import MyClassReference in other BUILD files:
python/ (same module): "//bzd/utils:my_lib"bzd_python: "@bzd_python//bzd/utils:my_lib"apps/ targeting another app: "//apps/my_app:my_module"python/bzd/ library code (hub: bzd_python_pip)Add the package name to python/requirements.in
Regenerate the lockfile:
./tools/bazel run @bzd_python//:requirements.update
Use in BUILD.bazel:
load("@bzd_python_pip//:requirements.bzl", "requirement")
py_library(
name = "my_lib",
srcs = ["my_lib.py"],
deps = [requirement("paramiko")],
)
apps/ or tools/ code (hub: pip)Add the package name to tools/python/requirements.in
Regenerate the lockfile:
./tools/bazel run //tools/python:requirements.update
Use in BUILD.bazel:
load("@pip//:requirements.bzl", "requirement")
py_binary(
name = "my_app",
srcs = ["my_app.py"],
deps = [requirement("pillow")],
)
All tests use Python's built-in unittest. Test file naming convention: <module>_test.py.
# my_feature_test.py
import unittest
class TestMyFeature(unittest.TestCase):
def testSomething(self) -> None:
self.assertEqual(1 + 1, 2)
if __name__ == "__main__":
unittest.main()
# BUILD.bazel
load("@rules_python//python:defs.bzl", "py_test")
py_test(
name = "my_feature",
srcs = ["my_feature_test.py"],
main = "my_feature_test.py",
visibility = ["//visibility:public"],
deps = ["//my_lib:my_feature"],
)
Use args with $(location ...) and data to pass file paths to the test:
py_test(
name = "parser",
srcs = ["parser_test.py"],
args = [
"$(location testdata.json)",
"$(location testdata2.json)",
],
data = [
"testdata.json",
"testdata2.json",
],
deps = [":parser"],
)
In the test file, read paths from sys.argv:
import sys, pathlib, unittest
class TestParser(unittest.TestCase):
dataFile: pathlib.Path
if __name__ == "__main__":
TestParser.dataFile = pathlib.Path(sys.argv.pop(1))
unittest.main()
[py_test(
name = path.replace(".py", ""),
srcs = [path],
deps = [":my_lib"],
) for path in glob(["*_test.py"])]
All custom rules are in python/private/ and exported via python/defs.bzl.
bzd_python_binary_libraryExposes Bazel-built executables as importable Python path constants:
load("@bzd_python//:defs.bzl", "bzd_python_binary_library")
bzd_python_binary_library(
name = "my_binaries",
executables = {"//tools/my_tool": "my_tool"},
)
bzd_python_hermetic_launcherWraps a py_binary to use the hermetic Bazel Python interpreter instead of the system one:
load("@bzd_python//:defs.bzl", "bzd_python_hermetic_launcher")
bzd_python_hermetic_launcher(
name = "my_app.hermetic",
binary = ":my_app",
)
bzd_python_ociBuilds a Docker OCI image from a py_binary (automatically applies the hermetic launcher):
load("@bzd_python//:defs.bzl", "bzd_python_oci")
bzd_python_oci(
name = "image",
binary = ":my_app",
base = "@oci_base_image",
)
# Produces: :image (OCI artifact), :image.load (for `docker load`)
from typing import ...)Args:, Returns:)camelCase for functions and variables (repo-wide convention)PascalCasemyHelper_(), value_)print(): use bzd.logging (from bzd.logging import Logger)importlib: declare all deps in BUILD.bazel#bzd/ import prefix: that pattern applies only to Node.js files# Full quality gate (runs everything: tests, mypy, sanitizer)
./quality_gate.sh
# Static analysis only (mypy aspect on all targets)
./tools/bazel build --config=mypy //...
# Sanitizer check only (ruff + codespell)
./tools/bazel run //:sanitizer -- --check --all
The CI pipeline runs @bzd_python//... in the test stage and --config=mypy in the static analysis stage. Always run the sanitizer before committing.