Skip to content

Instantly share code, notes, and snippets.

@odashi
Last active November 17, 2022 06:27
Show Gist options
  • Save odashi/e2b83150b520f0501cfbf6d7c6c21554 to your computer and use it in GitHub Desktop.
Save odashi/e2b83150b520f0501cfbf6d7c6c21554 to your computer and use it in GitHub Desktop.
Bazel rule to generate Python structs from OpenAPI documents

Bazel rule to generate Python structs from OpenAPI documents

  • Created: 2022-11-09
  • Author: Yusuke Oda (@odashi)
  • License: MIT

Usage:

  1. You must initialize rules_python, and prepare a local package index as "pip_deps". In requirements.txt for the local package index, you need to add the following packages:

    • pydantic
    • datamodel-code-generator
  2. Put files in this gist somewhere on your repository (//path/to).

  3. prepare a directory (//path/to/another) with the following files:

    • BUILD
    • openapi.yaml (your schema)
    • server.py (your server implementation)
  4. In your BUILD, define rules for the schema and the server:

    load("//path/to/openapi.bzl", "py_openapi_schema")
    
    # Rule to generate schema.py
    py_openapi_schema(
        name = "schema",
        schema = "openapi.yaml",
    )
    
    # Rule to generate the server binary.
    py_binary(
        name = "server",
        srcs = ["server.py"],
        deps = [":schema"],
    )
  5. schema.py will be generated onto the runfiles directory. In server.py, you can import schema.py as a local module:

    from path.to.another import schema
load("@pip_deps//:requirements.bzl", "requirement")
py_binary(
name = "datamodel_codegen",
srcs = ["datamodel_codegen_main.py"],
main = "datamodel_codegen_main.py",
visibility = ["//visibility:public"],
deps = [
requirement("datamodel-code-generator"),
],
)
from datamodel_code_generator import __main__ # type: ignore
if __name__ == "__main__":
__main__.main()
load("@pip_deps//:requirements.bzl", "requirement")
def _py_openapi_schema_impl(ctx):
# Generates schema stub.
out = ctx.actions.declare_file(ctx.label.name + ".py")
args = ctx.actions.args()
args.add("--input", ctx.file.schema)
args.add("--input-file-type", "openapi")
args.add("--output", out)
ctx.actions.run(
outputs = [out],
inputs = [ctx.file.schema],
executable = ctx.executable._codegen,
arguments = [args],
)
sources = depset(
direct = [out],
transitive = [
target[PyInfo].transitive_sources
for target in ctx.attr._deps
if PyInfo in target
],
)
runfiles = ctx.runfiles(files = [out])
runfiles = runfiles.merge_all([
target[DefaultInfo].default_runfiles
for target in ctx.attr._deps
])
imports = depset(
transitive = [
target[PyInfo].imports
for target in ctx.attr._deps
if PyInfo in target
],
)
return [
DefaultInfo(
files = sources,
default_runfiles = runfiles,
),
PyInfo(
transitive_sources = sources,
uses_shared_libraries = False,
imports = imports,
has_py2_only_sources = False,
has_py3_only_sources = True,
),
]
py_openapi_schema = rule(
implementation = _py_openapi_schema_impl,
attrs = {
"schema": attr.label(
allow_single_file = [".yaml"],
mandatory = True,
),
"_codegen": attr.label(
default = "//path/to:datamodel_codegen",
executable = True,
cfg = "target",
),
# Generated code implicitly depend on pydantic.
"_deps": attr.label_list(
default = [requirement("pydantic")],
providers = [PyInfo],
),
},
provides = [DefaultInfo, PyInfo],
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment