Skip to content

Latest commit

 

History

History
306 lines (236 loc) · 11.7 KB

cli.md

File metadata and controls

306 lines (236 loc) · 11.7 KB

CLI Guide

azure-functions-openapi ships with a CLI entry point for generating OpenAPI output from decorated handlers.

Install

pip install azure-functions-openapi

Then verify:

azure-functions-openapi --help

Command overview

Current command set:

  • generate: build OpenAPI spec in JSON or YAML

generate command

Basic usage

azure-functions-openapi generate

By default this prints JSON to stdout using:

  • title: API
  • version: 1.0.0
  • OpenAPI version: 3.1.0

Common examples

Generate JSON to stdout:

azure-functions-openapi generate --title "Todo API" --version "1.2.0"

Generate YAML to stdout:

azure-functions-openapi generate --format yaml --title "Todo API"

Write JSON to file:

azure-functions-openapi generate --output openapi.json --format json

Write YAML to file:

azure-functions-openapi generate --output openapi.yaml --format yaml

File output is written atomically, so a failed serialization or write leaves an existing specification unchanged. --fail-on-warnings exits before file output and therefore neither creates nor modifies the requested artifact.

Generate OpenAPI 3.1 output:

azure-functions-openapi generate --openapi-version 3.1 --output openapi-3.1.json

Import and scan your function app so decorator routes use their real bindings:

azure-functions-openapi generate --app function_app:app --title "Todo API"

Infer each operation's security requirement from its Azure Functions auth_level binding:

azure-functions-openapi generate \
  --app function_app:app \
  --infer-auth-level \
  --output openapi.json

--infer-auth-level is opt-in and requires the scan-capable --app module:variable form. ANONYMOUS routes remain public, while FUNCTION and ADMIN routes use the AzureFunctionKey x-functions-key scheme. Explicit operation security= metadata keeps precedence.

Pretty-print JSON output:

azure-functions-openapi generate --app function_app:app --pretty --output openapi.json

Endpoint-metadata discovery (module:variable)

@openapi-decorated routes register themselves when the module is imported, but an innermost @openapi (below @app.route) runs before its route binding exists. Therefore --app module:variable is required to recover the real route. It is also required for producers that register only through the shared endpoint-metadata namespace — @validate_http (azure-functions-validation), azure-functions-langgraph, and others — because their metadata is attached to the live FunctionApp object and discovered by scanning it.

To include those endpoints, pass an explicit module:variable so the CLI can resolve the FunctionApp instance and run discovery on it:

# Imports function_app, resolves `app`, then discovers BOTH decorator-
# registered and endpoint-metadata routes into a single spec.
azure-functions-openapi generate --app function_app:app --output openapi.json

!!! warning "--app executes module-level code" The CLI imports every --app module target, so all module-level code runs, not only decorators. Use only trusted modules. Import-time effects can include creating network clients or connections, environment-variable validation that raises, file writes, global initialization, and telemetry or logging side effects.

There is no sandbox: decorator registration and binding discovery require
executing normal Python imports, and the CLI does not isolate or restrict
them. Keep module-level code free of destructive side effects and provide
build-time-safe defaults, for example by guarding external initialization
with an environment flag when generating a spec in CI.

Discovery semantics:

  • --app function_app (module only): decorators fire, but binding discovery is not run. An innermost @openapi therefore emits an unresolved-route warning and uses the historical function-name fallback. This warning remains non-fatal with --strict, because the fallback can be Azure's correct default route. The CLI never guesses the FunctionApp variable name.
  • --app function_app:app (module + variable): the named FunctionApp attribute is resolved and scanned, merging endpoint-metadata routes into the same registry as the decorator routes.
  • Discovery is one-shot per run and uses the idempotent FunctionBuilder.build() path. The CLI never calls FunctionApp.get_functions(), which is non-idempotent on all supported SDK versions (1.21–1.25): it accumulates into functions_bindings and raises on a second call.
  • Blueprints (raw or registered via register_functions / register_blueprint) are enumerated through the same path — no special casing is required.
  • Re-running discovery on the same app is safe: the registry merges by function id, so no duplicate paths or operations are produced.

Options reference

