Skip to content

Repository files navigation

fastRAML

Parse, validate, inspect, and convert RAML 1.0 in Python

CI RAML TCK Licence Status: beta


Documentation: docs/README.md

Source Code: deiteris/FastRAML


fastRAML reads RAML 1.0 — the API description language — and gives you the effective API, everything resolved and merged, rather than the text of one file. Types inherit, traits add parameters, resource types add methods, and !include pulls in other documents; fastRAML resolves all of it and hands you a typed model plus tools for validation, navigation, linting, and conversion.

The key features are:

  • Effective model: resolves !include, uses, type expressions and inheritance, then applies traits, resource types and security schemes. An Overlay or Extension is merged into its master API first. Declaration order and source locations remain available on the typed Python model.
  • Type and value validation: implements RAML's built-in shapes and facets, custom facets, examples, defaults, annotations, recursive types, and JSON Schema external types. A shape can also validate an application value directly.
  • Tested coverage with explicit boundaries: all 984 evaluated fixtures in the RAML Test Compliance Kit (TCK) produce their expected outcome, Overlays and Extensions included. Applying several Overlays or Extensions to one master at once is deferred, and XML Schema external types are not supported; the coverage matrix records the details.
  • Structured diagnostics: errors carry source locations and trace chains, including failures reached through includes and merged templates. Independent failures accumulate rather than stop the parse, wherever the parser can continue safely.
  • Model navigation: list, show, refs and deps inspect named entities and the routes between them. graph emits RDF, Graphviz or JSON, while tree emits an addressed containment view.
  • Analysis and linting: run custom SPARQL or one of 9 named graph queries. lint checks the effective model against 85 built-in rules, with opt-in security (OWASP and OAuth), HTTP semantics (RFC 9110), problem details (RFC 9457), I-JSON (RFC 7493) and style rulesets, per-rule explanations and plugins (Linting).
  • Version comparison: compat walks two effective API models in parallel and classifies compatibility impact by whether a value is sent in a request or received in a response (docs/16). It exits non-zero when the policy identifies a breaking change.
  • OpenAPI and JSON Schema output: convert an effective API to a typed OpenAPI 3.0.3 document, or a RAML shape to JSON Schema draft-07. Both conversion APIs report information the target format could not represent.
  • Sample values: sample returns a deterministic value a shape accepts. It uses the shape's own example first, then builds one from its properties' examples, then generates what the facets allow. It never borrows a supertype's example. Every value is validated before it is returned (docs/16).
  • Typed and measured: ships py.typed and checks the package with strict mypy. CI gates linear scaling; the local benchmark harness measures time, allocations, and RSS under a machine fingerprint (docs/12).
  • Version-matched agent guides: the CLI ships its own usage guides for coding agents, so the guide always matches the installed version (Using it from an agent).

Status

fastRAML is in beta. The parser matches the expected outcome of every TCK fixture in its evaluated scope, and the design is settled in docs/. What is not settled is the surface you code against:

  • The public API may change before 1.0, including names, signatures and model attributes. There is no release yet, so pin a commit: git+https://github.com/deiteris/FastRAML@<commit>.
  • Emitted identifiers are provisional. The fastraml://id address base and the urn:fastraml:ns:raml# RDF namespace are not frozen; read fastraml.RAML_NS rather than hard-coding it.
  • Features may be added or withdrawn. The coverage matrix records what is deferred and what is out of scope.

Report anything that looks wrong at Issues. A failure within the documented supported surface is a bug.

Requirements

Python 3.12 or newer. Tested on Linux and Windows against 3.12 and 3.13.

Installation

fastraml is not yet on PyPI. Until it is:

uv tool install git+https://github.com/deiteris/FastRAML

Python API

from fastraml import ParseOptions, ObjectShape, parse_from_path

raml = parse_from_path('api.raml', ParseOptions(unwrap=True, validate=True))
api = raml.entry_point

user = api.types['User']
user.validate({'name': 'Bob', 'age': 35})  # None, or a RamlError

shape = user.shape
if isinstance(shape, ObjectShape):
    for name, prop in shape.properties.items():
        print(name, prop.base.type, prop.required)

Pass unwrap=True, validate=True together unless you specifically want to inspect un-flattened declarations — validate=True alone unwraps a private copy of every type it checks, and the benchmarks measure it slower for that.

