Parse, validate, inspect, and convert RAML 1.0 in Python
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,refsanddepsinspect named entities and the routes between them.graphemits RDF, Graphviz or JSON, whiletreeemits an addressed containment view. - Analysis and linting: run custom SPARQL or one of 9 named graph queries.
lintchecks 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:
compatwalks 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:
samplereturns 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.typedand 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).
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://idaddress base and theurn:fastraml:ns:raml#RDF namespace are not frozen; readfastraml.RAML_NSrather 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.
Python 3.12 or newer. Tested on Linux and Windows against 3.12 and 3.13.
fastraml is not yet on PyPI. Until it is:
uv tool install git+https://github.com/deiteris/FastRAMLfrom 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 itsample 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 SampleErrorParse 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 orderThe workspace root must contain the whole chain, so an extends: ../api.raml
needs one above the entry file's directory, as in the example.
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).
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.
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)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 ownrefs 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).
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 defaultrecommendedruleset): problems the RAML and JSON Schema specifications themselves imply, such as a$refwhose sibling keywords are ignored, deprecatedschemas:andschema:, a body whose media type cannot carry its declared type, an optional URI parameter that fills a whole path segment, a{version}with noversionto 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 inbaseUri, HTTPS-only transport and OAuth endpoints), unrestricted resource consumption (unbounded strings, numbers, arrays, files and objects, rate-limit headers and429responses), input validation (unanchored and backtracking-prone patterns, file types, wildcard request media types), guessable numeric resource IDs, objects that accept undeclared properties, and typed400/422,401and500error 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; theWWW-Authenticate,Allow,Proxy-Authenticate,LocationandContent-Rangeheaders 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; aContent-Typeheader 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); acharseton 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 useapplication/problem+jsonor+xml, the standard members have their standard types, andstatusagrees 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 summarylint 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: trueTo silence one finding, put a comment on the line directly above the line it points to:
# fastraml: ignore missing-description,missing-example
User: stringFor 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.
fastraml compat v1.raml v2.raml # exit 1 if any change is breakingThe 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))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]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 guideSeparate 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.
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.
| 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) |
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.
The fixtures are a submodule, from deiteris/raml-tck:
git submodule update --init # if you cloned without --recurse-submodules
uv run pytest tests/tckFASTRAML_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.
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 scalingA 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).
MIT. See LICENSE.