Option Alias Values Default Description
--app - module or module:var - Import the module; use module:var to scan route bindings (required for innermost @openapi) and endpoint metadata
--title - any string API OpenAPI info.title
--version - any string 1.0.0 OpenAPI info.version
--description - any string library default OpenAPI info.description (Markdown / CommonMark supported)
--output -o file path stdout Write generated content to file
--format -f json, yaml json Output serialization format
--openapi-version - 3.0, 3.1, 3.2 3.1 OpenAPI schema version
--pretty -p flag false Pretty-print JSON output (adds indentation); no effect on YAML
--route-prefix - any string (or "") /api HTTP route prefix from host.json extensions.http.routePrefix. See Route Prefix.
--fail-on-empty-paths - flag false Exit with code 1 if the generated spec has no paths
--strict - flag false Fail on any malformed registry entry instead of skipping it. Recommended for CI where a missing path should break the build
--fail-on-warnings - flag false Exit with code 2 if the generator emits any structured warnings (version skew, namespace fallback, or spec-validation issues; the advisory unresolved-route warning never fails). Use in CI to stop a wrong-but-plausible spec from being published
--infer-auth-level - flag false Infer operation security from scanned Azure Functions auth_level bindings. Requires --app module:variable; explicit operation security wins
--isolate-app - flag false Compatibility flag requiring --app module:variable; selecting a variable now always uses a fresh, app-scoped registry, even without this flag

Exit codes

Code Meaning
0 Success
1 Runtime or generation error (including --fail-on-empty-paths with no paths, or an --isolate-app that cannot be honored)
2 Either invalid CLI arguments (argparse parse error) or structured warnings were emitted while --fail-on-warnings is set

Validate generated output

Use a validator in local checks and CI:

pip install openapi-spec-validator
openapi-spec-validator openapi.json

For YAML:

openapi-spec-validator openapi.yaml

CI example

Two different things can be checked in CI, and they are not the same recipe:

  • Basic usage (above) has no --app target. It is useful for trying the tool, but with no application imported the registry is empty: generate succeeds with paths: {}, and a schema validator accepts that empty document too. Neither command on its own proves anything about your actual API.
  • Application validation (below) imports your real FunctionApp, fails the build on generation diagnostics, and fails the build if no paths were discovered — the recipe to use for CI that is meant to catch regressions in your documented API surface.

When passing --app in CI, first review the --app import-execution warning: import-time side effects (network clients, environment validation that raises, and so on) must be build-time-safe, guarded behind an environment flag if necessary.

name: OpenAPI Validation

on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Install tools and application dependencies
        # Generating with --app imports your application module, so its own
        # dependencies (azure-functions, any requirements.txt, etc.) must
        # already be importable before the next step runs.
        run: |
          pip install azure-functions-openapi openapi-spec-validator
          pip install -r requirements.txt
      - name: Generate spec
        # --app function_app:app: substitute your actual module path and
        # FunctionApp variable name. Run from the directory function_app is
        # importable from (or set PYTHONPATH), matching normal Python import
        # resolution -- the CLI does not search for it.
        #
        # --strict --fail-on-warnings surface generation diagnostics (a
        # malformed registry entry, version skew, a namespace fallback, a
        # spec-validation issue) as a build failure. This is a different
        # concern from the next step: these flags check how the spec was
        # generated, not whether the resulting document is schema-valid.
        #
        # --fail-on-empty-paths is the guard this recipe depends on: without
        # it, a forgotten --app target (or wrong module:variable) silently
        # produces an empty-but-schema-valid document, and the next step
        # (openapi-spec-validator) reports success on it.
        run: |
          azure-functions-openapi generate \
            --app function_app:app \
            --openapi-version 3.1 \
            --strict \
            --fail-on-warnings \
            --fail-on-empty-paths \
            --output openapi.json
      - name: Validate spec
        # Checks that the document is schema-valid OpenAPI -- it does not
        # check that the document describes your application's endpoints;
        # that is what --fail-on-empty-paths above is for.
        run: openapi-spec-validator openapi.json

To deliberately generate (and validate) an empty document — for example, to smoke-test the toolchain itself before any routes exist yet — omit --app and --fail-on-empty-paths, matching the basic usage example.

Troubleshooting

command not found

  • Confirm package installed in active environment
  • Use python -m azure_functions_openapi.cli --help as fallback

Empty paths in output

  • Ensure app handlers are decorated with @openapi

  • Pass --app <module>:<variable> so decorated routes and bindings are reconciled:

    azure-functions-openapi generate --app function_app:app --title "My API"
  • Use module:variable syntax when the FunctionApp instance is not named app:

    azure-functions-openapi generate --app function_app:my_app --title "My API"
  • In CI, add --fail-on-empty-paths so a forgotten --app target or a wrong module:variable fails the build instead of silently producing (and validating) an empty document — see the CI example.

Unsupported version error

  • Use only --openapi-version 3.0, --openapi-version 3.1, or --openapi-version 3.2

Related docs