parse_lenient(path) returns (model, error) rather than raising, for an editor that needs a partial model on every keystroke. It still raises when there is no model to return: the entry file cannot be read, its RAML header is missing or unrecognised, the extends chain of an Overlay or Extension cannot be loaded, or its root is not a mapping.

OpenAPI export stays typed until the serialization boundary:

from fastraml import to_openapi

openapi, dropped = to_openapi(raml)
print(openapi.info.title)
get_users = openapi.paths['/users'].get
if get_users is not None:
    print(get_users.responses['200'].description)
payload = openapi.to_dict()  # JSON/YAML-ready only when you need it

sample gives a value for a request or response body, a parameter or a type:

from fastraml import SampleOptions, sample

sample(user)  # always the same valid value
sample(user, options=SampleOptions(seed='ci'))  # another one, reproducibly
sample(user, options=SampleOptions(synthesize=False))  # from the author's examples only, or SampleError

Overlays and Extensions

Parse the Overlay or Extension itself. The result is the effective API of its extends chain: entry_point is the root API with every document in the chain applied, and raml.extensions lists those documents in order. Entities keep the location of the file that wrote them, and an Overlay that changes more than the spec allows fails with not allowed in an overlay (docs/19).

raml = parse_from_path('overlays/es.raml', ParseOptions(workspace_root='.'))
print(raml.location)  # the root API's URI
print([doc.location for doc in raml.extensions])  # the chain, applied in order

The workspace root must contain the whole chain, so an extends: ../api.raml needs one above the entry file's directory, as in the example.

Garbage collection

Parsing, build_graph, lint runs and to_openapi raise CPython's full-collection threshold while they run and restore it afterwards, which keeps large documents linear in time. The setting is process-wide, so call fastraml.set_gc_tuning(False) if your application manages the collector itself (docs/12).

Files outside the entry file's directory

The parser reads files only inside a workspace root, which defaults to the directory of the file you parse. An !include, uses:, extends or JSON Schema $ref that leaves that directory fails with path is outside the workspace root, and the error suggests a root that would contain it. Set the root to a directory that contains every file the document reaches:

raml = parse_from_path('api/api.raml', ParseOptions(workspace_root='.'))

On the command line, pass -w DIR (--workspace-root). --no-workspace-guard turns the check off; use it only for documents you trust.

Command line

fastraml validate api.raml           # exit 1 and a positioned trace if invalid
fastraml validate --json *.raml      # one JSON object per file
fastraml info api.raml               # YAML backend, timing, model counts
fastraml openapi api.raml            # OpenAPI 3.0.3 YAML; --format json for JSON
fastraml lint api.raml               # recommended spec checks (see Linting)

Inspecting the model

These commands work on the resolved model, through its containment and graph views (docs/16):

fastraml list api.raml               # every name you can ask about
fastraml refs api.raml User          # everything that uses User, with the route to it
fastraml deps api.raml User          # everything User is built from, with the route
fastraml show api.raml /users        # the effective view: everything merged in, with origins
fastraml graph api.raml              # the whole projection as Turtle (or nt, dot, json)
fastraml tree api.raml               # addressed JSON retaining containment and leaf data
fastraml query --list                # 9 named analysis queries
fastraml query api.raml -n type-fan-in   # or -q '<sparql>' for your own

refs and deps print a route for each result, not only the entity it reached, for example:

Operation  api.raml:446  Add a book -request-> request -payload-> application/json -range-> ... -inherits-> Book

show accepts a type, a resource by its path or displayName, or any name that list prints. query needs the graph extra (see Optional dependencies).

Linting

fastraml lint checks whether a valid document is a good one. It runs on the effective model, after traits, resource types and inheritance are applied, so it sees what a client of the API sees. The 85 built-in rules are grouped into six categories. The default recommended ruleset enables spec; all enables every built-in rule and activated plugin:

  • spec (16 rules, the default recommended ruleset): problems the RAML and JSON Schema specifications themselves imply, such as a $ref whose sibling keywords are ignored, deprecated schemas: and schema:, a body whose media type cannot carry its declared type, an optional URI parameter that fills a whole path segment, a {version} with no version to supply it, a header typed as an object, whose serialization RAML leaves undefined, or an Extension key that silently removes a conflicting property of its master.
  • security (28 rules, opt-in): derived from the OWASP API Security Top 10 (2023) and the OAuth RFCs (6749, 6750 and 9700), for the categories a document can express. They cover authentication (unsecured operations, HTTP Basic, OAuth 1.0, the OAuth 2.0 password and implicit grants, credentials in the query string, passwords in baseUri, HTTPS-only transport and OAuth endpoints), unrestricted resource consumption (unbounded strings, numbers, arrays, files and objects, rate-limit headers and 429 responses), input validation (unanchored and backtracking-prone patterns, file types, wildcard request media types), guessable numeric resource IDs, objects that accept undeclared properties, and typed 400/422, 401 and 500 error responses. Authorization logic, server-side request forgery and business-flow abuse depend on runtime behaviour, which no rule over a document can check.
  • http (18 rules, opt-in): what RFC 9110 requires and RAML does not check: no body on 1xx, 204, 304 or HEAD responses; the WWW-Authenticate, Allow, Proxy-Authenticate, Location and Content-Range headers their status codes call for; header names that are HTTP tokens, declared once regardless of case, and not connection-specific; date headers typed as HTTP dates; a Content-Type header that agrees with the body media types; retired status codes; a 304 that repeats its 200's validators; responses the declared request can never produce; resource paths with characters a URI path forbids or with ./.. segments (RFC 3986); a charset on JSON (RFC 8259); and a media type declared twice in different case.
  • problem-details (3 rules, opt-in): for APIs that adopt RFC 9457, which replaces RFC 7807: error bodies use application/problem+json or +xml, the standard members have their standard types, and status agrees with the response code.
  • i-json (4 rules, opt-in): for APIs that adopt the RFC 7493 I-JSON profile: JSON bodies are objects or arrays, integers stay within ±(2⁵³−1), timestamps are RFC 3339 with an offset, and binary data is base64url.
  • style (16 rules, opt-in): authoring conventions such as descriptions, examples, display names, concise type spellings and closed objects.

all enables every built-in rule plus every enabled plugin. fastraml lint --explain RULE lists the OWASP category, RFC clause or CWE a rule follows from.

fastraml lint api.raml                        # the recommended rules
fastraml lint --list-rules                    # every rule, with its category, severity and source
fastraml lint --explain unused-type           # what a rule checks, with good and bad examples
fastraml lint api.raml --ruleset security     # enable one more ruleset for this run
fastraml lint api.raml --rule https-only=error    # enable or regrade one rule for this run
fastraml lint api.raml --format text          # one line per finding; also json and summary

lint exits 1 when any finding has error severity, or when a document fails to parse. Built-in rules report at warning or info, so a run fails only on rules you have raised to error, or on warnings too with --fail-on warning. Every file is linted before the command exits. Output is capped at 1,000 findings, and 100 per rule in each file, keeping errors and warnings ahead of info; the exit status still counts every finding, and --max-findings 0 removes the cap.

Keep lasting policy in the lint: section of the configuration file:

lint:
  extends: [recommended, security]  # also: style, all
  categories:
    security: { severity: error }
  rules:
    - id: unused-type
      match: '.*internal.*'         # drop only the findings whose message matches
      disabled: true

To silence one finding, put a comment on the line directly above the line it points to:

# fastraml: ignore missing-description,missing-example
User: string

For your organisation's own policy, write a plugin: a package that exports a sequence of rules under the fastraml.lint_rules entry point group.

[project.entry-points."fastraml.lint_rules"]
house-style = "myorg.ramlrules:RULES"

Installing a plugin changes nothing on its own: its rules run only once plugins: [house-style] names it in the configuration. --list-rules shows which package provides each rule. No plugins are published yet. The rule model, every built-in policy and the output formats are in docs/18.

Comparing versions

fastraml compat v1.raml v2.raml      # exit 1 if any change is breaking

The Python API renders the same comparison as Markdown, for a pull request or a build summary. examples/backward_report.py is complete and runnable, and its two RAML files exercise every model-native compatibility rule:

from fastraml import ParseOptions, backward_markdown, parse_from_path

options = ParseOptions(unwrap=True)
old = parse_from_path('v1.raml', options)
new = parse_from_path('v2.raml', options)
print(backward_markdown(old, new))

Configuration

Every command that parses a document takes --config FILE: one YAML file with parser:, lint: and compatibility: sections. For example, a deployment behind an HTTP redirect can regrade only the transition from HTTP+HTTPS to HTTPS:

compatibility:
  rules:
    - id: protocol-removed
      impact: compatible
      match:
        before: [HTTP, HTTPS]
        after: [HTTPS]

Using it from an agent

fastraml skills serves a usage guide from inside the installed package, so the instructions an agent reads always match the version that answers them:

fastraml skills install              # into ./.agents/skills/, read by most agents
fastraml skills get core             # or just print the guide

Related packages

Separate distributions, versioned independently. fastRAML depends on none of them, so the parser takes no web framework. raml-codegen reads fastraml tree output and depends on no parser, and fastraml-viewer depends on nothing.

Package Direction What it is
raml-document — A typed authoring model, and a reader that builds one from pydantic models
fastapi-raml code → RAML Renders a FastAPI app's routes as RAML, and serves them
aiohttp-raml code → RAML Code-first RAML for aiohttp: pydantic-validated views that describe themselves
fastmcp-raml RAML → MCP Serves a RAML-described API as an MCP server
raml-mock RAML → HTTP Runs an in-process mock that validates requests and answers with sample values
raml-codegen tree → code Generates a typed httpx client, or a FastAPI server interface to implement, from fastraml tree output
fastraml-viewer — The tree viewer as static assets any server can mount
sphinxcontrib-fastraml RAML → docs A Sphinx extension that renders endpoints, methods and types as native Sphinx content, links prose to them through a raml domain, and writes request walkthroughs from validated values

Each has its own uv project and gate; contrib/README.md describes how they relate.

Design documents

docs/ is normative and settled before the code; each document owns one area and states the decisions that area has already made. Start at docs/README.md.

Where fastRAML reads the spec differently from go-raml, docs/01-scope-and-coverage.md § 4 records the difference and its reason.

Optional dependencies

Extra For
fastraml[graph] (pyoxigraph) fastraml query — SPARQL over the graph projection; the graph itself needs nothing
fastraml[serve] (fastraml-viewer) fastraml serve — the document in a browser; the built viewer bundle, a static package with no dependencies of its own (consumer boundary)
fastraml[http] (httpx) or requests remote !include; supply the client yourself, or use fastraml validate -r. Synchronous clients only — from async code run the parse in asyncio.to_thread (loaders)
fastraml[re2] (google-re2) ParseOptions(regex_engine="re2") — linear-time patterns for untrusted input
libyaml selected automatically when PyYAML was built with it; its scanner differs from the pure-Python backend for a tab after a mapping colon (YAML behavior)

Development

uv sync                                  # create the environment
uv run pytest -q                         # tests
uv run ruff check . && uv run ruff format --check .
uv run mypy fastraml/

All four must pass before any change lands.

uv sync leaves out the viewer bundle, so fastraml serve does not work in a fresh checkout. Run npm run build in viewer/, then uv sync --group viewer.

RAML Test Compliance Kit

The fixtures are a submodule, from deiteris/raml-tck:

git submodule update --init    # if you cloned without --recurse-submodules
uv run pytest tests/tck

FASTRAML_TCK_DIR=<path> runs against a different checkout instead. With neither, the TCK tests skip. tests/tck/ratchet.json records the expected outcome per fixture, and CI fails on drift in either direction — a regression, or progress that was not recorded. See docs/14-testing.md.

Benchmarks

python -m bench run                      # every bench, every configuration
python -m bench ab master --bench large  # this tree against a revision, alternated over one corpus
python -m bench linearity                # time and memory against a half-size corpus
python -m bench micro sample             # single functions, per call, at sizes 5 to 1000
FASTRAML_BENCH=1 uv run pytest tests/bench # the CI gate: linear scaling

A performance claim needs a workload that runs the changed code. Measure it with bench ab, and put the time delta (if it exceeds the reported noise) and the allocation delta in the commit message (docs/12).

Licence

MIT. See LICENSE.

About

Python RAML 1.0 parser and validator with a resolved API model, linting, compatibility checks, and OpenAPI export.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages